PIPE
http://ldl.wisplus.net/2010/10/01/linux%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1%EF%BC%9A%E7%AE%A1%E9%81%93/
概述:
int pipe(int pipefd[2]);
調用pipe函數在核心中開闢一塊緩衝區(稱為管道)用於單向通訊,它有一個讀端一個寫端,然後通過filedes參數傳給使用者程式兩個檔案描述符,filedes[0]指向PIPE的讀端,filedes[1]指向PIPE的寫端。所以在使用者程式看起來就像一個開啟的檔案,通過read(filedes[0]);
或者write(filedes[1]); 向這個檔案讀寫資料其實是在讀寫核心緩衝區。
建立PIPE的基本步驟:
• 父進程調用pipe 開闢PIPE,得到兩個檔案描述符指向管道的兩端。
• 父進程調用fork 建立子進程,那麼子進程也有兩個檔案描述符指向同一管道。
• 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往PIPE裡寫,子進程可以從PIPE裡讀,PIPE是用環形隊列實現的,資料從寫端流入從讀端流出,這樣就實現了處理序間通訊
12345678910111213141516171819202122232425262728293031323334 |
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> #include<string.h> int main() { char buf[20]; pid_t pid; int fd[2]; int n; pipe(fd); //建立管道 if ((pid = fork()) < 0) //fork子進程 { perror ( "fork error" ); exit (-1); } else if (pid == 0) //子進程中 { close(fd[1]); //關閉寫端 n = read(fd[0],buf,20); write(STDOUT_FILENO,buf,n); close(fd[0]); //關閉讀端 exit (0); } else //父進程中 { close(fd[0]); //關閉讀端 write(fd[1], "hello world" , strlen ( "hello world" )); close(fd[1]); //關閉寫端 waitpid(pid,NULL,0); exit (0); } } |
popen函數與pclose函數
標準IO函數庫提供了popen函數,它建立一個管道並啟動另外一個進程,該進程從該PIPE讀出標準輸入或將標準輸出寫入該PIPE。
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen函數:先執行fork,然後調用exec(sh)以執行command,並且返回一個標準I/O檔案指標。(錯誤返回NULL)
如果type是”r”,則檔案指標串連到command的標準輸出,(該進程為讀段,command所指進程為寫端),參數”w”同理.
This command is passed to /bin/sh using the -c flag;
pclose函數:關閉由popen建立的標準I/O流,等待命令執行結束,然後返回shell的終止狀態(錯誤返回-1)
12345678910111213141516171819202122232425262728293031 |
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #define PAGER "${PAGER:-more}" //如果shell變數PAGER已經定義而且非空,則使用其值,否則使用字串more int main() { char line[100]; FILE *fpin,*fpout; if ((fpin = fopen ( "A.txt" , "r" )) == NULL) { perror ( "can't open A.txt" ); exit (-1); } if ((fpout = popen(PAGER, "w" )) == NULL) { perror ( "popen error" ); exit (-1); } while ( fgets (line,100,fpin) != NULL) { if ( fputs (line,fpout) == EOF) { perror ( "fputs error to pipe" ); exit (-1); } } if (pclose(fpout) == -1) perror ( "pclose error" ); exit (0); } |
FIFO
FIFO即是命名PIPE,檔案系統中有個路徑名與之關聯。PIPE只能由有親緣關係的進程使用,它們共同的祖先進程建立了管道。但是,通過FIFO,不相關的進程也能交換資料
建立FIFO
int mkfifo(const char *pathname, mode_t mode);
mode為存取許可權(需結合進程的umask).一般的檔案I/O函數都可以用於FIFO
mkfifo函數已經隱含指定O_CREAT | O_EXCL,也就是說,要麼建立一個新的FIFO,要麼返回EEXIST錯誤(檔案已經存在)
刪除FIFO
int unlink(const char *pathname);
不同於PIPE,FIFO只有通過unlink才能從檔案系統中刪除
開啟FIFO
int open(const char *pathname, int flags);
使用open函數開啟FIFO,預設情況下沒有指定O_NONBLOCK標誌
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
//fifo_write.c #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<errno.h> #include<fcntl.h> #define FIFO_FILE "/tmp/myfifo" int main() { int fd = 0; int n; char buf[100]; if ((fd = open(FIFO_FILE,O_WRONLY | O_NONBLOCK)) < 0) //非阻塞方式開啟 { perror ( "open error" ); exit (-1); } while (1) { fgets (buf,100,stdin); n = strlen (buf); if ((n = write(fd,buf,n)) < 0) { if ( errno == EAGAIN) printf ( "The FIFO has not been read yet.Please try later\n" ); } } return 0; } //fifo_read.c #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<errno.h> #define FIFO_FILE "/tmp/myfifo" int main() { char buf[100]; int n = 0; int fd; if ((mkfifo(FIFO_FILE,S_IRWXU) < 0) && ( errno != EEXIST)) //如果該fifo檔案不存在,建立之 { perror ( "mkfifo error" ); exit (-1); } if ((fd = open(FIFO_FILE,O_RDONLY | O_NONBLOCK)) < 0) //非阻塞方式開啟 { perror ( "open error" ); exit (-1); } while (1) { if ((n = read(fd,buf,100)) < 0) { if ( errno == EAGAIN) { printf ( "No data yet\n" ); } } else write(STDOUT_FILENO,buf,n); sleep(1); //sleep } unlink(FIFO_FILE); return 0; } |
FIFO與PIPE的讀寫:
(1)對於PIPE或FIFO的write總是往末尾添加資料,對他們的read則總是從開頭返回資料。如果對PIPE或FIFO調用lseek,那就返回ESPIPE錯誤
(2)一個檔案描述符能以2中方式設定成非阻塞:(預設為阻塞)
• 調用open是可指定O_NONBLOCK標誌
• 如果檔案描述符已經開啟,那麼可以調用fcntl設定O_NONBLOCK標誌(PIPE只能採用這種方式)
(3)讀寫規則:
阻塞(預設設定):
唯讀open
• FIFO已經被唯寫開啟:成功返回
• FIFO沒有被唯寫開啟:阻塞到FIFO被開啟來寫
唯寫open
• FIFO已經被唯讀開啟:成功返回
• FIFO沒有被唯讀開啟:阻塞到FIFO被開啟來讀
從空PIPE或空FIFO中read
• FIFO或PIPE已經被唯寫開啟:阻塞到PIPE或FIFO中有資料或者不再為寫開啟著
• FIFO或PIPE沒有被唯寫開啟:返回0(檔案結束符)
write
• FIFO或PIPE已經被唯讀開啟:
寫入資料量不大於PIPE_BUF(保證原子性):有足夠空間存放則一次性全部寫入,沒有則進入睡眠,直到當緩衝區中有能夠容納要寫入的全部位元組數時,才開始進行一次性寫操作
寫入資料量大於PIPE_BUF(不保證原子性):緩衝區一有空閑地區,進程就會試圖寫入資料,函數在寫完全部資料後返回
• FIFO或PIPE沒有被唯讀開啟:給線程產生SIGPIPE(預設終止進程)
O_NONBLOCK設定:
唯讀open
• FIFO已經被唯寫開啟:成功返回
• FIFO沒有被唯寫開啟:成功返回
唯寫open
• FIFO已經被唯讀開啟:成功返回
• FIFO沒有被唯讀開啟:返回ENXIO錯誤
從空PIPE或空FIFO中read
• FIFO或PIPE已經被唯寫開啟:返回EAGAIN錯誤
• FIFO或PIPE沒有被唯寫開啟:返回0(檔案結束符)
write
• FIFO或PIPE已經被唯讀開啟:
寫入資料量不大於PIPE_BUF(保證原子性):有足夠空間存放則一次性全部寫入,沒有則返回EAGAIN錯誤(不會部分寫入)
寫入資料量大於PIPE_BUF(不保證原子性):有足夠空間存放則全部寫入,沒有則部分寫入,函數立即返回
• FIFO或PIPE沒有被唯讀開啟:給線程產生SIGPIPE(預設終止進程)
PIPE或FIFO若干額外的規則:
• 如果請求讀取的資料量多餘當前可用的資料量,那麼返回這些可用的資料
• 如果請求寫入的資料位元組數小於或等於PIPE_BUF,那麼write操作保證是原子的(O_NONBLOCK標誌的設定對原子性沒有影響)
• 當對PIPE或FIFO最後一個關閉時,仍在該PIPE或FIFO上的資料將被丟棄
FIFO與PIPE的限制:
• 它們是半雙工的(單向性),即資料只能在一個方向上流動。由進程A流向進程B或由進程B流向進程A。
• PIPE的讀寫端通過開啟的檔案描述符來傳遞,因此要通訊的兩個進程必須從它們的公用祖先那裡繼承PIPE檔案描述符。FIFO可以實現無關進程間的通訊。
• 一個進程在任意時刻開啟的最大檔案描述符個數OPEN_MAX(通過調用sysconf(_SC_OPEN_MAX)獲得)
• 可原子地寫往PIPE或FIFO的最大資料量PIPE_BUF(通常定義在limits.h)
小結:
• PIPE普遍用於SHELL中,不過也可以從程式中使用,往往是從子程式向父程式回傳資訊。使用PIPE時涉及的某些代碼(pipe、fork、close、exec和waitpid)可通過使用popen和pclose來避免,由它們處理具體細節並啟用一個shell
• FIFO與管道類似,但他們是用mkfifo建立的,之後需要用open開啟。開啟管道時必須小心,因為有許多規則制約著open的阻塞與否(甚至發生死結)