2012-10-17 131 views
2

我正在學習Linux中的進程管理,我需要通過管道使子進程和父進程通信。我宣佈兩種結構:通過管道寫結構數組

typedef struct 
{ 
    double min, max, avg; /*Number stats*/ 
} Stats; 

typedef struct { 
    pid_t pid;  /*Process ID*/ 
    int first;  /*First number to process*/ 
    int last;  /*Last number to process*/ 
    int fd[2];  /*Pipe descriptor*/ 
    Stats stats; /*Stats computed by process*/ 
}Process_list; 

我需要將M子進程來計算一些統計出來的數字陣列(分工)的。然後,子進程將讀取Process_list結構,從第一個到最後一個(在那裏指定)處理數組中的數字,並將計算的統計數據保存到統計結構中。

最相關的部分我的代碼的麻煩有以下幾種(我添加註釋,而不是其他代碼片段來解釋什麼是做OK):

int main(int argc, char *argv[]) 
{ 
    pList=(Process_list *)malloc((M+1)*sizeof(Process_list)); 
    /*Correct allocation is checked*/ 

    int i; 
    pid_t pid; 
    for (i=0 ; i<M ; i++)  /*M clones*/ 
    { 
     /*Fork and pipe creation*/ 
     pid = fork(); 
     pipe(pList[i].fd); 

     /*If it's the child*/ 
     if (pid == 0) 
     { 
      pList[i].pid=pid; 
      printf("CHILD %d: %d a %d\n",i, pList[i].first,pList[i].last); 

      /*A function here computes stats and saves them OK in the struct*/ 
      /*(e.g. min is saved in pList[i].stats.max, when printed it's a reasonable value)*/ 

      /*Now I need to send the info to the parent*/ 
      ret1=close(pList[i].fd[0]); 
      /*ret1=0 => OK */ 

      ret2=write(pList[i].fd[1], &(pList[i].stats), sizeof(Stats)); 
      printf("return write: %d\n",ret2); 
      /*TROUBLE HERE! This isn't even printed. sizeof(Stats)=24 which I think is OK*/ 

      exit(EXIT_SUCCESS); 
     } 

     /*Parent*/ 
     else if (pid > 0) 
     { 
      wait(NULL); /*Is this really neccesary?*/ 

      ret1=close(pList[i].fd[1]); 
      ret2=read(pList[i].fd[0], &(pList[i].stats), sizeof(Stats)); 
      /*Both ret1 and ret2 = 0*/ 

      printf("[p]Mín: %lf\n Max: %lf\nAverage: %lf\n",pList[i].stats.min,pList[i].stats.max,pList[i].stats.avg); 
      /*Everything printed here = 0.0000000000*/ 


     } 

     else /*fork error*/ 
       return -1; 
    } 

所以我的問題是,孩子的計算他們的統計數據完美無缺,但父母並沒有收到他們的數據。 write()函數什麼都不做。這發生在M的每個值(包括M = 1 - 只有一個進程)。

另外我不知道是否需要等待(NULL),因爲我看到一些不使用它的工作示例。但是,如果我不寫在那裏,父母的printfs將出現在孩子的前面,所以我認爲它不會等待孩子寫入管道。無論如何,沒有它,它就無法工作。

或者,也許結構方法是不是一個好的?

非常感謝您提前!

+2

[不投的malloc()''的返回值。(http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) – 2012-10-17 17:51:57

回答

2

您必須在分叉之前創建管道。否則,你正在創建兩個管道。一個在你的父母身上,另一個在你的孩子身上。

wait爲孩子獲得所有數據之後,否則如果孩子阻塞在write上以便管道緩衝區清除,最終可能會永久等待。 wait釋放與死亡孩子相關的資源。如果你的孩子沒有死,這將等到它。

另一件事:使用fwrite將數據塊(結構數組)寫入文件描述符。 fread供閱讀。您可以使用fdopen將文件描述符轉換爲FILE*

int main(int argc, char *argv[]) 
{ 
pList=(Process_list *)malloc((M+1)*sizeof(Process_list)); 
/*Correct allocation is checked*/ 

int i; 
pid_t pid; 
for (i=0 ; i<M ; i++)  /*M clones*/ 
{ 
    /*Fork and pipe creation*/ 
    if(pipe(pList[i].fd)) { 
     perror("pipe"); 
     return -1; 
    } 
    pid = fork(); 

    /*If it's the child*/ 
    if (pid == 0) 
    { 
     pList[i].pid=pid; 
     printf("CHILD %d: %d a %d\n",i, pList[i].first,pList[i].last); 

     /*A function here computes stats and saves them OK in the struct*/ 
     /*(e.g. min is saved in pList[i].stats.max, when printed it's a reasonable value)*/ 

     /*this just closes your read end of the pipe*/ 
     close(pList[i].fd[0]); 
     /*ret1=0 => OK */ 

     FILE* fp = fdopen(pList[i].fd[1], "w"); 
     while (!fwrite(&(pList[i].stats), sizeof(Stats), 1, fp) && !feof(fp)); 
     if (feof(fp)) { 
      fclose(fp); 
      fprintf(stderr, "reader closed his end of the pipe\n"); 
      return -1; 
     } 

     // sends eof to reader 
     fclose(fp); 

     printf("%d bytes written successfully",sizeof(Stats)); 
     /*TROUBLE HERE! This isn't even printed. sizeof(Stats)=24 which I think is OK*/ 

     exit(EXIT_SUCCESS); 
    } 

    /*Parent*/ 
    else if (pid > 0) 
    { 

     // this is actually nessesary to ensure that the pipe properly closes 
     close(pList[i].fd[1]); 
     FILE* fp = fdopen(pList[i].fd[0], "r"); 

     if (!fread(&(pList[i].stats), sizeof(Stats), 1, fp)) { 
      fprintf(stderr, "writer sent eof, but not enough data\n"); 
      return -1; 
     } 

     fclose(fp); 

     printf("[p]Mín: %lf\n Max: %lf\nAverage: %lf\n",pList[i].stats.min,pList[i].stats.max,pList[i].stats.avg); 
     /*Everything printed here = 0.0000000000*/ 

     wait(0); 

    } 

    else /*fork error*/ 
      return -1; 
} 
+0

這是令人難以置信的真棒。非常感謝您的回覆。我從中學到了很多東西!只是一個小問題,如果你不介意,爲什麼在這種情況下'fwrite'和'fread'好於'write'和'read'?如果進程需要寫入多個數據塊,它們似乎是必要的。但是,考慮到它只會寫一個區塊,爲什麼這是一個更好的選擇?更一致還是更高效? – Serge

+0

'fwrite'將確保你的數據以特定大小的卡盤離開緩衝區(在這種情況下'sizeof(Stats)')。沒有結構的任何部分將被寫入,只有整個結構。 'write'會將數據寫入一個字節的數據塊中,這意味着如果寫入緩衝區已滿,您最終可能只寫入一半結構,然後讀者關閉管道的末端,您將永遠不知道如何處理第二個數據一半(或讀者正在做什麼與半結構的事情)。 'fread'在讀取結束時也是這樣。 –

+0

另一件事是'fread'和'fwrite'是對緩衝流進行操作的庫調用,因此可以通過用戶空間中的附加緩衝區提供性能。 'read'和'write'是系統調用,它將**總是**調用內核。做一個練習:編寫一個程序,用'read' /'write'一個字節複製一個文件,另一個用'fread' /'fwrite'複製一個文件,並查看20MB +之間的差異。 –

0

對於終止的孩子,執行wait()允許系統釋放與孩子相關的資源;如果wait()未執行,則終止的子節點保持「殭屍」狀態。這是一種安全的做法,總是很好用。參考:man -s2 wait

wait(NULL)系統調用意味着,父進程將等待任何子進程(在你的情況下,它將等待剛剛分叉的子進程),並且不會存儲子進程返回的狀態。如果您想要更多控制等待,請查看:man -s2 waitpid。基本上,wait()是一種waitpid()的香草形式。

現在,管道部分。您正在子進程內創建一個管道。這對父進程是如何訪問的?實際上,它的工作原理如下:if a pipe is created in a parent process, the pipe fd's will be copied to the child process.不是相反的。 參見:man -s2 pipe

因此,在父進程創建的管道,然後發出fork()然後用管FD子進程發送給家長。孩子現在可以將write()中的數據寫入管道的寫入fd中,父代碼現在可以用read()子進程寫入的內容。

您編寫了一個從小孩到父母的單向IPC通信。因此,您正在關閉子級中的讀取fd,並且您還正在關閉父級中的寫入fd。所以,這樣你只能寫在孩子身上,只能在父母身上讀。邏輯正確實施。沒有錯誤。

一旦你修復了pipe()(在父級中創建管道然後分叉)的邏輯問題,你的代碼應該按照你的意圖工作。沒有理由不使用任何數據類型或你寫的結構。