1. 程式人生 > >Unix環境高級編程(十六)進程間通信

Unix環境高級編程(十六)進程間通信

一起 每次 map 通信 一次 rep span 9.png 打開

  進程間通信(IPC)是指能在兩個進程間進行數據交換的機制。現代OS都對進程有保護機制,因此兩個進程不能直接交換數據,必須通過一定機制來完成。
  IPC的機制的作用:
  (1)一個軟件也能更容易跟第三方軟件或內核進行配合的集成,或移植.如管道,在shell 下執行 ps –aux | grep bash。
  (2)簡化軟件結構, 可以把一個軟件劃分多個進程或線程,通過IPC,集成在一起工作.如消息隊列。
  (3)讓操作系統各個模塊交換數據,包括內核與應用程序機制。
  (4)提供進程之間或同一進程之間多線程的同步機制,如信號量。

1、管道

  管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道 只能用於父子進程或者兄弟進程之間(具有親緣關系的進程)單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。

  數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩沖區的末尾,並且每次都是從緩沖區的頭部讀出數據。
  管道的創建:int pipe(int fd[2]) ;
  管道的讀寫:管道文件也是一種文件,用write,read 即可完成讀寫。管道兩端可分別用描述字fd[0]以及fd[1]來描述,需要註意的是,管道的兩端是固定了任務的。即一端只能用於讀,由描述字fd[0]表示,稱其為管道讀端;另一端則只能用於寫,由描述字fd[1]來表示,稱其為管道寫端。如果試圖從管道寫端讀取數據,或者向管道讀端寫入數據都將導致錯誤發生。
  管道的關閉:管道文件也是一種文件,因此用close關閉即可。
  管道的局限:(1)只支持單向數據流; (2)只能用於具有親緣關系的進程之間; (3)沒有名字; (4)管道的緩沖區是有限的(管道制存在於內存中,在管道創建時,為緩沖區分配一個頁面大小); (5)管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等。

現在使用管道實現進程的同步,父進程讀取子進程輸入的數據、子進程讀取父進程恢復的數據。實現TELL_WAIT、TELL_PARENT、TELL_CHILD、TELL_PARENT及WAIT_CHILD函數。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 #include <sys/types.h>
 6 
 7 static int fd1[2],fd2[2];
 8 
 9 void TELL_WAIT()
10 {
11     pipe(fd1);
12     pipe(fd2);
13 }
14 
15 void TELL_PARENT(pid_t pid)
16 {
17     write(fd2[1],"c",1);
18 }
19 void WAIT_PARENT(void)
20 {
21     char c;
22     read(fd1[0],&c,1);
23     if(c!=‘p‘)
24     {
25         printf("WAIT_PARENT: Incorretc data");
26         exit(0);
27     }
28     else
29       printf("Read from parent.\n");
30 }
31 void TELL_CHILD(pid_t pid)
32 {
33     write(fd1[1],"p",1);
34 }
35 void WAIT_CHILD()
36 {
37     char c;
38     read(fd2[0],&c,1);
39     if(c!=‘c‘)
40     {
41         printf("WAIT_CHILD: Incorretc data");
42         exit(0);
43     }
44     else
45         printf("Read from child.\n");
46 }
47 
48 int main()
49 {
50     pid_t pid;
51     TELL_WAIT();
52     pid =fork();
53     if(pid == -1)
54     {
55         perror("fork() error");
56         exit(-1);
57     }
58     if(pid == 0)
59     {
60         printf("child process exec.\n");
61         WAIT_PARENT();
62         TELL_PARENT(getppid());
63     }
64     else
65     {
66         printf("Parent process exec.\n");
67         TELL_CHILD(pid);
68         WAIT_CHILD();
69 
70     }
71     exit(0);
72 }

程序執行結果如下:

技術分享圖片

popen和pclose函數

  常見的操作時創建一個管道連接到另外一個進程,然後讀取其輸出或向其輸入端發送數據。popen和pcolse函數實現的操作是:創建一個管道,調用fork產生一個子進程,關閉管道的不使用端,執行一個shell以運行命令,然後等待命令終止。函數原型如下:

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

