【Linux學習筆記】進程間通信之管道

在瞭解管道之前,我們先來了解下進程之間爲什麼需要通信。

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
    有讀方:返回失敗;
發佈了82 篇原創文章 · 獲贊 30 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章