Linux 進程間通信之 管道

爲什麼進程間要通信

  1. 數據傳輸 進程需要把數據傳遞給其他進程
  2. 資源共享 多個進程之間有時需要共享一份資源
  3. 通知事件 比如子進程要把自己的退出信息交給父進程
  4. 進程控制 比如Debug進程需要控制待調試進程的所有陷入和異常 ,
    並且知道他現在的狀態。

因爲每個進程本來是擁有自己的虛擬地址空間 用頁表映射到每個進程自己的物理內存上的 每個進程自己視爲獨佔系統資源 各個進程的地址空間 爲了安全期間並不關聯 相對封閉 所以需要一些機制來保證進程間能夠通信

學習進程間通信必須瞭解臨界資源的概念
1. 臨界資源:把多個進程(執行流)能夠看到訪問的共同資源叫臨界資源,管道是一種臨界資源。
2 . 臨界區:把訪問臨界資源的代碼叫臨界區。
進程間通訊的本質是二進程共享資源(不同進程看到公共資源),一進程以讀方式一進程以寫方式打開同一份文件
linux 下一切接文件,管道也是一種特殊的文件。
3. 互斥:在任意一個時刻只能有一個“人”訪問臨界資源,採用原子性原則訪問。
4. 原子性操作:狹義的原子性操作指該操作的彙編代碼只有一句(該操作不可分),廣義的原子操作指一個操作正在執行 時決不會被切出。原子操作要麼做了,要麼沒做,不存在正在做。

System V 的進程間通信機制有三種
1. 消息隊列
2. 信號量
3. 共享內存

這四個機制的共同點是 讓兩個或幾個要通信的進程 能夠按照一定的規則訪問同一內核內存空間 該資源也叫臨界資源。 臨界資源架起了兩個進程之間的橋樑 。

1. 管道

管道是最古老的unix進程間通信方式 大概的思想是 讓兩個進程打開並訪問到同一個文件 看到同一份內存 一個進程往文件裏面寫數據 另一個進程從文件裏面讀數據
這個文件於是就想鏈接兩個進程的管道一樣。

1.1匿名管道

匿名管道的創建

    #include <unistd.h>
       int pipe(int pipefd[2]);
       int pipe2(int pipefd[2], int flags);

系統調用pipe 的參數是一個文件描述符數組 有兩個數組元素
f[0] 表示文件的讀端 f[1]表示文件的寫端 返回值 成功返回0 失敗返回 -1 並置errno 錯誤代碼。

flag 表示 讀寫方式
pipe()系統調用默認讀寫方式阻塞

具體怎麼實現通信呢?
這裏寫圖片描述

這張圖做了說明 父進程調用pipe() 創建了一個匿名管道(在內核空間)
並且默認打開這個管道文件的讀端寫端 f[0] f[1]文件描述符
之後 fork() 創建子進程 子進程繼承了父進程的文件描述符表 故也默認打開f[0] f[1] 兩個文件描述符

之後父進程關閉讀端文件描述符 子進程關閉寫端文件描述符 父進程往文件裏寫 子進程從文件裏讀 。 這樣父子進程實現了通信

這是對於想要父寫 子讀的 也可以子寫父讀 父關f[1] 子關f[0]

代碼實現 :

#include<stdio.h>
#include<sys/types.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>

