目錄
一、進程間通信的介紹
??進程間通信的目的
??進程間通信的本質?
進程間通信的分類
??管道
??System V IPC
??POSIX IPC
二、管道?
🧠什么是管道
??匿名管道
📝匿名管道的原理
📝pipe函數
📝匿名管道的使用步驟?
?編輯📝管道讀寫的規則?
📝管道的特點?
一、進程間通信的介紹
??進程間通信的目的
- 數據傳輸:一個進程需要將它的數據發送給另一個進程
- 資源共享:多個進程之間共享同樣的資源。
- 通知事件:一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。
- 進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態改變。
??進程間通信的本質?
進程間通信的本質就是讓不同的進程看到同一份資源
這里要解釋一下了,由于進程具有獨立性,所以想要直接實現不同進程間的相互通信還是非常困難的。
這里我們就引入了第三方資源來作為不同進程之間通信的橋梁,也就是說這些進程可以通過向這么個第三方資源進行寫入和讀取來進行通信,示意圖如下:
通過上面的方式,我們就實現的不同進程看到了同一份資源,所以說進程間通信的本質就是讓不同的進程看到了同一份資源。
進程間通信的分類
??管道
- 匿名管道
- 命名管道
??System V IPC
- System V 消息隊列
- System V 共享內存
- System V 信號量
??POSIX IPC
- 消息隊列
- 共享內存
- 信號量
- 互斥量
- 條件變量
- 讀寫鎖
二、管道?
🧠什么是管道
管道是Unix中最古老的進程間通信的形式,我們把從一個進程連接到另一個進程的一個數據流稱為一個“管道”。
我們之前在談Linux的命令的時候也提及了管道這一概念:
例如,這個查看當前服務器有多少用戶登錄的命令
我們發現這個命令是由who命令和wc命令組成的,這個命令運行起來之后就變成了兩個進程了,who進程把執行得到的結果通過標準輸出寫入到了管道之中,wc進程通過標準輸入從管道中讀取數據,處理完之后再把結果通過標準輸出給到用戶。
注意:who顯示的是相關信息,wc -l命令是用來統計行數的。
??匿名管道
📝匿名管道的原理
匿名管道用于進程間的通信,且僅限于父子進程之間的通信。
我們之前也說了,進程間通信的本質就是讓不同的進程看到了同一份資源,使用匿名管道實現通信的原理就是讓父子進程看了同一份被打開的文件資源,然后父子進程就可以對同一份資源進行讀寫操作,從而實現了進程間的通信。
敲黑板:
這里需要注意的是這里打開的文件是由操作系統來進行管理的,父子進程進行讀寫時并不會進行寫時拷貝。
📝pipe函數
我們可以使用pipe函數來創建匿名管道
int pipe(int pipefd[2]);
參數說明:輸入的參數實際上是輸出型的參數,數組中的兩個元素分別指向讀端和寫端。‘
- pipefd[0]:管道讀端的文件描述符
- pipefd[1]:管道寫端的文件描述符?
如果創建失敗則返回-1,成功創建則返回0。
📝匿名管道的使用步驟?
我們在使用管道通信的時候,既要使用fork函數來創建子進程,也要使用pipe函數來創建管道。
第一步:父進程創建管道
第二步:父進程創建子進程
第三步:父端關閉寫,子端關閉讀
敲黑板:
這里的通信只能是單向的,也就是我們說的半雙工,在代碼中的反應就是管道只開一個讀端和寫端。
我們這里可以將上面的三個步驟再細化一下:
第一步:父進程創建管道
第二步:父進程創建子進程
第三步:父進程關閉寫端,子進程關閉讀端?
我們來寫個代碼演示一下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {int fd[2] = {0}; // 作為pipe函數的參數來進行傳遞if(pipe(fd) < 0) {perror("pipe");exit(1);}pid_t id = fork();if(id == 0) // 子進程{close(fd[0]); // 關閉了讀端const char* message = "Hello my father, I am child...";int count = 10;while(count--) {write(fd[1], message, strlen(message)); // 字節流的寫入數據sleep(1);}close(fd[1]);exit(0);}// 父進程close(fd[1]); // 關閉了寫端char buffer[64];while(1) {ssize_t s = read(fd[0], buffer, sizeof(buffer)); // 由于休眠時間的存在,就會讀完整if(s > 0) {buffer[s] = '\0';printf("child send to father: %s\n", buffer);}else if (s == 0) {printf("the end\n");break;}else {printf("read error\n");break;}}close(fd[0]);waitpid(id, NULL, 0);return 0;
}
📝管道讀寫的規則?
pipe2函數的操作和pipe函數類似,具體的函數原型如下:
int pipe2(int pipefd[2], int flags);
pipe函數的第二個參數用來設置選項可以設置為O_NONBLOCK ,也就是非阻塞:
- 默認不傳參數時,read在沒有數據會一直等待數據的到來,write在數據數據寫滿的時候會等待數據被讀走。
- 在加了O_NONBLOCK 參數的時候,read在沒有數據的時候會返回-1,并把erron的值設置為EAGIN,write在數據寫滿的時候也會返回-1,并把erron的值設置為EAGIN。
- 如果所有管道寫端對應的文件描述符被關閉,則read返回0
- 如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE,進而可能導致write進程退出
- 當要寫入的數據量不大于PIPE_BUF時,Linux將保證寫入的原子性。
- 當要寫入的數據量大于PIPE_BUF時,Linux將不再保證寫入的原子性。?
📝管道的特點?
第一點:管道內自帶了同步和互斥機制
首先我們要知道什么是同步和互斥:
同步:兩個或者是兩個以上的進程在運行時要按照之前約定好的順序依次執行,可以類比到做菜的場景,我們要先切菜洗菜才能去炒,要有一個先后的順序才行。
互斥:防止多個進程同時操作了同一個資源,可以類比到打印的場景,A和B都要使用打印機打印,但是我們不能同時發送內容到打印機,否則就是打印出來的結果出錯。
我們仔細思考會發現兩者其實是類似的,同步是一種更加復雜的互斥,而互斥則是一種特殊的同步,對于我們的管道而言,互斥就是兩個進程不可以同時對管道進行操作,他們會相互排斥,而同步也說的是不能對管道進行操作,但這里的要求是必須要是按照一定的次序來對管道進行操作的。
第二點:管道的生命周期隨進程
管道的本質就是通過文件來進行通信的,也就是說管道是依賴于文件系統的,那么當所有的文件都退出了,文件也就被釋放掉了,所一說管道的生命周期是隨進程的。
第三點:管道提供的是流式服務
對于進程A寫入管道的數據,進程B每次都可以從管道中任意讀取數據,這種方式被稱之為流式服務,與之對應的就是數據報服務,也就是后面我們會講的字節流和數據包。
- 流式服務:數據沒有明確的分割,也就是會粘連在一起。
- 數據報服務:數據有了明確的分割了,數據是一份一份的。
第四點:管道是半雙工通信
在數據通信中,數據在線路上的傳送方式有以下這幾種:
1、單工通信:數據的傳輸是單向的。通信雙方中,一方是發送端,一方是接受數據端。
2、半雙工通信:半雙工是指既可以從從兩個反向上面通信,但是不可以同時通信。
3、全雙工通信:全雙工就是兩個反向上可以同時通信,也就是加強版的半雙工,雙倍的單雙工。