原文連結:https://blog.csdn.net/qq_33951180/article/details/68959819
程序間通信(IPC)
每個程序有各自不同的使用者位址空間,任何一個程序的全局變量在另一個程序中都看不到。是以程序之間要交換資料必須通過核心,在核心中開辟一塊緩沖區,程序1把資料從使用者空間中拷貝到緩沖區,程序2再從緩沖區把資料讀走。核心提供的這種機制就是程序間通信。
通信需要媒介,兩個程序間通信的媒介就是記憶體。通信的原理就是讓兩個或多個程序能夠看到同一塊共同的資源,這塊資源一般都是由記憶體提供。
匿名管道(pipe)
管道是IPC最基本的一種實作機制。我們都知道在Linux下“一切皆檔案”,其實這裡的管道就是一個檔案。管道實作程序通信就是讓兩個程序都能通路該檔案。
管道的特征:
①隻提供單向通信,也就是說,兩個程序都能通路這個檔案,假設程序1往檔案内寫東西,那麼程序2 就隻能讀取檔案的内容。
②隻能用于具有血緣關系的程序間通信,通常用于父子程序建通信
③管道是基于位元組流來通信的
④依賴于檔案系統,它的生命周期随程序的結束結束(随程序)
⑤其本身自帶同步互斥效果
要實作管道,首先我們介紹兩個函數:
1.建立管道:
int pipe(int pipefd[2])
1
注釋:調用pipe函數時,首先在核心中開辟一塊緩沖區用于通信,它有一個讀端和一個寫端,然後通過pipefd參數傳出給使用者程序兩個檔案描述符,pipefd[0]指向管道的讀端,pipefd[1]指向管道的寫段。在使用者層面看來,打開管道就是打開了一個檔案,通過read()或者write()向檔案内讀寫資料,讀寫資料的實質也就是往核心緩沖區讀寫資料。
傳回值:成功傳回0,失敗傳回-1。
既然管道隻能用于具有血緣關系的程序間通信,是以在這裡我們可以調用fork函數,建立一個子程序,然後讓父子程序通過管道進行通信。
2.建立子程序:
pid_t fork(void);
1
注釋:包含在頭檔案“unistd.h”中,無參數,傳回值類型為pid_t
傳回值:(下面這個是關于該函數傳回值的介紹)調用成功将子程序的pid傳回給父程序,失敗傳回-1給父程序。注意:調用成功會有兩個傳回值,對于父程序,傳回的是子程序的pid;對于子程序,傳回的是0
總結一下實作管道通信的步驟:
①調用pipe函數,由父程序建立管道,得到兩個檔案描述符指向管道的兩端
②父程序調用fork建立子程序,則對于子程序,也有兩個檔案描述符指向管道的兩端
③父程序關閉讀端,隻進行寫操作;子程序關閉寫端,隻進行讀操作。管道是用喚醒隊列實作的,資料從寫段流入到讀端,這樣就形成了程序間通信。
代碼:https://github.com/lybb/Linux/tree/master/mypipe
使用管道需要注意的4種特殊情況:
如果所有指向管道寫端的檔案描述符都關閉了,而仍然有程序從管道的讀端讀資料,那麼檔案内的所有内容被讀完後再次read就會傳回0,就像讀到檔案結尾。
如果有指向管道寫端的檔案描述符沒有關閉(管道寫段的引用計數大于0),而持有管道寫端的程序沒有向管道内寫入資料,假如這時有程序從管道讀端讀資料,那麼讀完管道内剩餘的資料後就會阻塞等待,直到有資料可讀才讀取資料并傳回。
如果所有指向管道讀端的檔案描述符都關閉,此時有程序通過寫端檔案描述符向管道内寫資料時,則該程序就會收到SIGPIPE信号,并異常終止。
如果有指向管道讀端的檔案描述符沒有關閉(管道讀端的引用計數大于0),而持有管道讀端的程序沒有從管道内讀資料,假如此時有程序通過管道寫段寫資料,那麼管道被寫滿後就會被阻塞,直到管道内有空位置後才寫入資料并傳回。
命名管道(FIFO)
上述管道雖然實作了程序間通信,但是它具有一定的局限性:首先,這個管道隻能是具有血緣關系的程序之間通信;第二,它隻能實作一個程序寫另一個程序讀,而如果需要兩者同時進行時,就得重新打開一個管道。
為了使任意兩個程序之間能夠通信,就提出了命名管道(named pipe 或 FIFO)。
1、與管道的差別:提供了一個路徑名與之關聯,以FIFO檔案的形式存儲于檔案系統中,能夠實作任何兩個程序之間通信。而匿名管道對于檔案系統是不可見的,它僅限于在父子程序之間的通信。
2、FIFO是一個裝置檔案,在檔案系統中以檔案名的形式存在,是以即使程序與建立FIFO的程序不存在血緣關系也依然可以通信,前提是可以通路該路徑。
3、FIFO(first input first output)總是遵循先進先出的原則,即第一個進來的資料會第一個被讀走。
那麼知道什麼是命名管道後我們如何通過一個命名管道實作兩個程序之間通信呢????同上一樣,我們先給出函數:
建立命名管道(兩種方法):
(1)Shell下用指令mknod 或 mkfifo建立命名管道:mknod namedpipe
(2)系統函數建立:
#include <sys/stat.h>
int mknod(const char* path, mode_t mod, dev_t dev);
int mkfifo(const char* path, mode_t mod);
1
2
3
注釋:這兩個函數都能建立一個FIFO檔案,該檔案是真實存在于檔案系統中的。函數 mknod 中參數 path 為建立命名管道的全路徑; mod 為建立命名管道的模式,指的是其存取權限; dev為裝置值,改值取決于檔案建立的種類,它隻在建立裝置檔案是才會用到。
傳回值:這兩個函數都是成功傳回 0 ,失敗傳回 -1
命名管道與匿名管道使用的差別:
命名管道建立完成後就可以使用,其使用方法與管道一樣,差別在于:命名管道使用之前需要使用open()打開。這是因為:命名管道是裝置檔案,它是存儲在硬碟上的,而管道是存在記憶體中的特殊檔案。但是需要注意的是,命名管道調用open()打開有可能會阻塞,但是如果以讀寫方式(O_RDWR)打開則一定不會阻塞;以隻讀(O_RDONLY)方式打開時,調用open()的函數會被阻塞直到有資料可讀;如果以隻寫方式(O_WRONLY)打開時同樣也會被阻塞,知道有以讀方式打開該管道。