管道


管道是兩個或更多相關或相關進程之間的通訊媒介。 它可以在一個進程內,也可以在子進程和父進程之間進行通訊。 通訊也可以是多層次的,如父進程,子進程和子進程之間的溝通等。通訊是通過一個過程寫入管道和從管道讀取來實現的。 要實現管道系統呼叫,請建立兩個檔案,一個寫入檔案,另一個從檔案讀取。

管道機制可以用一個實時的場景來看,比如用管子把水灌進一個容器裡。 填充過程可以理解為是寫入管道,讀取過程只不過是從管道中取出。 這意味著一個輸出(水)是為另一個(桶)輸入的。參考下圖 -

#include<unistd.h>

int pipe(int pipedes[2]);

這個系統呼叫將建立一個單向通訊的管道,即它建立兩個描述符,第一個連線從管道讀取,另一個連線寫入管道。

描述符pipedes [0]用於讀取,pipedes [1]用於寫入。 無論寫入pipedes [1]什麼都可以從pipedes [0]中讀取。

這個呼叫在成功時將返回0,在失敗的情況下為-1。 要知道失敗的原因,請檢查errno變數或perror()函式。

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

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

即使檔案的基本操作是讀寫,在完成所需的操作之後,在執行操作和關閉檔案之前,必須開啟檔案。 通常,預設情況下,為每個進程開啟3個描述符,分別用於輸入(標準輸入 - 標準輸入),輸出(標準輸出 - 標準輸出)和錯誤(標準錯誤 - 標準錯誤),分別具有檔案描述符0,12

這個系統呼叫將返回一個檔案描述符,用於讀/寫/查詢(lseek)的進一步檔案操作。 通常檔案描述符從3開始,隨著開啟的檔案數量增加一個數位。

傳遞給開放系統呼叫的引數是路徑名(相對路徑或絕對路徑),標誌提到了開啟檔案的目的(比如開啟O_RDONLY進行讀取,O_RDONR,讀寫O_RDWR,以追加到現有的檔案 O_APPEND,建立檔案,如果不存在與O_CREAT等)和所需的模式提供讀/寫/執行許可權的使用者或所有者/組/其他人。 模式可以用符號來描述。

讀使用4表示,寫使用2表示和執行使用1表示。

例如:八進位制值(以0開頭),0764表示擁有者擁有讀取,寫入和執行許可權,組擁有讀寫許可權,其他擁有讀許可權。 這也可以表示為S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH,表示或操作0700 | 0040 | 00040764

這個系統呼叫在成功時返回新的檔案描述符id-1以防出錯。 錯誤的原因可以用errno變數或perror()函式來標識。

#include<unistd.h>

int close(int fd)

上面的系統呼叫關閉已經開啟的檔案描述符。 這意味著檔案不再被使用,相關的資源可以被任何其他進程重用。 這個系統呼叫在成功時返回零,在出錯時返回-1。 錯誤的原因可以用errno變數或perror()函式來標識。

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

上面的系統呼叫是從指定的檔案中讀取檔案描述符fd的引數,具有分配記憶體(靜態或動態)的正確緩衝區以及緩衝區的大小。

檔案描述符ID用於標識在呼叫open()pipe()系統呼叫之後返回的相應檔案。 從檔案中讀取檔案之前,需要開啟該檔案。 它會在呼叫pipe()系統呼叫的情況下自動開啟。

這個呼叫將返回成功時讀取的位元組數(或在遇到檔案結尾的情況下為零),在失敗的情況下返回-1。 返回位元組可以小於請求的位元組數,以防萬一沒有資料可用或檔案關閉。

要知道失敗的原因,請檢查errno變數或perror()函式。

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

上面的系統呼叫是使用檔案描述符fd的引數,具有已分配記憶體(靜態或動態)和緩衝區大小的適當緩衝區來寫入指定檔案。

檔案描述符ID用於標識在呼叫open()pipe()系統呼叫之後返回的相應檔案。

在寫入檔案之前需要開啟該檔案。 它會在呼叫pipe()系統呼叫的情況下自動開啟。

這個呼叫將返回成功寫入的位元組數(或者在沒有寫入的情況下為零),如果失敗則返回-1。 正確的錯誤碼設定在失敗的情況下。

要知道失敗的原因,請檢查errno變數或perror()函式。

範例程式

以下是一些範例程式。

範例程式1 - 使用管道編寫和讀取兩條訊息的程式。

演算法

第1步 - 建立一個管道。
第2步 - 傳送訊息到管道。
第3步 - 從管道中檢索訊息並將其寫入標準輸出。
第4步 - 傳送另一條訊息到管道。
第5步 - 從管道中檢索訊息並將其寫入標準輸出。
註 - 檢索訊息也可以在傳送所有訊息之後完成。

原始碼:simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);

   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }

   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

註 - 理想情況下,每個系統呼叫都需要檢查返回狀態。 為了簡化這個過程,所有的呼叫都沒有進行檢查。

執行步驟

首先編譯 -

$ gcc -o simplepipe simplepipe.c

執行輸出結果如下 -

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

範例程式2 - 使用父進程和子進程通過管道寫入和讀取兩條訊息的程式。

演算法
第1步 - 建立一個管道。
第2步 - 建立一個子進程。
第3步 - 父進程寫入管道。
第4步 - 子進程從管道中檢索訊息並將其寫入標準輸出。
第5步 - 再次重複步驟3和步驟4。

原始碼:pipewithprocesses.c -

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();

   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

執行步驟

首先編譯 -

$ gcc pipewithprocesses.c –o pipewithprocesses

執行輸出結果如下 -

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

使用管道雙向通訊

管道通訊僅被視為單向通訊,即,父進程寫入和子進程讀取,或者相反,但不是兩者。 但是,如果父子進程雙方都需要同時寫入和讀取管道,那麼解決方案就是使用管道的雙向通訊。 建立雙向通訊需要兩根管道。

以下是實現雙向通訊的步驟 -

第1步 - 建立兩個管道。 第一個是父進程寫入和子進程讀取,如:pipe1。 第二個是為子進程寫入和父進程讀取,如:pipe2。
第2步 - 建立一個子進程。
第3步 - 關閉不需要的結束,因為每個通訊只需要一端。
第4步 - 在父進程中關閉不需要的結束,讀取pipe1的結尾並寫入pipe2的結尾。
第5步 - 關閉子進程中的不需要的結束,寫入pipe1的結尾和讀取pipe2的結束。
第6步 - 根據需要進行通訊。

範例程式
範例程式1 - 使用管道實現雙向通訊。

演算法
第1步 - 建立pipe1為父進程寫和子進程讀取。
第2步 - 為要寫入的子進程和要讀取的父進程建立pipe2
第3步 - 關閉從父進程和子進程一側管道不需要的一端。
第4步 - 父進程寫一個訊息和子進程讀取並顯示在螢幕上。
第5步 - 子進程寫入訊息和父進程讀取並顯示在螢幕上。

原始碼:twowayspipe.c -

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);

   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);

   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();

   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

執行步驟

首先編譯 -

$ gcc twowayspipe.c –o twowayspipe

執行輸出結果如下 -

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello