在瞭解管道之前,我們先來了解下進程之間爲什麼需要通信。
1、進程間通信的目的
- 數據傳輸: 一個進程需要將它的數據發送給另一個進程
- 資源共享:多個進程之間共享同樣的資源
- 通知事件: 一個進程需要向另一個或一組進程發送消息,通知它發什麼了某種事件
- 進程控制:有些進程希望完全控制另一個進程的執行,此時控制進程希望能夠攔截另一個所有陷入和異常,並能夠及時知道它的狀態。
那麼爲什麼進程不直接通信呢?
進程具有獨立性,它們時互相獨立的,每個進程都有自己的PCB,在fork()時,父子進程都採用的是寫時拷貝,因此,在每個進程看來,自己享有所有的內存資源,所以兩個進程不能直接通信。我們只有讓兩個或多個進程訪問到同一個資源,才能達到通信的目的。
2、管道
什麼是管道
- 管道是unix中最古老的進程間通信的形式。
- 我們把一個進程連接到另一個進程的一個數據流稱爲一個“管道”。
- 管道的本質是內核的一塊緩存。
管道的內核緩存大小是65536字節,往管道中寫入數據時,一次寫入大小不要超過PIPE_BUF(4096)
管道分爲兩種:匿名管道和命名管道。
1.匿名管道
#include <unistd.h>
功能:創建一個無名管道
原型:
int pipe(int fd[2]);
參數:
fd:文件描述符 fd[0]爲表示讀端,fd[1]表示寫端
返回值:成功返回0,失敗返回錯誤代碼
下面我們來看一個例子:
//父進程向管道中寫數據,子進程讀數據
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main( void )
{
int fds[2];
pipe(fds); // 創建管道
if ( fork() == 0 ) { // 子進程讀取管道
close(fds[1]); // 管道的讀端是fds[0], 所以關閉fds[1]
char buf[100] = {};
read(fds[0], buf, 100); // 讀取管道中的內容
printf("child,buf=[%s]\n", buf);
close(fds[0]); // 關閉管道的讀端
exit(0);
} else { // 父進程向管道中寫入數據
close(fds[0]); // 父進程要往管道中寫數據,所以關閉fds[0]
char *msg = "this is maomaochong";
sleep(2);
write(fds[1], msg, strlen(msg)); // 兩秒之後向管道中寫入數據
close(fds[1]);
}
}
fork之後
fork之後關掉不用的描述符
從文件描述符的角度來看:
1.父進程創建管道
2.父進程fork出子進程
3.父進程關閉fd[0],子進程關閉fd[1]
匿名管道的特點
- 只能用於具有共同祖先的進程(具有親緣關係的,兄弟進程也可以)之間通信。
- 一般而言,進程退出,管道釋放。所以管道生命週期隨進程。
- 一般而言,內核會對管道進行同步與互斥。
- 管道提供面向字節流服務,數據只能向一個方向傳輸,雙方傳輸時,需要建起兩個管道。
管道的讀寫規則
- 寫方一隻寫,讀方不讀:write調用阻塞,直到有進程讀走數據。
- 寫方不寫,讀方一直讀:read調用阻塞,直到有數據來爲止。
- 寫方將寫端關閉,讀方一直讀:讀方將讀完然後返回0;
- 寫方一直寫,讀方將讀端關閉:會產生SIGPIPE信號,終止write進程。
2.命名管道
- 匿名管道的一個限制就是必須在具有共同祖先的進程間通信。
- 如果想在毫無相關的進程間交換數據,就要使用命名管道。
- 命名管道是一種特殊類型的文件
創建一個命名管道有兩種方式:
在命令行創建:
$ mkfifo filename
命名管道可以從程序裏創建,相關函數是;
int mkfifo(const char8 filename,mode_t mode);
成功返回0,失敗返回-1;
下面我們來看一個例子:
writepipe.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main( void )
{
int fd = open("my.p", O_WRONLY);
char buf[10] = {};
for (int i=0; ; i++ ) {
sprintf(buf, "%c", 'A'+i%26);
write(fd, buf, 1);
printf("write %d ok\n", i);
sleep(1);
}
}
readpipe.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main( void )
{
int fd = open("my.p", O_RDONLY);
char buf[1024];
while ( 1 ) {
memset(buf, 0x00, sizeof(buf));
read(fd, buf, 1024);
printf("buf=[%s]\n", buf);
}
}
匿名管道與命名管道的區別
- 匿名管道有函數pipe創建並打開
- 命名函數有mkfifo創建,打開用open
- 命名管道與匿名管道之間唯一的區別在他們創建於打開的方式不同,一旦這些工作做好之後,他們具有相同的的語義。
- 最大的區別是:命名管道時文件在硬盤上有對應的文件,而匿名管道是在內核中實現的。
打開規則
- 爲讀打開時:
無寫方:阻塞直到有相應進程來打開該FIFO
有寫方:返回成功; - 爲寫打開:
無讀方:阻塞直到有相應進程爲讀而打開該FIFO
有讀方:返回失敗;