Linux 進程間通訊(一)(經典IPC:管道、FIFO)

管道

管道是Unix系統IPC的最古老方式,有兩種侷限性:shell

(1)   歷史上它們是半雙工的(即數據只能在一個方向上流動),雖然如今某些系統提供了全雙工管道,可是爲了可移植性,不要抱有絕對的全雙工假設。服務器

(2)   管道只能在具備公共祖先的兩個進程之間使用(通常都是用於父子進程之間)。ide

管道是經過調用pipe函數建立的:函數

#include <unistd.h>大數據

int pipe(int fd[2]);this

返回值:成功,返回0;失敗,返回-1spa

說明:設計

fd返回兩個文件描述符:fd[0]用於讀,fd[1]用於寫,fd[1]的輸出恰好是fd[0]的輸入。指針

即shell爲每一條命令單首創建一個進程,而後管道將前一條命令的標準輸出與後一條命令的標準輸入相鏈接。excel

注:

POSIX.1容許實現支持全雙工管道,對於這些實現,fd[0]和fd[1]以讀/寫方式打開。

以下給出了兩種描繪半雙工管道的方法,左圖中管道的兩端在一個進程中相互鏈接,右圖中則強調數據須要經過內核在管道中流動:

 

管道一般在單個進程中沒有太大用處,下圖顯示了父子進程之間的管道:進程先調用pipe,接着調用fork,從而建立從父進程到子進程的IPC管道

 

fork以後具體要作什麼取決於咱們想要的數據流的方向。對於從父進程到子進程的管道,父進程關閉管道的讀端(fd[0]),子進程關閉管道的寫端(fd[1]):

 

當管道的一端被關閉後,下列兩條規則起做用:

(1)   當read一個寫端已被關閉的管道時,在全部數據都被讀取後,read返回0,表示文件結束。

(2)   當write一個讀端已被關閉的管道時,則產生信號SIGPIPE,若是忽略該信號或者捕捉該信號並從其處理程序返回,則write返回-1,errno設置爲EPIPE。

 

以下爲管道程序實例:

實例一:建立一個從父進程到子進程的管道,而且父進程經由該管道向子進程傳送數據:

 1 [root@benxintuzi ipc]# cat pipe1.c
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 
 5 #define MAXLINE 1024
 6 
 7 int main(void)
 8 {
 9         int             n;
10         int             fd[2];
11         pid_t   pid;
12         char    line[MAXLINE];
13 
14         if (pipe(fd) < 0)
15                 printf("pipe error\n");
16         if ((pid = fork()) < 0) {
17                 printf("fork error\n");
18         } else if (pid > 0) {           /* parent */
19                 close(fd[0]);
20                 write(fd[1], "hello world\n", 12);              /* write data to fd[1] */
21         } else {                                        /* child */
22                 close(fd[1]);
23                 n = read(fd[0], line, MAXLINE);                 /* read data from fd[0] */
24                 write(STDOUT_FILENO, line, n);                  /* write data to standard output */
25         }
26 
27         return (0);
28 }
29 
30 [root@benxintuzi ipc]# ./pipe1
31 [root@benxintuzi ipc]# hello world
32 
33 [root@benxintuzi ipc]#
View Code

 

