一 Linux進程間通信( 二 )


父子進程通過匿名管道通信原理:匿名管道是提供給有親緣關系兩個進程進行通信的 。所以我們可以在創建管道之后通過fork函數創建子進程 , 這樣父子進程就看到同一份資源,且父子進程都有這個管道的讀寫文件描述符 。我們可以關閉父進程的讀端,關閉子進程的寫端,這樣子進程往管道里面寫數據,父進程往管道里面讀數據,這樣兩個進程就可以實現通信了 。原理解讀:
fork函數調用成功后,將為子進程申請PCB和用戶內存空間,子進程是父進程的副本,在用戶空間將復制父進程用戶空間所有的數據(代碼段、數據段、BBS、棧、堆,實際上是復制的父進程的虛擬空間的地址),子進程從父進程繼承下列屬性:有效用戶、組號、進程組號、環境變量、信號處理方式設置、信號屏蔽集合、當前工作目錄、根目錄、文件模式掩碼、文件大小限制和打開的文件描述符(特別注意:共享同一文件表項) 。
共享同一文件表項就造成了一種現象,父子進程無論誰對文件進行操作 , 那么另外一個進程的文件表也會受到相同的影響 。

一 Linux進程間通信

文章插圖
從圖中可以看出,雖然在子進程的表項中式復制了關于打開文件的信息,但是他們是共享文件表的,所以如果一個進程對文件指針進行移動 , 那么肯定會影響到另外的進程 。
思考:這是不是和寫時拷貝相違背了,為什么文件表就能共享了呢?
要知道在linux源碼中,每個進程都存在一個PCB結構體,每個PCB中,存放了一個結構體指針指向一個我們理解為文件描述符的結構體struct file,而這個結構體里,才存了文件的id,值得注意的是,這個結構體里有一個指針才是指向真正文件的 。文件系統存在于磁盤當中,對磁盤的操作操作系統不會拷貝一份文件給子進程,相反,像那些臨時創建存放于堆區和棧區的數據,操作系統會采用寫時拷貝,進行復制 。
一 Linux進程間通信

文章插圖
總結:父子進程共享文件表,對文件表進行的任何操作都會對父子進程造成相同的影響 , 與寫時拷貝進行區分 。
父子進程通過創建匿名管道通信具體過程如下:
1.父進程創建管道(管道創建要在進程創建之前)
一 Linux進程間通信

文章插圖
2.fork創建子進程(子進程繼承父進程的管道文件描述符)
一 Linux進程間通信

文章插圖
3.關閉父進程的寫段,子進程的讀端
一 Linux進程間通信

文章插圖
實例演示: 子進程每隔1秒往管道里面寫數據,父進程每隔1秒往管道里讀數據
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>int main(){int pipefd[2];int ret = pipe(pipefd);if (ret == -1){// 管道創建失敗perror("make piep");exit(-1);}pid_t id = fork();if (id < 0){perror("fork failed");exit(-1);}else if (id == 0){// child// 關閉讀端close(pipefd[0]);const char* msg = "I am child...!\n";//int count = 0;// 寫數據while (1){ssize_t s = write(pipefd[1], msg, strlen(msg));printf("child is sending message...\n");sleep(1);}}else{// parentclose(pipefd[1]);char buf[64];while (1){ssize_t s = read(pipefd[0], buf, sizeof(buf)/sizeof(buf[0])-1);if (s > 0){buf[s] = '\0';// 字符串后放一個'\0'printf("father get message:%s", buf);}else if (s == 0){// 讀到文件結尾寫端關閉文件描述符 讀端會讀到文件結尾printf("father read end of file...\n ");}sleep(1);}}return 0;}運行結果如下:
一 Linux進程間通信

文章插圖
匿名管道讀寫規則
一 Linux進程間通信

文章插圖
讀寫規則總結: