Linux進程通信之管道解析
概述
管道是 UNIX系統 IPC的最古老的形式,所有的UNIX系統都提供此種通信。所謂的管道,也就是內核里面的一串緩存,從管道的一段寫入的數據,實際上是緩存在內核中的,令一端讀取,也就是從內核中讀取這段數據。對于管道傳輸的數據是無格式的流且大小受限。對于管道來說,也分為匿名管道和命名管道,其中命名管道也被叫做 FIFO,下面則分別闡述這兩種管道。
匿名管道
默認情況下,在 Shell命令執行過程中,任何一個命令都有一個標準輸入設備(鍵盤)、標準輸出設備(顯示器)和標準輸出設備(顯示器),使用管道"|"可以將兩個命令連接起來,從而改變標準的輸入輸出方式,下面是在 Linux 端運行命令行的一個截圖:
上述命令中的意思也就是,將ls命令得到的結果作為 grep tags命令的輸入。
連接輸入輸出的中間設備即為一個管道文件,綜上,也就是說使用管道可以將一個命令的輸出作為另一個命令的輸入(在運行的時候,一個命令將創建一個進程),而這種管道是臨時的,命令執行完畢之后就會自動消失,這類管道稱為無名管道。
匿名管道例子
匿名管道在使用前要先創建,其函數的聲明如下:
- extern int pipe (int __pipedes[2]);
此函數的參數是一個整型數組,如果執行成功,pipe 將存儲兩個整型文件描述符于__pipedes[0]和__pipedes[1]中,他們分別指向管道的兩端。如果系統調用失敗,則返回 -1。
讀無名管道,該函數的聲明如下:
- extern ssize_t read (int __fd, void *__buf, size_t __nbytes);
第一個參數fd為打開的文件描述符,buf為讀出數據的存儲位置,nbytes為讀取數據的大小,調用 read 函數將從 fd 指向的文件描述符指定的打開文件中宏讀 n 字節到 buf 指向的緩沖區內。
如果試圖向已經填滿的管道寫入,系統會自動阻塞。一個管道不能同時被兩個進程打開。
- extern ssize_ t write(int __fd, __const void *__buf, size_t __n);
從 buf指向的緩沖區中向管道中寫入nbytes字節,且每次寫入的內容都附件在管道的末端。
那要如何使用管道在兩個進程之間通信呢,我們可以使用 fork()創建子進程,創建的子進程會復制父進程的文件描述符,這樣就做到了兩個進程各有兩個fd[0]與fd[1],兩個進程就可以通過各自的fd寫入和讀取同一個管道文件實現進程通信了,具體原理如下所示:
具體的例子如下所示:
- #include<unistd.h>
- #include<stdio.h>
- #include<stdlib.h>
- int main(int argc, char *argv[])
- {
- pid_t pid;
- int temp;
- int pipedes[2];
- char s[14] = "test message!";
- char d[14];
- if (pipe(pipedes) == -1) // 創建管道
- {
- perror("pipe");
- exit(EXIT_FAILURE);
- }
- if (pid == fork() == -1)
- {
- perror("fork");
- exit(EXIT_FAILURE);
- }
- else if (pid == 0) // 子進程
- {
- printf("now,write data to pipe\n");
- if (write(pipedes[1], s, 14) == -1) // 寫數據到管道
- {
- perror("write");
- exit(EXIT_FAILURE);
- }
- else
- {
- printf("the written data is:%s\n",s);
- exit(EXIT_SUCESS);
- }
- }
- else if (pid > 0) // 父進程
- {
- slepp(2);
- printf("now, read from pipe\n");
- if ((read(pipedes[0], d, 14)) == -1)
- {
- perror("read");
- exit(EXIT_FAILURE);
- }
- printf("the data from pipe is:%s\n",d);
- }
- return 0;
- }
代碼運行的結果如下所示:
命名管道
命名管道又被稱之為是 FIFO ,未命名的管道只能在兩個相關的進程之間使用,而且這兩個相關的進程還要又一個共同創建了他們的祖先進程,但是,通過 FIFO ,不相關的進程也能交換數據。
首先,介紹下是如何創建命名管道的:
- extern int mkfifo (__const char *__path, __mode_t __mode);
mkfifo會根據參數建立特殊的有名管道文件,該文件必須不存在,而參數mode為該文件的權限。
下面是一個使用命名管道進行進程間通信的例子,例子分為兩個程序,分別是讀部分和寫部分,首先看先往管道寫數據的代碼,代碼如下所示:
- #include <stdio.h>
- #include <string.h>
- #include <fcntl.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- int fd;
- // FIFO file path
- char * myfifo = "/tmp/myfifo";
- // Creating the named file(FIFO)
- // mkfifo(<pathname>, <permission>)
- mkfifo(myfifo, 0666);
- char arr1[80], arr2[80];
- while (1)
- {
- // Open FIFO for write only
- fd = open(myfifo, O_WRONLY);
- printf("The fd is:%d\n",fd);
- // Take an input arr2ing from user.
- // 80 is maximum length
- fgets(arr2, 80, stdin);
- // Write the input arr2ing on FIFO
- // and close it
- write(fd, arr2, strlen(arr2)+1);
- close(fd);
- // Open FIFO for Read only
- fd = open(myfifo, O_RDONLY);
- // Read from FIFO
- read(fd, arr1, sizeof(arr1));
- // Print the read message
- printf("User2: %s", arr1);
- close(fd);
- }
- return 0;
- }
然后是先往管道讀數據的代碼,代碼如下所示:
- #include <stdio.h>
- #include <string.h>
- #include <fcntl.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- int fd1;
- // FIFO file path
- char * myfifo = "/tmp/myfifo";
- char str1[80], str2[80];
- while (1)
- {
- // First open in read only and read
- fd1 = open(myfifo,O_RDONLY);
- printf("The fd is:%d\n",fd1);
- read(fd1, str1, 80);
- // Print the read string and close
- printf("User1: %s", str1);
- close(fd1);
- // Now open in write mode and write
- // string taken from user.
- fd1 = open(myfifo,O_WRONLY);
- fgets(str2, 80, stdin);
- write(fd1, str2, strlen(str2)+1);
- close(fd1);
- }
- return 0;
- }
下面是代碼運行的一個結果:
說明一下,就是說當運行 write程序的時候,會創建fifo文件,命名管道,然后,在 write文件中就執行open操作,但是,這里存在的一個問題就是,因為在運行 write程序的時候,沒有進程打開讀端,也就阻塞了 open函數的運行,只有運行read操作,以讀的方式讀取管道的數據,這樣才能使得write中的open函數繼續執行。
綜上,也就是命名管道在進程中通信的一個例子。
小結
上述就是本次進程通信中關于管道的相關內容,其中就包括匿名管道以及命名管道,他們之間存在著差別嗎,也各有各的應用,本次的分享就到這里啦~
本文轉載自微信公眾號「wenzi嵌入式軟件」,可以通過以下二維碼關注。轉載本文請聯系wenzi嵌入式軟件公眾號。