您錯用了管道。
管道是一個單向通信通道。您可以使用它將數據從父進程發送到子進程,也可以將數據從子進程發送到父進程。你不能這樣做 - 即使你在這兩個過程中保持管道的讀寫通道都是打開的,每個過程都不會知道什麼時候輪到從管道讀取數據(例如,你最終可能會讀到兒童應該由父母閱讀)。
將字符從父母發送到孩子的代碼看起來大部分是正確的(下面有更多詳細信息),但是您需要重新設計孩子到父母的通信。現在,您有兩個選項可以將結果從子項發送到父項:
- 使用另一個管道。在爲孩子與父母溝通分擔之前,您設置了一個額外的管道。這使設計和代碼複雜化,因爲現在有4個文件描述符可以從2個不同的管道進行管理,並且在關閉每個文件描述符以確保進程不會掛起時需要小心。這也可能有點矯枉過正,因爲孩子只向父母發送號碼。
- 將孩子的結果作爲退出值返回。這就是你現在正在做的,這是一個不錯的選擇。但是,您無法在父級中檢索該信息:孩子的終止狀態會告訴您處理的字符數,您可以使用
waitpid(2)
獲取此值,但您之前完成的操作不會看到status
(其中包含您正在尋找)。
請記住,子進程有自己的地址空間。嘗試在父文件中讀取charCounter
是沒有意義的,因爲父文件從未修改它。子進程獲得自己的副本charCounter
,所以任何修改都只能由孩子看到。你的代碼似乎假定不然。
爲了使這更明顯,我建議將變量的聲明移到相應的進程代碼。需要在兩個進程中複製fd
和pid
,其他變量是特定於每個進程的任務的。因此,您可以將status
和nChar
的聲明移至父進程特定代碼,並且您可以將charCounter
,readbuffer
和readIn
移動到子代。這將非常明顯地表明變量在每個過程中都是完全獨立的。
現在,一些更具體的說明:
pipe(2)
可以返回一個錯誤。你忽略了返回值,你不應該這樣做。至少,您應該打印一條錯誤消息並終止,如果pipe(2)
出於某種原因失敗。我也注意到你在fork(2)
與printf("fork error %d\n", pid);
報告錯誤。這不是正確的方法:fork(2)
和其他系統調用(和庫調用)在出錯時總是返回-1
,並設置全局變量errno
以指示原因。因此,printf()
將始終打印分叉錯誤-1無論錯誤原因是什麼。這沒有幫助。此外,它將錯誤消息打印到stdout
,由於多種原因,應將錯誤消息打印到stderr
。所以我建議使用perror(3)
代替,或手動將錯誤打印到stderr
與fprintf(3)
。 perror(3)
還具有將錯誤消息描述附加到所提供文本的附加好處,所以它通常是一個不錯的選擇。
例子:
if (pipe(fd) < 0) {
perror("pipe(2) error");
exit(EXIT_FAILURE);
}
您使用整個代碼也可能會失敗,並再次,你忽略了(可能的)錯誤返回等功能。 close(2)
可能會失敗,以及read(2)
。處理錯誤,他們在那裏是有原因的。
您使用readIn
的方式是錯誤的。 readIn
是read(2)
的結果,它返回讀取的字符數(它應該是int
)。該代碼使用readIn
,就好像它是下一個閱讀的字符一樣。讀取的字符存儲在readbuffer
中,而readIn
會告訴您該緩衝區中有多少個字符。因此,您使用readIn
循環訪問緩衝區內容並對字符進行計數。事情是這樣的:
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
while (readIn > 0) {
int i;
for (i = 0; i < readIn; i++) {
if (readbuffer[i] != ' ') {
charCounter++;
}
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
}
現在,關於父進程:
你是不是寫的人物進入管道。這是毫無意義:
write(fd[1], &argv, sizeof(argv));
&argv
是char ***
類型,並且sizeof(argv)
相同sizeof(char **)
,因爲argv
是一個char **
。傳遞給函數時,數組維不保留。
您需要通過argv
手動循環和寫入每個進入管道,就像這樣:
int i;
for (i = 1; i < argv; i++) {
size_t to_write = strlen(argv[i]);
ssize_t written = write(fd[1], argv[i], to_write);
if (written != to_write) {
if (written < 0)
perror("write(2) error");
else
fprintf(stderr, "Short write detected on argv[%d]: %zd/zd\n", i, written, to_write);
}
}
注意argv[0]
是程序的名稱,這就是爲什麼i
從1開始。如果你想也計算argv[0]
,只需將其更改爲從0開始。
最後,正如我之前說的,你需要使用waitpid(2)
獲取的終止狀態度日的孩子返回的實際數量。因此,您只能在waitpid(2)
返回並確保孩子正常終止後才能打印結果。另外,要獲取實際的退出代碼,您需要使用宏(如果WIFEXITED
返回true,則只能使用該宏)。
所以這裏是所有這些問題的完整方案解決:
// Characters from command line arguments are sent to child process
// from parent process one at a time through pipe.
//
// Child process counts number of characters sent through pipe.
//
// Child process returns number of characters counted to parent process.
//
// Parent process prints number of characters counted by child process.
#include <stdlib.h>
#include <stdio.h>
#include <string.h> // for strlen()
#include <unistd.h> // for fork()
#include <sys/types.h> // for pid_t
#include <sys/wait.h> // for waitpid()
int main(int argc, char **argv)
{
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
perror("pipe(2) error");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid < 0) {
perror("fork(2) error");
exit(EXIT_FAILURE);
}
if (pid == 0) {
int readIn;
int charCounter = 0;
char readbuffer[80];
if (close(fd[1]) < 0) {
perror("close(2) failed on pipe's write channel");
/* We use abort() here so that the child terminates with SIGABRT
* and the parent knows that the exit code is not meaningful
*/
abort();
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
while (readIn > 0) {
int i;
for (i = 0; i < readIn; i++) {
if (readbuffer[i] != ' ') {
charCounter++;
}
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
}
if (readIn < 0) {
perror("read(2) error");
}
printf("The value of charCounter is %d\n", charCounter);
return charCounter;
} else {
int status;
if (close(fd[0]) < 0) {
perror("close(2) failed on pipe's read channel");
exit(EXIT_FAILURE);
}
int i;
for (i = 1; i < argc; i++) {
size_t to_write = strlen(argv[i]);
ssize_t written = write(fd[1], argv[i], to_write);
if (written != to_write) {
if (written < 0) {
perror("write(2) error");
} else {
fprintf(stderr, "Short write detected on argv[%d]: %zd/%zd\n", i, written, to_write);
}
}
}
if (close(fd[1]) < 0) {
perror("close(2) failed on pipe's write channel on parent");
exit(EXIT_FAILURE);
}
if (waitpid(pid, &status, 0) < 0) {
perror("waitpid(2) error");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
printf("CS201 - Assignment 3 - Andy Grill\n");
printf("The child processed %d characters\n\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Child terminated abnormally with signal %d\n", WTERMSIG(status));
} else {
fprintf(stderr, "Unknown child termination status\n");
}
return 0;
}
}
最後的一些注意事項:
- 外殼由空格分割的參數,所以如果你啓動的程序爲
./a.out this is a test
,該代碼不會看到單個空間。這是無關緊要的,因爲無論如何都應該忽略空格,但如果要測試代碼真的忽略空格,則需要引用這些參數,以便shell不處理它們,如./a.out "this is a test" "hello world" "lalala"
。
- 只使用程序退出代碼的最右邊(最低有效)8位,因此
WEXITSTATUS
永遠不會返回超過255個。如果子讀取的字符數超過255個,則該值將環繞,因此您實際上有一個字符計數器模256.如果這是一個問題,那麼你需要採取另一種方法,並設置第二個管道進行孩子到家長的溝通,並在那裏寫出結果(並讓父母讀取它)。您可以在man 2 waitpid
證實了這一點:
WEXITSTATUS(狀態)
返回子的退出狀態。這包括狀態參數的最低 顯著8位,在調用中指定的子 退出(3)或_exit(2)或作爲主要參數爲迴歸 聲明()。僅當 WIFEXITED返回true時才應使用此宏。
提示:張貼在計算器代碼時,一定要通過4個空格所以它得到正確顯示縮進它。請參閱[編輯幫助](http://stackoverflow.com/editing-help)。 – spectras
謝謝,提出改變編輯適當間距 –