匿名管道和命名管道

進程間通信(IPC)

每個進程有各自不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到。所以進程之間要交換數據必須通過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間中拷貝到緩衝區,進程2再從緩衝區把數據讀走。內核提供的這種機制就是進程間通信。
通信需要媒介,兩個進程間通信的媒介就是內存。通信的原理就是讓兩個或多個進程能夠看到同一塊共同的資源,這塊資源一般都是由內存提供。

匿名管道(pipe)

管道是IPC最基本的一種實現機制。我們都知道在Linux下“一切皆文件”,其實這裏的管道就是一個文件。管道實現進程通信就是讓兩個進程都能訪問該文件。
管道的特徵:
①只提供單向通信,也就是說,兩個進程都能訪問這個文件,假設進程1往文件內寫東西,那麼進程2 就只能讀取文件的內容。
②只能用於具有血緣關係的進程間通信,通常用於父子進程建通信
③管道是基於字節流來通信的
④依賴於文件系統,它的生命週期隨進程的結束結束(隨進程)
⑤其本身自帶同步互斥效果

要實現管道,首先我們介紹兩個函數:
1.創建管道:

int pipe(int pipefd[2])

註釋:調用pipe函數時,首先在內核中開闢一塊緩衝區用於通信,它有一個讀端和一個寫端,然後通過pipefd參數傳出給用戶進程兩個文件描述符,pipefd[0]指向管道的讀端,pipefd[1]指向管道的寫段。在用戶層面看來,打開管道就是打開了一個文件,通過read()或者write()向文件內讀寫數據,讀寫數據的實質也就是往內核緩衝區讀寫數據。
返回值:成功返回0,失敗返回-1。

既然管道只能用於具有血緣關係的進程間通信,因此在這裏我們可以調用fork函數,創建一個子進程,然後讓父子進程通過管道進行通信。

2.創建子進程:

 pid_t fork(void);

註釋:包含在頭文件“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);

註釋:這兩個函數都能創建一個FIFO文件,該文件是真實存在於文件系統中的。函數 mknod 中參數 path 爲創建命名管道的全路徑; mod 爲創建命名管道的模式,指的是其存取權限; dev爲設備值,改值取決於文件創建的種類,它只在創建設備文件是纔會用到。
返回值:這兩個函數都是成功返回 0 ,失敗返回 -1

  • 命名管道與匿名管道使用的區別:
    命名管道創建完成後就可以使用,其使用方法與管道一樣,區別在於:命名管道使用之前需要使用open()打開。這是因爲:命名管道是設備文件,它是存儲在硬盤上的,而管道是存在內存中的特殊文件。但是需要注意的是,命名管道調用open()打開有可能會阻塞,但是如果以讀寫方式(O_RDWR)打開則一定不會阻塞;以只讀(O_RDONLY)方式打開時,調用open()的函數會被阻塞直到有數據可讀;如果以只寫方式(O_WRONLY)打開時同樣也會被阻塞,知道有以讀方式打開該管道。

代碼:https://github.com/lybb/Linux/tree/master/FIFO

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章