函數popen先執行fork,然後調用exec執行cmdstring,並且返回一個標準I/O文件指針。如果type是“r”,則文件指針連接到cmdstring的標準輸出,如果type是“w”,則文件指針連接到cmdstring的標準輸入。popen特別適用於構造簡單的過濾程序,它變換運行命令的輸入或輸出。寫一個程序,將標準輸入復制到標準輸出,復制的時候將所有的大寫字母變換為小寫字母,程序分為兩部分,轉換程序如下:

 1 #include <stdio.h>
 2 #include <ctype.h>
 3 #include <stdlib.h>
 4 int main()
 5 {
 6     int c;
 7     while((c = getchar()) != EOF)
 8     {
 9         if(isupper(c))
10             c= tolower(c);
11         if(putchar(c) == EOF)
12             printf("output error");
13         if(c==‘\n‘)
14             fflush(stdout);
15     }
16     exit(0);
17 }

將可執行文件保存為change。輸入輸出程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 #include <errno.h>
 6 
 7 #define MAXLINE 1024
 8 
 9 int main()
10 {
11     char    line[MAXLINE];
12     FILE    *fpin;
13     if((fpin = popen(".//change","r")) == NULL)
14     {
15         perror("popen() error");
16         exit(-1);
17     }
18     for(; ;)
19     {
20         fputs("prompt> ",stdout);
21         fflush(stdout);
22         if(fgets(line,MAXLINE,fpin) == NULL)
23             break;
24         if(fputs(line,stdout) == EOF)
25         {
26             perror("fputs error to pipe");
27         }
28     }
29     if(pclose(fpin) == -1)
30     {
31         perror("pclose() error");
32         exit(-1);
33     }
34     putchar(‘\n‘);
35     exit(0);
36 }

程序執行結果如下

技術分享圖片

  協同進程:當一個進程產生某個過濾程序的輸入,同時又讀取該過濾程序的輸出。popen只提供鏈接到另一個進程的標準輸入或標準輸出的一個單向管道,對於協同進程,則連接到另一個進程的兩個單向管道,一個接到標準輸入,一個接標準輸出。寫個程序展示一下協同進程,程序從標準輸入讀入兩個整數,調用程序計算它們的和,然後將結果輸出到標準輸出。過濾程序即求和程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <fcntl.h>
 5 #include <unistd.h>
 6 #define MAXLINE 1024
 7 
 8 int main()
 9 {
10     int n,int1,int2;
11     char line[MAXLINE];
12     while((n=read(STDIN_FILENO,line,MAXLINE)) > 0)
13     {
14         line[n] = ‘\0‘;
15         if(sscanf(line,"%d%d",&int1,&int2) == 2)
16         {
17             sprintf(line,"%d\n",int1+int2);
18             n = strlen(line);
19             if(write(STDOUT_FILENO,line,n) != n)
20             {
21                 perror("write() error");
22                 exit(-1);
23             }
24         }
25         else if(write(STDOUT_FILENO,"invalid arg\n",13) != 13)
26         {
27             perror("write() error");
28             exit(-1);
29         }
30     }
31     exit(0);
32 }

編譯執行保存為可執行文件為add。

協同程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 #include <signal.h>
 6 #include <string.h>
 7 #define MAXLINE 1024
 8 
 9 static void sig_pipe(int);
