#include<unistd.h> int pipe(int fd[2])
若是成功,則返回值是0,若是失敗,則返回值是-1,而且設置errno。
成功調用pipe函數以後,會返回兩個打開的文件描述符,一個是管道的讀取端描述符pipefd[0],另外一個是管道的寫入端描述符pipefd[1]。管道沒有文件名與之關聯,所以程序沒有選擇,只能經過文件描述符來訪問管道,只有那些能看到這兩個文件描述符的進程纔可以使用管道。那麼誰能看到進程打開的文件描述符呢?只有該進程及該進程的子孫進程才能看到。這就限制了管道的使用範圍。程序員
write(pipefd[1],wbuf,count);
一旦向管道的寫入端寫入數據後,就能夠對讀取端描述符pipefd[0]調用read,讀出管道里面的內容。以下所示,管道上的read調用返回的字節數等於請求字節數和管道中當前存在的字節數的最小值。若是當前管道爲空,那麼read調用會阻塞(若是沒有設置O_NONBLOCK標誌位的話)。shell
調用pipe函數返回的兩個文件描述符中,讀取端pipefd[0]支持的文件操做定義在read_pipefifo_fops,寫入端pipefd[1]支持的文件操做定義在write_pipefifo_fops,其定義以下:編程
const struct file_operations read_pipefifo_fops = { //讀端相關操做 .llseek = no_llseek, .read = do_sync_read, .aio_read = pipe_read, .write = bad_pipe_w, //一旦寫,將調用bad_pipe_w .poll = pipe_poll, .unlocked_ioctl = pipe_ioctl, .open = pipe_read_open, .release = pipe_read_release, .fasync = pipe_read_fasync,};const struct file_operations write_pipefifo_fops = {//寫端相關操做 .llseek = no_llseek, .read = bad_pipe_r, //一旦讀,將調用bad_pipe_r .write = do_sync_write, .aio_write = pipe_write, .poll = pipe_poll, .unlocked_ioctl = pipe_ioctl, .open = pipe_write_open, .release = pipe_write_release, .fasync = pipe_write_fasync,};
咱們能夠看到,對讀取端描述符執行write操做,內核就會執行bad_pipe_w函數;對寫入端描述符執行read操做,內核就會執行bad_pipe_r函數。這兩個函數比較簡單,都是直接返回-EBADF。所以對應的read和write調用都會失敗,返回-1,並置errno爲EBADF。async
static ssize_t bad_pipe_r(struct file filp, char __user buf, size_t count, loff_t ppos) { return -EBADF; //返回錯誤 } static ssize_t bad_pipe_w(struct file filp, const char __user buf, size_t count,loff_t ppos) { return -EBADF; }
咱們只介紹了pipe函數接口,至今尚看不出來該如何使用pipe函數進行進程間通訊。調用pipe以後,進程發生了什麼呢?請看圖9-5。
函數
能夠看到,調用pipe函數以後,系統給進程分配了兩個文件描述符,即pipe函數返回的兩個描述符。該進程既能夠往寫入端描述符寫入信息,也能夠從讀取端描述符讀出信息。但是一個進程管道,起不到任何通訊的做用。這不是通訊,而是自言自語。
若是調用pipe函數的進程隨後調用fork函數,建立了子進程,狀況就不同了。fork之後,子進程複製了父進程打開的文件描述符(如圖9-6所示),兩條通訊的通道就創建起來了。此時,能夠是父進程往管道里寫,子進程從管道里面讀;也能夠是子進程往管道里寫,父進程從管道里面讀。這兩條通路都是可選的,可是不能都選。緣由前面介紹過,管道里面是字節流,父子進程都寫、都讀,就會致使內容混在一塊兒,對於讀管道的一方,解析起來就比較困難。常規的使用方法是父子進程一方只能寫入,另外一方只能讀出,管道變成一個單向的通道,以方便使用。如圖9-7所示,父進程放棄讀,子進程放棄寫,變成父進程寫入,子進程讀出,成爲一個通訊的通道…
spa
int pipefd[2]; pipe(pipefd); switch(fork()) { case -1: /fork failed, error handler here/ case 0: /子進程/ close(pipefd[1]) ; /關閉掉寫入端對應的文件描述符/ /子進程能夠對pipefd[0]調用read/ break; default: /父進程/ close(pipefd[0]); /父進程關閉掉讀取端對應的文件描述符/ /父進程能夠對pipefd[1]調用write, 寫入想告知子進程的內容/ break }
if(pipefd[1] != STDOUT_FILENO){dup2(pipefd[1],STDOUT_FILENO);close(pipefd[1]);}
一樣的道理,對於第二個子進程,如法炮製:3d
if(pipefd[0] != STDIN_FILENO){dup2(pipefd[0],STDIN_FILENO);close(pipefd[0]);}
簡單來講,就是第一個子進程的標準輸出被綁定到了管道的寫入端,因而第一個命令的輸出,寫入了管道,而第二個子進程管道將其標準輸入綁定到管道的讀取端,只要管道里面有了內容,這些內容就成了標準輸入。指針
兩個示例代碼,爲何要判斷管道的文件描述符是否等於標準輸入和標準輸出呢?緣由是,在調用pipe時,進程極可能已經關閉了標準輸入和標準輸出,調用pipe函數時,內核會分配最小的文件描述符,因此pipe的文件描述符可能等於0或1。在這種狀況下,若是沒有if判斷加以保護,代碼就變成了:code
dup2(1,1);close(1);
這樣的話,第一行代碼什麼也沒作,第二行代碼就把管道的寫入端給關閉了,因而便沒法傳遞信息了orm
道的一個重要做用是和外部命令進行通訊。在平常編程中,常常會須要調用一個外部命令,而且要獲取命令的輸出。而有些時候,須要給外部命令提供一些內容,讓外部命令處理這些輸入。Linux提供了popen接口來幫助程序員作這些事情。
就像system函數,即便沒有system函數,咱們經過fork、exec及wait家族函數同樣也能夠實現system的功能。但終歸是不方便,system函數爲咱們提供了一些便利。一樣的道理,只用pipe函數及dup2等函數,也能完成popen要完成的工做,但popen接口給咱們提供了便利。
popen接口定義以下:
#include <stdio.h>FILE *popen(const char *command, const char *type);int pclose(FILE *stream);
popen函數會建立一個管道,而且建立一個子進程來執行shell,shell會建立一個子進程來執行command。根據type值的不一樣,分紅如下兩種狀況。
若是type是r:command執行的標準輸出,就會寫入管道,從而被調用popen的進程讀到。經過對popen返回的FILE類型指針執行read或fgets等操做,就能夠讀取到command的標準輸出,如圖9-10所示。
若是type是w:調用popen的進程,能夠經過對FILE類型的指針fp執行write、fputs等操做,負責往管道里面寫入,寫入的內容通過管道傳給執行command的進程,做爲命令的輸入,如圖9-11所示
popen函數成功時,會返回stdio庫封裝的FILE類型的指針,失敗時會返回NULL,而且設置errno。常見的失敗有fork失敗,pipe失敗,或者分配內存失敗。
I/O結束了之後,能夠調用pclose函數來關閉管道,而且等待子進程的退出。儘管popen函數返回的是FILE類型的指針,也不該調用fclose函數來關閉popen函數打開的文件流指針,由於fclose不會等待子進程的退出。pclose函數成功時會返回子進程中shell的終止狀態。popen函數和system函數相似,若是command對應的命令沒法執行,就如同執行了exit(127)同樣。若是發生其餘錯誤,pclose函數則返回-1。能夠從errno中獲取到失敗的緣由。
下面給出一個簡單的例子,來示範下popen的用法:
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<errno.h>#include<sys/wait.h>#include<signal.h>#define MAX_LINE_SIZE 8192void print_wait_exit(int status){ printf("status = %d\n",status); if(WIFEXITED(status)) { printf("normal termination,exit status = %d\n",WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { printf("abnormal termination,signal number =%d%s\n", WTERMSIG(status),#ifdef WCOREDUMP WCOREDUMP(status)?"core file generated" : "");#else "");#endif }}int main(int argc ,char* argv[]){ FILE *fp = NULL ; char command[MAX_LINE_SIZE],buffer[MAX_LINE_SIZE]; if(argc != 2 ) { fprintf(stderr,"Usage: %s filename \n",argv[0]); exit(1); } snprintf(command,sizeof(command),"cat %s",argv[1]); fp = popen(command,"r"); if(fp == NULL) { fprintf(stderr,"popen failed (%s)",strerror(errno)); exit(2); } while(fgets(buffer,MAX_LINE_SIZE,fp) != NULL) { fprintf(stdout,"%s",buffer); } int ret = pclose(fp); if(ret == 127 ) { fprintf(stderr,"bad command : %s\n",command); exit(3); } else if(ret == -1) { fprintf(stderr,"failed to get child status (%s)\n",strerror(errno)); exit(4); } else { print_wait_exit(ret); } exit(0);}