實例二:編寫一個程序,其功能是每次一頁地顯示已產生的輸出。爲了不先將全部數據寫到一個臨時文件中,而後再調用系統中有關程序顯示該文件,咱們但願經過管道將輸出直接送到分頁程序。爲此,先建立一個管道,fork一個子進程,使子進程的標準輸入成爲管道的讀端,而後調用exec,執行分頁程序:(說明點:1. 在調用fork以前,先建立一個管道。調用fork以後,父進程關閉讀端,子進程關閉寫端,而後子進程調用dup2,使其標準輸入成爲管道的讀端。當執行分頁程序時,其標準輸入就是管道的讀端了; 2. 咱們使用環境變量PAGER來得到用戶分頁程序名,若是沒有成功,則使用系統默認值,這是環境變量的常見用法。)

  1 [root@benxintuzi ipc]# cat pipe2.c
  2 #include <unistd.h>
  3 #include <sys/wait.h>
  4 #include <stdio.h>
  5 
  6 #define DEF_PAGER       "/bin/more"             /* default pager program */
  7 #define MAXLINE 1024
  8 
  9 int main(int argc, char *argv[])
 10 {
 11         int             n;
 12         int             fd[2];
 13         pid_t   pid;
 14         char    *pager, *argv0;
 15         char    line[MAXLINE];
 16         FILE    *fp;
 17 
 18         if (argc != 2)
 19         {
 20                 printf("usage: a.out <pathname>\n");
 21                 return (-1);
 22         }
 23 
 24         if ((fp = fopen(argv[1], "r")) == NULL)
 25                 printf("can't open %s\n", argv[1]);
 26         if (pipe(fd) < 0)
 27                 printf("pipe error\n");
 28 
 29         if ((pid = fork()) < 0) {
 30                 printf("fork error\n");
 31         } else if (pid > 0) {                                             /* parent */
 32                 close(fd[0]);           /* close read end */
 33 
 34                 /* parent copies argv[1] to pipe */
 35                 while (fgets(line, MAXLINE, fp) != NULL) {
 36                         n = strlen(line);
 37                         if (write(fd[1], line, n) != n)
 38                                 printf("write error to pipe\n");
 39                 }
 40                 if (ferror(fp))
 41                         printf("fgets error\n");
 42 
 43                 close(fd[1]);   /* close write end of pipe for reader */
 44 
 45                 if (waitpid(pid, NULL, 0) < 0)
 46                         printf("waitpid error\n");
 47                 return (0);
 48         } else {                                                          /* child */
 49                 close(fd[1]);   /* close write end */
 50                 if (fd[0] != STDIN_FILENO) {
 51                         if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
 52                                 printf("dup2 error to stdin\n");
 53                         close(fd[0]);   /* don't need this after dup2 */
 54                 }
 55 
 56                 /* get arguments for execl() */
 57                 if ((pager = getenv("PAGER")) == NULL)
 58                         pager = DEF_PAGER;
 59                 if ((argv0 = strrchr(pager, '/')) != NULL)
 60                         argv0++;                /* step past rightmost slash */
 61                 else
 62                         argv0 = pager;  /* no slash in pager */
 63 
 64                 if (execl(pager, argv0, (char *)0) < 0)
 65                         printf("execl error for %s\n", pager);
 66         }
 67         return (0);
 68 }
 69 [root@benxintuzi ipc]# ./pipe2 pipe2.c
 70 #include <unistd.h>
 71 #include <sys/wait.h>
 72 #include <stdio.h>
 73 
 74 #define DEF_PAGER       "/bin/more"             /* default pager program */
 75 #define MAXLINE 1024
 76 
 77 int main(int argc, char *argv[])
 78 {
 79         int             n;
 80         int             fd[2];
 81         pid_t   pid;
 82         char    *pager, *argv0;
 83         char    line[MAXLINE];
 84         FILE    *fp;
 85 
 86         if (argc != 2)
 87         {
 88                 printf("usage: a.out <pathname>\n");
 89                 return (-1);
 90         }
 91 
 92         if ((fp = fopen(argv[1], "r")) == NULL)
 93                 printf("can't open %s\n", argv[1]);
 94         if (pipe(fd) < 0)
 95                 printf("pipe error\n");
 96 
 97         if ((pid = fork()) < 0) {
 98                 printf("fork error\n");
 99         } else if (pid > 0) {                                             /* parent */
100                 close(fd[0]);           /* close read end */
101 
102                 /* parent copies argv[1] to pipe */
103                 while (fgets(line, MAXLINE, fp) != NULL) {
104 --More--
View Code

 

實例三:父子進程同步函數的管道實現:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT、WAIT_CHILD:(說明點:父進程在調用TELL_CHILD時,經由上一個管道寫一個字符「p」,子進程在調用TELL_PARENT時,經由下一個管道寫一個字符「c」,相應的WAIT_XXX函數調用read讀一個字符,沒有讀到字符時則阻塞)

  

 1 static int    pfd1[2], pfd2[2];
 2 
 3 void TELL_WAIT(void)
 4 {
 5     if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
 6         printf("pipe error\n");
 7 }
 8 
 9 void TELL_PARENT(pid_t pid)
10 {
11     if (write(pfd2[1], "c", 1) != 1)
12         printf("write error\n");
13 }
14 
15 void WAIT_PARENT(void)
16 {
17     char    c;
18 
19     if (read(pfd1[0], &c, 1) != 1)
20         printf("read error\n");
21 
22     if (c != 'p')
23     {
24         printf("WAIT_PARENT: incorrect data\n");
25         return ;
26     }
27 
28 }
29 
30 void TELL_CHILD(pid_t pid)
31 {
32     if (write(pfd1[1], "p", 1) != 1)
33         printf("write error\n");
34 }
35 
36 void WAIT_CHILD(void)
37 {
38     char    c;
39 
40     if (read(pfd2[0], &c, 1) != 1)
41         printf("read error\n");
42 
43     if (c != 'c')
44     {
45         printf("WAIT_CHILD: incorrect data\n");
46         return ;
47     }
48 }
View Code

 

常見的操做是建立一個鏈接到另外一個進程的管道,而後讀其輸出或向其輸入端發送數據,爲此,標準I/O庫提供了兩個函數popen和pclose。這兩個函數的功能是:建立一個管道,fork一個子進程,關閉未使用的管道端,而後執行一個shell運行命令,等待命令終止(使用popen能夠減小代碼編寫量)。

#include <stdio.h>

FILE* popen(const char* cmdstring, const char* type);

返回值:成功,返回文件指針;失敗,返回NULL

int pclose(FILE* fp);

返回值:成功,返回cmdstring的終止狀態;失敗,返回-1

說明:

函數popen先執行fork,而後調用exec執行cmdstring,而且返回一個標準I/O文件指針。若是type是r,則文件指針鏈接到cmdstring的標準輸出;若是type是w,則文件指針鏈接到cmdstring的標準輸入,見下圖:

 

pclose函數關閉標準I/O流,等待命令終止,而後返回shell的終止狀態。

cmdstring由Bourne shell如下列方式執行:sh –c cmdstring

 shell命令${PAGER:-more}的意思是:若是shell變量PAGER已經定義,且其值非空,則使用其值,不然使用字符串more。利用popen函數重寫實例二

 1 [root@benxintuzi ipc]# cat pipe3.c
 2 #include <stdio.h>
 3 #include <sys/wait.h>
 4 
 5 #define MAXLINE 1024
 6 #define PAGER   "${PAGER:-more}" /* environment variable, or default */
 7 
 8 int main(int argc, char *argv[])
 9 {
10         char    line[MAXLINE];
11         FILE    *fpin, *fpout;
12 
13         if (argc != 2)
14         {
15                 printf("usage: a.out <pathname>\n");
16                 return (-1);
17         }
18 
19         if ((fpin = fopen(argv[1], "r")) == NULL)
20                 printf("can't open %s\n", argv[1]);
21 
22         if ((fpout = popen(PAGER, "w")) == NULL)
23                 printf("popen error\n");
24 
25         /* copy argv[1] to pager */
26         while (fgets(line, MAXLINE, fpin) != NULL) {
27                 if (fputs(line, fpout) == EOF)
28                         printf("fputs error to pipe\n");
29         }
30         if (ferror(fpin))
31                 printf("fgets error\n");
32         if (pclose(fpout) == -1)
33                 printf("pclose error\n");
34 
35         return (0);
36 }
37 [root@benxintuzi ipc]# ./pipe3 pipe2.c
38 #include <unistd.h>
39 #include <sys/wait.h>
40 #include <stdio.h>
41 
42 #define DEF_PAGER       "/bin/more"             /* default pager program */
43 #define MAXLINE 1024
44 
45 int main(int argc, char *argv[])
46 {
47         int             n;
48         int             fd[2];
49         pid_t   pid;
50         char    *pager, *argv0;
51         char    line[MAXLINE];
52         FILE    *fp;
53 
54         if (argc != 2)
55         {
56                 printf("usage: a.out <pathname>\n");
57                 return (-1);
58         }
59 
60         if ((fp = fopen(argv[1], "r")) == NULL)
61                 printf("can't open %s\n", argv[1]);
62         if (pipe(fd) < 0)
63                 printf("pipe error\n");
64 
65         if ((pid = fork()) < 0) {
66                 printf("fork error\n");
67         } else if (pid > 0) {                                             /* parent */
68                 close(fd[0]);           /* close read end */
69 
70                 /* parent copies argv[1] to pipe */
71                 while (fgets(line, MAXLINE, fp) != NULL) {
72 --More--
View Code

 

協同進程:

當一個進程既要產生某個程序的輸入,又讀取該程序的輸出時,它就變成了協同進程(coprocess)。協同進程一般在shell後臺運行,其標準輸入和標準輸出經過管道鏈接到另外一個程序。popen只提供鏈接到另外一個進程的標準輸入或標準輸出的一個單向管道,而協同進程則有鏈接到另外一個進程的兩個單向管道:一個鏈接到其標準輸入,另外一個則來自其標準輸出。

實例:從標準輸入讀取兩個數,計算它們的和,而後將和寫至其標準輸出。

 1 [root@benxintuzi ipc]# cat coprocess.c
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 
 5 #define MAXLINE 1024
 6 
 7 int main(void)
 8 {
 9         int             n, int1, int2;
10         char    line[MAXLINE];
11 
12         while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) {
13                 line[n] = 0;            /* null terminate */
14                 if (sscanf(line, "%d%d", &int1, &int2) == 2) {
15                         sprintf(line, "%d\n", int1 + int2);
16                         n = strlen(line);
17                         if (write(STDOUT_FILENO, line, n) != n)
18                                 printf("write error\n");
19                 } else {
20                         if (write(STDOUT_FILENO, "invalid args\n", 13) != 13)
21                                 printf("write error\n");
22                 }
23         }
24 
25         return (0);
26 }
27 
28 [root@benxintuzi ipc]# ./coprocess
29 1 2
30 3
31 10 20
32 30
33 100 200
34 300
35 ^C
36 [root@benxintuzi ipc]#
View Code

 

實例:將上述程序編譯成爲add2協同進程,而後下列程序建立了兩個管道,父進程、子進程各自關閉了它們不須要的管道端,必須使用兩個管道:一個用做協同進程的標準輸入,另外一個用做它的標準輸出。而後子進程調用dup2使管道描述符移至其標準輸入和標準輸出,最後調用了excel執行add2:

 1 [root@benxintuzi ipc]# gcc coprocess.c -o add2
 2 [root@benxintuzi ipc]# cat coprocess2.c
 3 #include <unistd.h>
 4 #include <signal.h>
 5 #include <stdio.h>
 6 
 7 #define MAXLINE 1024
 8 
 9 static void     sig_pipe(int);          /* our signal handler */
10 
11 int main(void)
12 {
13         int             n, fd1[2], fd2[2];
14         pid_t   pid;
15         char    line[MAXLINE];
16 
17         if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
18                 printf("signal error\n");
19 
20         if (pipe(fd1) < 0 || pipe(fd2) < 0)
21                 printf("pipe error\n");
22 
23         if ((pid = fork()) < 0) {
24                 printf("fork error\n");
25         } else if (pid > 0) {                                             /* parent */
26                 close(fd1[0]);
27                 close(fd2[1]);
28 
29                 while (fgets(line, MAXLINE, stdin) != NULL) {
30                         n = strlen(line);
31                         if (write(fd1[1], line, n) != n)
32                                 printf("write error to pipe\n");
33                         if ((n = read(fd2[0], line, MAXLINE)) < 0)
34                                 printf("read error from pipe\n");
35                         if (n == 0) {
36                                 printf("child closed pipe\n");
37                                 break;
38                         }
39                         line[n] = 0;    /* null terminate */
40                         if (fputs(line, stdout) == EOF)
41                                 printf("fputs error\n");
42                 }
43 
44                 if (ferror(stdin))
45                         printf("fgets error on stdin\n");
46                 exit(0);
47         } else {                                                          /* child */
48                 close(fd1[1]);
49                 close(fd2[0]);
50                 if (fd1[0] != STDIN_FILENO) {
51                         if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
52                                 printf("dup2 error to stdin\n");
53                         close(fd1[0]);
54                 }
55 
56                 if (fd2[1] != STDOUT_FILENO) {
57                         if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
58                                 printf("dup2 error to stdout\n");
59                         close(fd2[1]);
60                 }
61                 if (execl("./add2", "add2", (char *)0) < 0)
62                         printf("execl error\n");
63         }
64         exit(0);
65 }
66 
67 static void sig_pipe(int signo)
68 {
69         printf("SIGPIPE caught\n");
70         exit(1);
71 }
72 
73 [root@benxintuzi ipc]# ./coprocess2
74 100 50
75 150
76 100 500
77 600
View Code

 

FIFO

FIFO有時被稱爲命名管道,未命名的管道只能在兩個相關的進程之間使用,並且這兩個相關的進程還要有一個共同的祖先進程。可是,經過FIFO,不相關的進程之間也能交換數據。

使用以下函數建立FIFO:

#include <sys/stat.h>

int mkfifo(const char* path, mode_t mode);

int mkfifoat(int fd, const char* path, mode_t mode);

返回值:成功,返回0;失敗,返回-1

說明:

mkfifoat與mkfifo類似,像以前其餘*at系列函數同樣,有3種情形:

(1)   若是path參數指定了絕對路徑名,則fd被忽略,此時mkfifoat和mkfifo同樣。

(2)   若是path參數指定了相對路徑名,則fd參數是一個打開目錄的有效文件描述符,路徑名和目錄有關。

(3)   若是path參數指定了相對路徑名,而且fd參數指定了AT_FDCWD,則路徑名以當前目錄開始,mkfifoat和mkfifo相似。

當咱們使用mkfifo或者mkfifoat函數建立FIFO時,要用open打開,確是,正常的I/O函數(如close、read、write、unlink)都須要FIFO。當open一個FIFO時,非阻塞標誌(O_NONBLOCK)會產生以下影響:

(1)   沒有指定O_NONBLOCK時,只讀open要阻塞到某個其餘進程爲寫而打開這個FIFO爲止。相似地,只寫open要阻塞到某個其餘進程爲讀而打開這個FIFO爲止。

(2)   若是指定了P_NONBLOCK,則只讀open當即返回。可是,若是沒有進程爲讀而打開這個FIFO,那麼只寫open將返回-1,並將errno設置爲ENXIO。

一個給定的FIFO有多個寫進程是很常見的,這就意味着,若是不但願多個進程所寫的數據交叉,則必須考慮原子寫操做。和管道同樣,常量PIPE_BUF說明了可被原子地寫到FIFO的最大數據量。

 

FIFO主要有如下兩種用途:

(1)   shell命令使用FIFO將數據從一條管道傳送到另外一條管道,無需建立中間臨時文件。

實例:考慮這樣一個過程,他須要對一個輸入文件進行兩次處理,示意圖以下:

 

咱們可使用FIFO和tee命令以下處理:

mkfifo fifo1

prog3 < fifo1 &

prog1 < (輸入文件) | tee fifo1 | prog2

執行流程以下:

 

(2)   客戶進程-服務器進程應用程序中,FIFO用做匯聚點,在客戶進程和服務器進程之間傳遞數據。

實例:有一個服務器進程,它與不少客戶進程相關,每一個客戶進程均可將請求寫到一個該服務器進程建立的FIFO中。因爲該FIFO有多個寫進程,所以客戶進程每次發送給服務器的數據長度要小於PIPE_BUF字節,這樣就能避免客戶進程之間的寫交叉。

 

可是這種類型的FIFO設計有問題,服務器如何迴應各個客戶進程呢?一種解決方法是,每一個客戶進程都在其請求中包含它的進程ID,而後服務器進程爲每一個客戶進程建立一個FIFO,所使用的路徑名是以客戶進程的進程ID爲基礎的。例如,服務進程能夠用名字/tmp/serv1.XXXXX建立FIFO,其中XXXXX被替換成客戶進程的進程ID,以下圖所示:

 

相關文章
相關標籤/搜索