進程間通信至少可以通過傳送打開文件來實現,不同的進程通過一個或多個文件來傳遞信息,事實上,在很多應用系統裡,都使用了這種方法。但一般說來, 進程間通信(IPC:InterProcess Communication)不包括這種似乎比較低級的通信方法。Unix系統中實現進程間通信的方法很多,而且不幸的是,極少方法能在所有的Unix系 統中進行移植(唯一一種是半雙工的管道,這也是最原始的一種通信方式)。而Linux作為一種新興的操作系統,幾乎支持所有的Unix下常用的進程間通信 方法:管道、消息隊列、共享內存、信號量、套接口等等。
管道概念
管道是Unix中最古老的進程間通信的形式,我們把從一個進程連接到另一個進程的一個數據流稱為一個“管道”, 管道的本質是固定大小的內核緩沖區;它包括無名管道和有名管道兩種,前者用於父進程和子進程間的通信,後者用於運行於同一台機器上的任意兩個進程間的通信。
管道限制
1)管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;
2)匿名管道只能用於具有共同祖先的進程(如父進程與fork出的子進程)之間進行通信, 原因是pipe創建的是兩個文件描述符, 不同進程直接無法直接獲得;(通常,一個管道由一個進程創建,然後該進程調用fork,此後父子進程共享該管道)
匿名管道創建
#includeint pipe(int pipefd[2]);
參數
Pipefd:文件描述符數組,其中pipefd[0]表示讀端,pipefd[1]表示寫端,示意圖如下:
(1)接下來,我們利用匿名管道來進行父子進程之間的通信,子進程向父進程發送信息。
int main() { int pipefd[2]; if(pipe(pipefd)==-1) ERR_EXIT("pipe error!"); pid_t pid; pid=fork(); if(pid==-1) ERR_EXIT("fork error"); if(pid==0) { close(pipefd[0]); write(pipefd[1],"hello",5); close(pipefd[1]); exit(EXIT_SUCCESS); } close(pipefd[1]); char buf[10]={0}; read(pipefd[0],buf,10); printf("buf=%s\n",buf); return 0; }結果:父進程接收到子進程發送的hello
(2)我們來模擬實現管道命令 ls | wc -w 關鍵點就是:
1.子進程運行ls,dup2(pipefd[1],STDOUT_FILENO)重定向標准輸出,定位到管道寫端,ls寫入到管道寫端而不是標准輸出設備;
2.父進程運行wc -w ,wc獲取數據的時候從管道讀端獲取,不再從標准輸入設備。
3.通過管道, 將子進程的輸出發送到wc的輸入 。
int main() { int pipefd[2]; if(pipe(pipefd)==-1) ERR_EXIT("pipe error!"); pid_t pid; pid=fork(); if(pid==-1) ERR_EXIT("fork error"); if(pid==0) { dup2(pipefd[1],STDOUT_FILENO);//重定向輸出 close(pipefd[0]); close(pipefd[1]); execlp("ls","ls",NULL);//若出錯才執行下面的代碼 fprintf(stderr,"error execute ls\n"); exit(EXIT_FAILURE); } dup2(pipefd[0],STDIN_FILENO); close(pipefd[0]); close(pipefd[1]); execlp("wc","wc","-w",NULL); fprintf(stderr,"error execute wc\n"); exit(EXIT_FAILURE); return 0; }
不帶任何參數的cat命令是從標准輸入讀入命令,寫到標准輸出。0->Makefile ; 1->Makefile2;
int main() { close(0); open("Makefile",O_RDONLY); close(1); open("Makefile2",O_WRONLY | O_CREAT | O_TRUNC,0644); execlp("cat","cat",NULL); return 0; }管道的讀寫規則
我們對以上的規則一一進行驗證。
(1)如果管道為空,那麼read會阻塞(模式),如果使用非阻塞模式的話,也就是使用fcntl函數,對模式進行修改後
int flags=fcntl(pipefd[0],F_SETFL,flags | O_NONBLOCK); read(pipefd[0],buf,10);
此時,讀操作會失敗,顯示資源暫且不可用的錯誤。
(2)管道的寫端關閉,read打印輸出0,但是並不報錯誤,顯示讀到了文件的末尾。
int main() { int pipefd[2]; if (pipe(pipefd) != 0) err_exit("pipe error"); pid_t pid = fork(); if (pid == -1) err_exit("fork error"); else if (pid == 0) { close(pipefd[1]); exit(EXIT_SUCCESS); } close(pipefd[1]); sleep(2); char buf[2]; if (read(pipefd[0], buf, sizeof(buf)) == 0) cout << "sure" << endl; }(3)如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE,進程終止。如果我們自定義SIGPIPE的處理函數的話會生效。
int main() { if (signal(SIGPIPE, handler) == SIG_ERR) err_exit("signal error"); int pipefd[2]; if (pipe(pipefd) != 0) err_exit("pipe error"); pid_t pid = fork(); if (pid == -1) err_exit("fork error"); else if (pid == 0) { close(pipefd[0]); exit(EXIT_SUCCESS); } close(pipefd[0]); sleep(2); char test; if (write(pipefd[1], &test, sizeof(test)) < 0) err_exit("write error"); }會打印出 singal error錯誤。
(4)關於PIPE_BUF和原子性操作之間的關系,
已知管道的PIPE_BUF為4K, 我們啟動兩個進程A, B向管道中各自寫入68K的內容, 然後我們以4K為一組, 為了方便我們查看管道最後一個字節的內容, 多運行該程序幾次, 就會發現這68K的數據會有交叉寫入的情況 。
int main() { const int TEST_BUF = 68 * 1024; //設置寫入的數據量為68K char bufA[TEST_BUF]; char bufB[TEST_BUF]; memset(bufA, 'A', sizeof(bufA)); memset(bufB, 'B', sizeof(bufB)); int pipefd[2]; if (pipe(pipefd) != 0) err_exit("pipe error"); pid_t pid; if ((pid = fork()) == -1) err_exit("first fork error"); else if (pid == 0) //第一個子進程A, 向管道寫入bufA { close(pipefd[0]); int writeBytes = write(pipefd[1], bufA, sizeof(bufA)); cout << "A Process " << getpid() << ", write " << writeBytes << " bytes to pipe" << endl; exit(EXIT_SUCCESS); } if ((pid = fork()) == -1) err_exit("second fork error"); else if (pid == 0) //第二個子進程B, 向管道寫入bufB { close(pipefd[0]); int writeBytes = write(pipefd[1], bufB, sizeof(bufB)); cout << "B Process " << getpid() << ", write " << writeBytes << " bytes to pipe" << endl; exit(EXIT_SUCCESS); } // 父進程 close(pipefd[1]); sleep(2); //等待兩個子進程寫完 char buf[4 * 1024]; //申請一個4K的buf int fd = open("save.txt", O_WRONLY|O_TRUNC|O_CREAT, 0666); if (fd == -1) err_exit("file open error"); while (true) { int readBytes = read(pipefd[0], buf, sizeof(buf)); if (readBytes == 0) break; if (write(fd, buf, readBytes) == -1) err_exit("write file error"); cout << "Parent Process " << getpid() << " read " << readBytes << " bytes from pipe, buf[4095] = " << buf[4095] << endl; } }