溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Linux如何通過匿名管道進(jìn)行進(jìn)程間通信

發(fā)布時(shí)間:2021-06-11 12:48:19 來(lái)源:億速云 閱讀:158 作者:小新 欄目:服務(wù)器

這篇文章主要介紹了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的信息的字符串(以行為單位)打印在屏幕上。

二、使用popen函數(shù)

1、popen函數(shù)和pclose函數(shù)介紹

有靜就有動(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)程的退出碼。

2、例子

很多時(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é)果如下:

Linux如何通過匿名管道進(jìn)行進(jìn)程間通信

從運(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。

3、popen的實(shí)現(xiàn)方式及優(yōu)缺點(diǎn)

當(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)用比正常方式要慢一些。

三、pipe調(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é)果為:

Linux如何通過匿名管道進(jìn)行進(jìn)程間通信

可見,子進(jìn)程讀取了父進(jìn)程寫到filedes[1]中的數(shù)據(jù),如果在父進(jìn)程中沒有sleep語(yǔ)句,父進(jìn)程可能在子進(jìn)程結(jié)束前結(jié)束,這樣你可能將看到兩個(gè)輸入之間有一個(gè)命令提示符分隔。

四、把管道用作標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出

下面來(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é)果為:

Linux如何通過匿名管道進(jìn)行進(jìn)程間通信

從運(yùn)行結(jié)果中可以看出od進(jìn)程正確地完成了它的任務(wù),與在shell中直接輸入od -c和123的效果一樣。

五、關(guān)于管道關(guān)閉后的讀操作的討論

現(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í)!

向AI問一下細(xì)節(jié)

免責(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)容。

AI