前面我們講了進程間通信的一種方式,共享內存。下面看一看另一種機制,匿名管道。
1.什么是管道
管道是一個進程的數據流到另一個進程的通道,即一個進程的數據輸出作為另一個進程的數據輸入,管道起到了橋梁的作用。
比如,在shell中輸入命令:ls -l|grep string,ls和grep是兩個進程,"|"符號表示管道,意思是執行ls -l進程,並將輸出結果result_1,作為grep string進程的輸入result_0,grep進程將result_0中存在字符串string的信息打印到屏幕。
2.管道的使用
1)popen函數:啟用一個新進程,並可以向它傳遞數據,或者通過它接受數據。
FILE *popen(const char *command,conse char *open_mode);
command:運行的程序名和參數
open_mode:有兩個值"r(只讀)","w(只寫)"
"r":可以獲取新進程的輸出
"w":可以向新進程發送數據
返回值:返回輸入輸出文件流指針
2)pclose函數:關閉輸入輸出文件流指針
若調用該函數時,新進程仍然在運行,則pclose將等待,直至新進程結束。
返回值:返回新進程的退出碼。
3.popen函數使用示例
下例循環讀取read_fp輸出文件流的內容,寫入write_fp的輸入文件流,直到輸出流內容讀完。
#include<stdlib.h> #include<stdio.h> #include<string.h> int main() { FILE *read_fp = NULL; FILE *write_fp = NULL; char buffer[BUFSIZ+1]; int chars_read = 0; //初始化緩沖區 memset(buffer,'\0',sizeof(buffer)); read_fp = popen("ls -l","r"); write_fp = popen("grep rwxrwxr-x","w"); if(read_fp && write_fp) { chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp); while(chars_read) { buffer[chars_read]='\0'; //把數據寫入grep進程 fwrite(buffer,sizeof(char),chars_read,write_fp); chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp); } //關閉文件流 pclose(read_fp); pclose(write_fp); exit(EXIT_SUCCESS); } printf("%d\n",2); exit(EXIT_FAILURE); }
輸出結果:
3、popen的原理及優缺點
當調用popen運行一個新進程時,它首先啟動shell,然后將command參數傳遞給它。
優點:可以使用shell來分析命令字符串,啟動非常復雜的shell命令。
缺點:不僅要啟動一個新進程,還要啟動一個shell,效率會比較低。
4.pipe函數的使用
int pipe(int file_description[2]);
file_description[2]:表示管道的輸出輸入端,輸出端數據經過管道流到輸入端,函數執行完后, 會將這個數組賦值。
file_description[1]表示管道輸出端文件描述符
file_description[0]表示管道輸入端文件描述符
返回值:0成功,-1失敗
與popen不同的是,pipe函數是一個底層調用,不會啟動shell。
popen是使用文件流(FILE)工作的,pipe使用的是文件描述符,相應的數據要用底層的read和write來讀取和發送。
5.pipe函數使用示例
下例中,我們在父進程中創建一個管道,然后調用fork創建一個子進程。
此時,父進程的file_description[1]輸出端,對應着子進程file_description[0]的輸入端。
數據通過管道由父進程傳到子進程。示例如下:
#include<stdlib.h> #include<stdio.h> #include<string.h> int main() { int data_processed = 0; const char data[]="Hello pipe!"; char buffer[BUFSIZ+1]; pid_t pid; memset(buffer,'\0',sizeof(buffer)); int filedes[2]; if(pipe(filedes)==0) { //創建管道成功 //fork子進程 pid=fork(); if(pid==-1) { fprintf(stderr,"Fork failure"); exit(EXIT_FAILURE); } if(pid==0) { data_processed = read(filedes[0],buffer,BUFSIZ); printf("read %d bytes:%s\n",data_processed,buffer); exit(EXIT_SUCCESS); } else { data_processed = write(filedes[1],data,strlen(data)); printf("wrote %d bytes:%s\n",data_processed,data); exit(EXIT_SUCCESS); } } exit(EXIT_FAILURE); }
輸出結果:
6.管道用作標准輸入和輸出
我們知道標准的輸入描述符為0,輸出描述符為1,
為了使用已經定義好的標准程序,如od命令,從標准輸入讀入數據。
需要將管道的輸入端描述符置為0,此時,我們需要用到一個輔助函數dup
dup函數:創建一個描述符,復制原有描述符參數的結構到新建的描述符。
int dup(int file_descriptor);
新的描述符規則是,使用最小的可用值。
要想使管道的輸入描述符為標准輸入描述符,我們可以先關閉文件描述符0,然后調用dup,
此時新建的描述符即為最小可用值0,標准輸入描述符。
close(0); dup(file_description[0]);
上例使用標准輸入描述符改造后的示例如下:
#include<stdlib.h> #include<stdio.h> #include<string.h> int main() { int data_processed = 0; const char data[]="Hello pipe!"; int filedes[2]; pid_t pid; if(pipe(filedes)==0) { pid = fork(); if(pid==-1) { fprintf("stderr","fork failure!\n"); exit(EXIT_FAILURE); } if(pid==0) { close(0); dup(filedes[0]); close(filedes[0]); close(filedes[1]); execlp("od","od","-c",0); exit(EXIT_FAILURE); } else { close(filedes[0]); data_processed = write(filedes[1],data,strlen(data)); close(filedes[1]); printf("wrote %d bytes:%s\n",data_processed,data); } } }
輸出結果:
7.匿名管道需要注意的問題
1)當管道沒有關閉時,若沒有數據可讀,read調用會阻塞
2)當管道關閉時,read調用會返回0
3)匿名管道通信,進程間必須是父子關系。