10 
11 int main()
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     {
19         perror("signal() error");
20         exit(-1);
21     }
22     if(pipe(fd1) == -1||pipe(fd2) == -1)
23     {
24         perror("pipe() error");
25         exit(-1);
26     }
27     if((pid =fork()) == -1)
28     {
29         perror("fork() error");
30         exit(-1);
31     }
32     if(pid == 0)
33     {
34         close(fd1[1]);
35         close(fd2[0]);
36         if(fd1[0] != STDIN_FILENO)
37             if(dup2(fd1[0],STDIN_FILENO) != STDIN_FILENO)
38             {
39                 perror("dup2 error in stdin");
40                 close(fd1[0]);
41                 exit(-1);
42             };
43         if(fd2[1] != STDOUT_FILENO)
44             if(dup2(fd2[1],STDOUT_FILENO) != STDOUT_FILENO)
45             {
46                 perror("dup2 error in stdout");
47                 close(fd2[1]);
48                 exit(-1);
49             };
50         if(execl(".//add","add",(char *)0) == -1)
51         {
52             perror("execl() error");
53             exit(-1);
54         }
55     }
56     else
57     {
58         close(fd1[0]);
59         close(fd2[1]);
60         printf("Enter two number: ");
61         while(fgets(line,MAXLINE,stdin) != NULL)
62         {
63             n = strlen(line);
64             if(write(fd1[1],line,n) != n)
65             {
66                 perror("write errot to pipe");
67                 exit(-1);
68             }
69             if((n=read(fd2[0],line,MAXLINE)) ==-1)
70             {
71                 perror("read error to pipe");
72                 exit(-1);
73             }
74             if(n== 0)
75             {
76                 printf("child close pipe.\n");
77                 break;
78             }
79             line[n] = ‘\0‘;
80             printf("The result is: ");
81             fputs(line,stdout);
82         }
83     }
84 }
85 
86 static void sig_pipe(int signo)
87 {
88     printf("SIGPIPE caught\n");
89     exit(1);
90 }

程序執行結果如下:

技術分享圖片

2、FIFO

  FIFO不同於管道之處在於它提供一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中。這樣,即使與FIFO的創建進程不存在親緣關系的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信。FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操作。

命名管道的命名管道創建:int mkfifo(const char * pathname, mode_t mode) 。
命名管道的打開:命名管道比管道多了一個打開操作:open ,在open時,用O_NONBLOCK 標誌表示非阻塞模式,如fd=open(“/tmp/fifo”,O_RDONLY|O_NONBLOCK,0)。
命名管道的讀入:read 讀取管道數據,讀取分為阻塞和非阻塞模式,阻塞模式下,如果沒有數據被入,進程會在read處停下來.直到有新數據被寫入,或管道被關閉,才會繼續。
命名管道的寫入:write 寫入管道數據,PIPE_BUF表示一次觸發管道讀操作最大長度.如果每次寫入數據長於PIPE_BUF ,write將會多次觸發read 操作。
命名管道的關閉:管道文件也是一種文件,因此用close關閉即可。

FIFO的兩種用途:

(1)FIFO有shell命令使用以便將數據從一條管道線傳送到另一條,為此無需創建中間臨時文件。

(2)FIFO用於客戶進程—服務器進程應用程序中,以在客戶進程和服務器進程之間傳遞數據。

3、XSI IPC

 消息隊列、信號量、共享存儲區相似的特征如下:具有標識符和鍵,標識符是IPC對象的內部名,每個IPC對象都與一個鍵相關聯,創建IPC結構需要指定一個鍵,鍵的數據類型為key_t。每個IPC都設置了權限結構。

  優點及缺點:IPC結構是在系統範圍內起作用,沒有訪問計數。在文件系統中沒有名字,不使用文件描述符,不能對它們使用多路轉接I/O函數。優點:可靠、流是受控的,面向記錄、可以用非先進先出方式處理。

  消息隊列(Messge Queue):消息隊列是消息的鏈接表,包括Posix消息隊列SystemV消息隊列。它克服了前兩種通信方式中信息量有限的缺點,具有寫權限的進程可以按照一定的規則向消息隊列中添加新消息;對消息隊列有讀權限的進程則可以從消息隊列中讀取消息。   信號量(Semaphore):主要作為進程之間以及同一進程的不同線程之間的同步和互斥手段。   共享內存(Shared memory):可以說這是最有用的進程間通信方式。它使得多個進程可以訪問同一塊內存空間,不同進程可以及時看到對方進程中對共享內存中數據的更新。這種通信方式需要依靠某種同步機制,如互斥鎖和信號量等。 建議:要學會使用管道和FIFO,因為大量應用程序中仍可以有效地使用這兩種技術,在新的應用程序中,要盡可能避免使用消息隊列及信號量,應當考慮全雙工管道和記錄鎖。

Unix環境高級編程(十六)進程間通信