UNIX系統過濾程序從標準輸入讀取數據,對其進行適當處理后寫到標準輸出。幾個過濾程序通常在shell管道命令行中線性地連接。當一個程序產生某個過濾程序的輸入,同時又讀取該過濾程序的輸出時,則該過濾程序就成為協同進程(coPRocess)。
Korn shell提供了協同進程。Bourne shell、Bourne-again shell和C shell并沒有提供按協同進程方式將進程連接起來的方法。協同進程通常在shell的后臺運行,其標準輸入和標準輸出通過管道連接到另一個程序。
popen只提供連接到另一個進程的標準輸入或標準輸出的一個單向管道,而對于協同進程,則它有連接到另一個進程的兩個單向管道——一個接到其標準輸入,另一個則來自其標準輸出。我們先要將數據寫到其標準輸入,經其處理后,再從其標準輸出讀取數據。
實例
進程線創建兩個管道:一個是協同進程的標準輸入,另一個是協同進程的標準輸出。圖15-8顯示了這種安排。
圖15-8 寫協同進程的標準輸入,讀它的標準輸出
程序清單15-8程序是一個簡單的協同進程,它從其標準輸入讀兩個數,計算它們的和,然后將結果寫至標準輸出。
程序清單15-8 對兩個數求和的簡單過濾程序
#include "apue.h"intmain(void){ int n, int1, int2; char line[MAXLINE]; while((n = read(STDIN_FILENO, line, MAXLINE)) > 0) { line[n] = 0; /* null terminate */ if(sscanf(line, "%d%d", &int1, &int2) == 2) { sprintf(line, "%d/n", int1 + int2); n = strlen(line); if(write(STDOUT_FILENO, line, n) != n) err_sys("write error"); } else { if(write(STDOUT_FILENO, "invalid args/n", 13) != 13) err_sys("write error"); } } exit(0);}
對此程序進行編譯,將其可執行目標代碼存入名為add2的文件。
程序清單15-9從其標準輸入讀入兩個數之后調用add2協同進程,并將協同進程送來的值寫到其標準輸出。
程序清單15-9 驅動add2過濾程序的程序
#include "apue.h"static void sig_pipe(int); /* our signal handler */intmain(void){ int n, fd1[2], fd2[2]; pid_t pid; char line[MAXLINE]; if(signal(SIGPIPE, sig_pipe) == SIG_ERR) err_sys("signal error"); if(pipe(fd1) < 0 || pipe(fd2) < 0) err_sys("pipe error"); if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid > 0) /* parent */ { close(fd1[0]); close(fd2[1]); while(fgets(line, MAXLINE, stdin) != NULL) { n = strlen(line); if((m = write(fd1[1], line, n)) != n); /* 無論這里的if條件是真是假都執行err_sys(“write error to pipe”);不知為何 */ { err_sys("write error to pipe"); } if((n = read(fd2[0], line, MAXLINE)) < 0) err_sys("read error from pipe"); if(n == 0) { err_msg("child closed pipe"); break; } line[n] = 0; /* null terminate */ if(fputs(line, stdout) == EOF) err_sys("fputs error"); } if(ferror(stdin)) err_sys("fgets error on stdin"); exit(0); } else /* child */ { close(fd1[1]); close(fd2[0]); if(fd1[0] != STDIN_FILENO) { if(dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO) err_sys("dup2 error to stdin"); close(fd1[0]); } if(fd2[1] != STDOUT_FILENO) { if(dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) err_sys("dup2 error to stdout"); close(fd2[1]); } if(execl("./add2", "add2", (char *)0) < 0) err_sys("execl error"); } exit(0);}static voidsig_pipe(int signo){ printf("SIGPIPE caught/n"); exit(1);}
在程序中創建了兩個管道,父、子進程各自關閉它們不需要使用的端口。兩個管道一個用作協同進程的標準輸入,另一個則用作它的標準輸出。子進程調用dup2使管道描述符移至其標準輸入和標準輸出,然后調用execl。
實例
在協同進程add2中,有意地使用了read和writeI/O(UNIX系統調用)。如果使用標準I/O改寫該協同進程,其后果是什么呢?程序清單15-10為改寫后的版本。
程序清單15-10 對兩個數求和的過濾程序,使用標準I/O
#include "apue.h"intmain(void){ int int1, int2; char line[MAXLINE]; while(fgets(line, MAXLINE, stdin) != NULL) { if(sscanf(line, "%d%d", &int1, &int2) == 2) { if(printf("%d/n", int1 + int2) == EOF) err_sys("printf error"); } else { if(printf("invalid args/n") == EOF) err_sys("printf error"); } } exit(0);}
若程序清單15-9調用此新的協同進程,則它不再工作。問題出在系統默認的標準I/O緩沖機制上(見http://www.CUOXin.com/nufangrensheng/p/3505307.html)。當調用程序清單15-10所示程序時,對標準輸入的第一個fgets引起標準I/O庫分配一個緩沖區,并選擇緩沖區的類型。因為標準輸入是個管道,所以標準I/O庫由系統默認是全緩沖的。對標準輸出也做同樣的處理。當add2從其標準輸入讀取而發生阻塞時,程序清單15-9程序從管道讀時也發生阻塞,于是產生了死鎖。
為此,更改將要運行的協同進程的緩沖類型,在程序清單15-10中的while循環之前加上下面4行:
if(setvbuf(stdin, NULL, _IOLBF, 0) != 0) err_sys("setvbuf error");if(setvbuf(stdout, NULL, _IOLBF, 0) != 0) err_sys("setvbuf error");
這些代碼行使得當有一行可用時,fgets就返回,并使得當輸出一換行符時,printf立即執行fflush操作。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答