int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(-1 == ret){
        printf(" Create pipe error!, error code id %d",ret);
    }
    pid_t pid = fork();
    if(pid < 0){
        perror("fork");
    }else if(0 ==  pid ){
        //Child
        close(_pipe[0]);
        char* masg = NULL;
        int i = 0;
        while( i < 10){
            masg = "I am Child!";
            write(_pipe[1],masg,strlen(masg) + 1);
            sleep(1); 
        }
    }
     else if( 0 < pid){
        //Father
        close(_pipe[1]);
        char masg_buf[20];  
        int i = 0;
        while( i < 10){
            memset(masg_buf, '\0',sizeof(masg_buf));
            read(_pipe[0],masg_buf,sizeof(masg_buf));
            printf("Child said :%s\n",masg_buf);
            +`````
i;
        }
    }
    return 0;
}

這裏寫圖片描述

從內核角度看進程間通信

兩個進程的進程控制塊 task_struct——-> file_struct———–>file 結構體——–>dentry 結構體——–> inode結構體

同時指向同一個inode結構體 一個file結構體的f_op ——>file_operators 爲讀 一個爲寫
這裏寫圖片描述
這一段看不懂請看:http://blog.csdn.net/x__016meliorem/article/details/78825904

管道的讀寫規則:

和 pipe2() 函數的flag設置有關
沒有數據可讀時:
1. O_NONBLOCK diabale read()調用阻塞 進程暫停執行 直到有數據寫進去才繼續開始讀
2. O_NONBLOCK enable read調用返回-1, errno值設置爲 EAGAIN

當管道被寫滿的時候:
1. O_NONBLOCK disanble write()調用阻塞 進程暫停執行 直到有數據被讀走纔開始繼續寫。
2. O_NONBLOCK enable write()調用返回-1, errno至設置爲 EAGAIN。

如果所有管道的對應寫端文件描述符被關閉 ,read()返回0。
如果所有管道的對應讀端文件描述符被關閉 , write()操作會產生信號SIGPIPE 該信號可能導致write()進程退出。(因爲沒有人讀 寫是沒有意義的)

當寫入數據量不大的 PIPE_BUF時 linux保證寫入的原子操作。
當寫入數據量很大的 PIPE_BUF時 linux保不證寫入的原子操作。

管道的特點:
1. 生命週期隨進程 當進程退出時 管道的內存空間被釋放 。
2. 只用於有親源關係的進程之間通信 , 因爲有相同親源的進程從共同祖先那裏繼承了一份文件描述符表 表中有同一管道的讀寫 端文件描述符
這樣才能讓不同進程看到同一管道
3. 半雙工 數據只朝一個方向流動 要實現雙向通信 就必須建立兩個管道
4 管道通信是面向字節流的 一次寫的數據不一定要一次都出來 ,一次讀的數據不一定要一次寫進去。
5. 內核對管道的操作提供同步與互斥保證

什麼是同步與互斥
同步:多人多進程要完成最終目標就一定要按照一定次序協同完成 , 否則無法完成或效率底下。
互斥:任意時刻只能有一個“人” 訪問臨界資源 採用原子操作訪問。

管道的同步與互斥是指:
同步:寫滿了就不再寫 讀完了就不再讀
互斥:不可以兩個進程同時讀 或者寫管道

命名管道fifo

如果想在兩個並不相關的進程之間實現通信 就得使用FIFO命名管道
FIFO的意思是先進先出 mkfifo創建的文件 內核提供機制讓 先寫入的數據被先讀到。

命名管道是一種特殊類型的文件

創建命名管道的函數

       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

該函數的第一個參數是 要創建的命名管道文件的路徑名 第二個參數mode是擁有者,所屬組 , 和其他三種“人” 的權限。

命名管道的實質是創建一個文件, 兩個進程以open方式打開這個文件 一個寫一個讀

命名管道與之前的匿名管道的區別
只在於 創建方式不同 可以在非親元關係的進程

根本都是內核提供一塊內存讓 兩個進程通過文件描述符 讀寫公共資源
在內核角度都是讓兩個進程的file結構體指向同一個inode節點

命名管道實現 client —–server 通信
client.c

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/types.h>

int main()
{
    int ret  = mkfifo("./myfifo", 0666);
    if(ret < 0){
        perror("mkfifo");
        return 1;
    }
    int fd = open("./myfifo", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 2;
    }
    char buf[1024] = {0};
    while(1){
        ssize_t read_size = read(fd, buf, sizeof(buf) - 1);
        if(read_size > 0){
            buf[read_size - 1] = 0;
            printf("client say : %s\n", buf);
        }
        else if(read_size == 0){
            printf("client quit!, quit now\n");
            return 4;
        }else if(read_size < 0){
            perror("read");
            return 3; 
        }
    }
    close(fd);
    return 0;
}

server.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int ret = mkfifo("./myfifo", 0666);
    if(ret == 0){
        perror("mkfifo");
        return 1;
    }
    int fd = open("./myfifo", O_WRONLY);
    if(fd < 0){
        perror("open");
        return 2;
    }
    while(1){
        char buf[1024] = {0};
        printf("plase enter!:");
        fflush(stdout);
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if(read_size > 0){
            buf[read_size] = 0;
            write(fd, buf, strlen(buf));
        }
        if(read_size <= 0){
            perror("read");
            return 3;
        }
    }
    close(fd);
    return 0;
}

這裏寫圖片描述

發佈了47 篇原創文章 · 獲贊 21 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章