您好,登錄后才能下訂單哦!
這篇文章主要介紹了Linux如何通過匿名管道進(jìn)行進(jìn)程間通信,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
如果你使用過Linux的命令,那么對(duì)于管道這個(gè)名詞你一定不會(huì)感覺到陌生,因?yàn)槲覀兺ǔMㄟ^符號(hào)“|"來(lái)使用管道,但是管理的真正定義是什么呢?管道是一個(gè)進(jìn)程連接數(shù)據(jù)流到另一個(gè)進(jìn)程的通道,它通常是用作把一個(gè)進(jìn)程的輸出通過管道連接到另一個(gè)進(jìn)程的輸入。
舉個(gè)例子,在shell中輸入命令:ls -l | grep string,我們知道ls命令(其實(shí)也是一個(gè)進(jìn)程)會(huì)把當(dāng)前目錄中的文件都列出來(lái),但是它不會(huì)直接輸出,而是把本來(lái)要輸出到屏幕上的數(shù)據(jù)通過管道輸出到grep這個(gè)進(jìn)程中,作為grep這個(gè)進(jìn)程的輸入,然后這個(gè)進(jìn)程對(duì)輸入的信息進(jìn)行篩選,把存在string的信息的字符串(以行為單位)打印在屏幕上。
有靜就有動(dòng),有開就有關(guān),與此相同,與popen函數(shù)相對(duì)應(yīng)的函數(shù)是pclose函數(shù),它們的原型如下:
#include <stdio.h> FILE* popen (const char *command, const char *open_mode); int pclose(FILE *stream_to_close);
poen函數(shù)允許一個(gè)程序?qū)⒘硪粋€(gè)程序作為新進(jìn)程來(lái)啟動(dòng),并可以傳遞數(shù)據(jù)給它或者通過它接收數(shù)據(jù)。command是要運(yùn)行的程序名和相應(yīng)的參數(shù)。open_mode只能是"r(只讀)"和"w(只寫)"的其中之一。注意,popen函數(shù)的返回值是一個(gè)FILE類型的指針,而Linux把一切都視為文件,也就是說我們可以使用stdio I/O庫(kù)中的文件處理函數(shù)來(lái)對(duì)其進(jìn)行操作。
如果open_mode是"r",主調(diào)用程序就可以使用被調(diào)用程序的輸出,通過函數(shù)返回的FILE指針,就可以能過stdio函數(shù)(如fread)來(lái)讀取程序的輸出;如果open_mode是"w",主調(diào)用程序就可以向被調(diào)用程序發(fā)送數(shù)據(jù),即通過stdio函數(shù)(如fwrite)向被調(diào)用程序?qū)憯?shù)據(jù),而被調(diào)用程序就可以在自己的標(biāo)準(zhǔn)輸入中讀取這些數(shù)據(jù)。
pclose函數(shù)用于關(guān)閉由popen創(chuàng)建出的關(guān)聯(lián)文件流。pclose只在popen啟動(dòng)的進(jìn)程結(jié)束后才返回,如果調(diào)用pclose時(shí)被調(diào)用進(jìn)程仍在運(yùn)行,pclose調(diào)用將等待該進(jìn)程結(jié)束。它返回關(guān)閉的文件流所在進(jìn)程的退出碼。
很多時(shí)候,我們根本就不知道輸出數(shù)據(jù)的長(zhǎng)度,為了避免定義一個(gè)非常大的數(shù)組作為緩沖區(qū),我們可以以塊的方式來(lái)發(fā)送數(shù)據(jù),一次讀取一個(gè)塊的數(shù)據(jù)并發(fā)送一個(gè)塊的數(shù)據(jù),直到把所有的數(shù)據(jù)都發(fā)送完。下面的例子就是采用這種方式的數(shù)據(jù)讀取和發(fā)送方式。源文件名為popen.c,代碼如下:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { FILE *read_fp = NULL; FILE *write_fp = NULL; char buffer[BUFSIZ + 1]; int chars_read = 0; //初始化緩沖區(qū) memset(buffer, '\0', sizeof(buffer)); //打開ls和grep進(jìn)程 read_fp = popen("ls -l", "r"); write_fp = popen("grep rwxrwxr-x", "w"); //兩個(gè)進(jìn)程都打開成功 if(read_fp && write_fp) { //讀取一個(gè)數(shù)據(jù)塊 chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); while(chars_read > 0) { buffer[chars_read] = '\0'; //把數(shù)據(jù)寫入grep進(jìn)程 fwrite(buffer, sizeof(char), chars_read, write_fp); //還有數(shù)據(jù)可讀,循環(huán)讀取數(shù)據(jù),直到讀完所有數(shù)據(jù) chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); } //關(guān)閉文件流 pclose(read_fp); pclose(write_fp); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); }
運(yùn)行結(jié)果如下:
從運(yùn)行結(jié)果來(lái)看,達(dá)到了信息篩選的目的。程序在進(jìn)程ls中讀取數(shù)據(jù),再把數(shù)據(jù)發(fā)送到進(jìn)程grep中進(jìn)行篩選處理,相當(dāng)于在shell中直接輸入命令:ls -l | grep rwxrwxr-x。
當(dāng)請(qǐng)求popen調(diào)用運(yùn)行一個(gè)程序時(shí),它首先啟動(dòng)shell,即系統(tǒng)中的sh命令,然后將command字符串作為一個(gè)參數(shù)傳遞給它。
這樣就帶來(lái)了一個(gè)優(yōu)點(diǎn)和一個(gè)缺點(diǎn)。優(yōu)點(diǎn)是:在Linux中所有的參數(shù)擴(kuò)展都是由shell來(lái)完成的。所以在啟動(dòng)程序(command中的命令程序)之前先啟動(dòng)shell來(lái)分析命令字符串,也就可以使各種shell擴(kuò)展(如通配符)在程序啟動(dòng)之前就全部完成,這樣我們就可以通過popen啟動(dòng)非常復(fù)雜的shell命令。
而它的缺點(diǎn)就是:對(duì)于每個(gè)popen調(diào)用,不僅要啟動(dòng)一個(gè)被請(qǐng)求的程序,還要啟動(dòng)一個(gè)shell,即每一個(gè)popen調(diào)用將啟動(dòng)兩個(gè)進(jìn)程,從效率和資源的角度看,popen函數(shù)的調(diào)用比正常方式要慢一些。
如果說popen是一個(gè)高級(jí)的函數(shù),pipe則是一個(gè)底層的調(diào)用。與popen函數(shù)不同的是,它在兩個(gè)進(jìn)程之間傳遞數(shù)據(jù)不需要啟動(dòng)一個(gè)shell來(lái)解釋請(qǐng)求命令,同時(shí)它還提供對(duì)讀寫數(shù)據(jù)的更多的控制。
pipe函數(shù)的原型如下:
#include <unistd.h> int pipe(int file_descriptor[2]);
我們可以看到pipe函數(shù)的定義非常特別,該函數(shù)在數(shù)組中墻上兩個(gè)新的文件描述符后返回0,如果返回返回-1,并設(shè)置errno來(lái)說明失敗原因。
數(shù)組中的兩個(gè)文件描述符以一種特殊的方式連接起來(lái),數(shù)據(jù)基于先進(jìn)先出的原則,寫到file_descriptor[1]的所有數(shù)據(jù)都可以從file_descriptor[0]讀回來(lái)。由于數(shù)據(jù)基于先進(jìn)先出的原則,所以讀取的數(shù)據(jù)和寫入的數(shù)據(jù)是一致的。
1、從函數(shù)的原型我們可以看到,它跟popen函數(shù)的一個(gè)重大區(qū)別是,popen函數(shù)是基于文件流(FILE)工作的,而pipe是基于文件描述符工作的,所以在使用pipe后,數(shù)據(jù)必須要用底層的read和write調(diào)用來(lái)讀取和發(fā)送。
2、不要用file_descriptor[0]寫數(shù)據(jù),也不要用file_descriptor[1]讀數(shù)據(jù),其行為未定義的,但在有些系統(tǒng)上可能會(huì)返回-1表示調(diào)用失敗。數(shù)據(jù)只能從file_descriptor[0]中讀取,數(shù)據(jù)也只能寫入到file_descriptor[1],不能倒過來(lái)。
例子:
首先,我們?cè)谠鹊倪M(jìn)程中創(chuàng)建一個(gè)管道,然后再調(diào)用fork創(chuàng)建一個(gè)新的進(jìn)程,最后通過管道在兩個(gè)進(jìn)程之間傳遞數(shù)據(jù)。源文件名為pipe.c,代碼如下:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed = 0; int filedes[2]; const char data[] = "Hello pipe!"; char buffer[BUFSIZ + 1]; pid_t pid; //清空緩沖區(qū) memset(buffer, '\0', sizeof(buffer)); if(pipe(filedes) == 0) { //創(chuàng)建管道成功 //通過調(diào)用fork創(chuàng)建子進(jìn)程 pid = fork(); if(pid == -1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } if(pid == 0) { //子進(jìn)程中 //讀取數(shù)據(jù) data_processed = read(filedes[0], buffer, BUFSIZ); printf("Read %d bytes: %s\n", data_processed, buffer); exit(EXIT_SUCCESS); } else { //父進(jìn)程中 //寫數(shù)據(jù) data_processed = write(filedes[1], data, strlen(data)); printf("Wrote %d bytes: %s\n", data_processed, data); //休眠2秒,主要是為了等子進(jìn)程先結(jié)束,這樣做也只是純粹為了輸出好看而已 //父進(jìn)程其實(shí)沒有必要等等子進(jìn)程結(jié)束 sleep(2); exit(EXIT_SUCCESS); } } exit(EXIT_FAILURE); }
運(yùn)行結(jié)果為:
可見,子進(jìn)程讀取了父進(jìn)程寫到filedes[1]中的數(shù)據(jù),如果在父進(jìn)程中沒有sleep語(yǔ)句,父進(jìn)程可能在子進(jìn)程結(jié)束前結(jié)束,這樣你可能將看到兩個(gè)輸入之間有一個(gè)命令提示符分隔。
下面來(lái)介紹一種用管道來(lái)連接兩個(gè)進(jìn)程的更簡(jiǎn)潔方法,我們可以把文件描述符設(shè)置為一個(gè)已知值,一般是標(biāo)準(zhǔn)輸入0或標(biāo)準(zhǔn)輸出1。這樣做最大的好處是可以調(diào)用標(biāo)準(zhǔn)程序,即那些不需要以文件描述符為參數(shù)的程序。
為了完成這個(gè)工作,我們還需要兩個(gè)函數(shù)的輔助,它們分別是dup函數(shù)或dup2函數(shù),它們的原型如下
#include <unistd.h> int dup(int file_descriptor); int dup2(int file_descriptor_one, int file_descriptor_two);
dup調(diào)用創(chuàng)建一個(gè)新的文件描述符與作為它的參數(shù)的那個(gè)已有文件描述符指向同一個(gè)文件或管道。對(duì)于dup函數(shù)而言,新的文件描述總是取最小的可用值。而dup2所創(chuàng)建的新文件描述符或者與int file_descriptor_two相同,或者是第一個(gè)大于該參數(shù)的可用值。所以當(dāng)我們首先關(guān)閉文件描述符0后調(diào)用dup,那么新的文件描述符將是數(shù)字0.
在下面的例子中,首先打開管道,然后fork一個(gè)子進(jìn)程,然后在子進(jìn)程中,使標(biāo)準(zhǔn)輸入指向讀管道,然后關(guān)閉子進(jìn)程中的讀管道和寫管道,只留下標(biāo)準(zhǔn)輸入,最后調(diào)用execlp函數(shù)來(lái)啟動(dòng)一個(gè)新的進(jìn)程od,但是od并不知道它的數(shù)據(jù)來(lái)源是管道還是終端。父進(jìn)程則相對(duì)簡(jiǎn)單,它首先關(guān)閉讀管道,然后在寫管道中寫入數(shù)據(jù),再關(guān)閉寫管道就完成了它的任務(wù)。源文件為pipe2.c,代碼如下:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed = 0; int pipes[2]; const char data[] = "123"; pid_t pid; if(pipe(pipes) == 0) { pid = fork(); if(pid == -1) { fprintf(stderr, "Fork failure!\n"); exit(EXIT_FAILURE); } if(pid == 0) { //子進(jìn)程中 //使標(biāo)準(zhǔn)輸入指向fildes[0] close(0); dup(pipes[0]); //關(guān)閉pipes[0]和pipes[1],只剩下標(biāo)準(zhǔn)輸入 close(pipes[0]); close(pipes[1]); //啟動(dòng)新進(jìn)程od execlp("od", "od", "-c", 0); exit(EXIT_FAILURE); } else { //關(guān)閉pipes[0],因?yàn)楦高M(jìn)程不用讀取數(shù)據(jù) close(pipes[0]); data_processed = write(pipes[1], data, strlen(data)); //寫完數(shù)據(jù)后,關(guān)閉pipes[1] close(pipes[1]); printf("%d - Wrote %d bytes\n", getpid(), data_processed); } } exit(EXIT_SUCCESS); }
運(yùn)行結(jié)果為:
從運(yùn)行結(jié)果中可以看出od進(jìn)程正確地完成了它的任務(wù),與在shell中直接輸入od -c和123的效果一樣。
現(xiàn)在有這樣一個(gè)問題,假如父進(jìn)程向管道file_pipe[1]寫數(shù)據(jù),而子進(jìn)程在管道file_pipe[0]中讀取數(shù)據(jù),當(dāng)父進(jìn)程沒有向file_pipe[1]寫數(shù)據(jù)時(shí),子進(jìn)程則沒有數(shù)據(jù)可讀,則子進(jìn)程會(huì)發(fā)生什么呢?再者父進(jìn)程把file_pipe[1]關(guān)閉了,子進(jìn)程又會(huì)有什么反應(yīng)呢?
當(dāng)寫數(shù)據(jù)的管道沒有關(guān)閉,而又沒有數(shù)據(jù)可讀時(shí),read調(diào)用通常會(huì)阻塞,但是當(dāng)寫數(shù)據(jù)的管道關(guān)閉時(shí),read調(diào)用將會(huì)返回0而不是阻塞。注意,這與讀取一個(gè)無(wú)效的文件描述符不同,read一個(gè)無(wú)效的文件描述符返回-1。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Linux如何通過匿名管道進(jìn)行進(jìn)程間通信”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。