進程間通信(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函數。優點:可靠、流是受控的,面向記錄、可以用非先進先出方式處理。
