在談管道之前我們先了解一下什麼是進程間通信。
每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程間要交換數據必須通過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷貝到內核,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信(IPC,InterProcess Communication)。如下圖所示
簡單說進程間通信的本質是讓不同進程看到一份共同的資源,這份共同資源一般由操作系統提供。
一、匿名管道(pipe)
管道是一種最基本的IPC機制,由pipe函數創建:
#include<unistd.h>
#int pipe<int filedes[2]>;
調用pipe函數時在內核中開闢一塊緩衝區(稱爲管道)用於通信,它有一個讀端一個寫端,然後通過filedes參數傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端(就像0是標準輸入,1是標準輸出)。所以管道在用戶程序看起來就像一個打開的文件,通過read(filedes[0];或者write(filedes[1]);向這個文件讀寫數據其實是在讀寫內核緩衝區。pipe函數調用成功返回0,調用失敗返回-1。
開闢了管道之後的兩個進程間的通信步驟如下:
1、父進程調用pipe開闢管道,得到兩個文件描述符指向管道的兩端。
2、父進程調用fork創建子進程,那麼子進程也有兩個文件描述符指向同一管道。
3、父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道里讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。
使用管道的一些限制:
1、兩個進程通過一個管道只能實現單向通信,但也可以創建雙管道實現雙向通信。
2、管道只能實現有血緣關係的進程間通信。
3、管道依賴文件系統,它的生命週期隨進程,即進程退出,管道不存在。
4、管道的數據更新基於字節流。
5、管道內部完成同步機制,保證數據的一致性。
使用管道需要注意以下4種特殊情況:
1、如果所有指向管道寫端的文件描述符都關閉了(管道的引用計數等於0),而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣。代碼如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int _pipe[2];
9 int ret=pipe(_pipe);
10 if(ret==-1)
11 {
12 printf("create pipe error!errno code is:%d\n",errno);
13 return 1;
14
15 }
16 pid_t id=fork();
17 if(id<0)
18 {
19 printf("fork error!");
20 return 2;
21 }
22 else if(id==0){//child
23 close(_pipe[0]);
24 int i=0;
25 char* _mesg_c=NULL;
26 while(i<10){
27 _mesg_c="i an child";
28 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
29 sleep(1);
30 i++;
31 }
32 close(_pipe[1]);
33 }else{//father
34 close(_pipe[1]);
35 char _mesg[100];
36 int j=0;
37 while(j<100)
38 {
39 memset(_mesg,'\0',sizeof(_mesg));
40 int ret=read(_pipe[0],_mesg,sizeof(_mesg));
41 printf("%s:code is%d\n",_mesg,ret);
42 j++;
43 }
44 if(waitpid(id,NULL,0)<0)
45 {
46 return 3;
47 }
48 }
49 return 0;
50 }
2、如果指向管道寫端的文件描述符沒關閉(管道寫端的引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時進程從數據從管道讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀纔讀取數據並返回。代碼如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int _pipe[2];
9 int ret=pipe(_pipe);
10 if(ret==-1)
11 {
12 printf("create pipe error!errno code is:%d\n",errno);
13 return 1;
14
15 }
16 pid_t id=fork();
17 if(id<0)
18 {
19 printf("fork error!");
20 return 2;
21 }
22 else if(id==0){//child
23 close(_pipe[0]);
24 int i=0;
25 char* _mesg_c=NULL;
26 while(i<20){
27 if(i<10){
28 _mesg_c="i an child";
29 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
30 }
31 sleep(1);
32 i++;
33 }
34 close(_pipe[1]);
35 }else{//father
36 close(_pipe[1]);
37 char _mesg[100];
38 int j=0;
39 while(j<20)
40 {
41 memset(_mesg,'\0',sizeof(_mesg));
42 int ret=read(_pipe[0],_mesg,sizeof(_mesg));
43 printf("%s:code is%d\n",_mesg,ret);
44 j++;
45 }
46 if(waitpid(id,NULL,0)<0)
47 {
48 return 3;
49 }
50 }
51 return 0;
52 }
3、如果所有指向管道讀端的文件描述符都關閉了(管道讀端的引用計數等於0),這時有進程管道的寫端write,那麼進程會收到信號SIGPIPE,通常會導致進程異常終止。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int _pipe[2];
9 int ret=pipe(_pipe);
10 if(ret==-1)
11 {
12 printf("create pipe error!errno code is:%d\n",errno);
13 return 1;
14 }
15 pid_t id=fork();
16 if(id<0)
17 {
18 printf("fork error!");
19 return 2;
20 }
21 else if(id==0){//child
22 close(_pipe[0]);
23 int i=0;
24 char* _mesg_c=NULL;
25 while(i<20){
26 if(i<10){
27 _mesg_c="i an child";
28 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
29 }
30 sleep(1);
31 i++;
32 }
33 }else{//father
34 close(_pipe[1]);
35 char _mesg[100];
36 int j=0;
37 while(j<3)
38 {
39 memset(_mesg,'\0',sizeof(_mesg));
40 int ret=read(_pipe[0],_mesg,sizeof(_mesg));
41 printf("%s:code is%d\n",_mesg,ret);
42 j++;
43 }
44 close(_pipe[0]);
45 sleep(10);
46 if(waitpid(id,NULL,0)<0)
47 {
48 return 3;
49 }
50 }
51 return 0;
52 }
4、如果有指向管道讀端的文件描述符沒關閉(管道讀端的引用計數大於0),而持有管道讀端的進程也沒有從管道中數據,這時有管道寫端寫數據,那麼在管道被·寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int _pipe[2];
9 int ret=pipe(_pipe);
10 if(ret==-1)
11 {
12 printf("create pipe error!errno code is:%d\n",errno);
13 return 1;
14 }
15 pid_t id=fork();
16 if(id<0)
17 {
18 printf("fork error!");
19 return 2;
20 }
21 else if(id==0){//child
22 close(_pipe[0]);
23 int i=0;
24 char* _mesg_c=NULL;
25 while(1){
26 _mesg_c="i an child";
27 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
28 i++;
29 printf("%d\n",i);
30 }
31 }else{//father
32 close(_pipe[1]);
33 sleep(1);
34 if(waitpid(id,NULL,0)<0)
35 {
36 return 3;
37 }
38 }
39 return 0;
40 }
二、命名管道(FIFO)
概念
管道的一個不足之處就是沒有名字,因此,只能用於具有血緣關係的進程間通信,在命名管道提出後,該限制得到了克服,命名管道不同於管道之處在於它提供一個路徑名與之關聯,以命名管道的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建命名管道的進程不存在血緣關係,只要可以訪問該路徑,就能通過命名管道互相通信。需注意:命名管道總是按照先進先出的原則工作,第一個被寫入的數據將從管道中讀出。
命名管道的創建與讀寫
Linux下有兩種方式創建命名管道,一是在shell下交互地建立一個命名管道,二是在程序中使用系統函數建立命名管道。shell方式下可使用mknod或mkfifo命令,下面使用mknod創建了一個命名管道:
mknod namedpipe
創建命名管道的系統函數有兩個:mknod和mkfifio。兩函數均定義在頭文件sys/stat.h,函數原型如下:
#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char* path,mode_t,dev_t dev);
int mkfifo(const char* path,mode_t mode);
函數mknod參數中path爲創建的命名管道的全路徑名:mod爲創建的命名管道的模式u,指明其存取權限:dev爲設備值,該值取決於文件創建的種類,它只在創建設備文件時纔會用到。這兩個函數調用成功都返回0,失敗都返回-1。下面使用mknod函數創建了一個命名管道:
umask(0);
if(mknod("/tmp/fifo",S_IFIFO|0666)==-1)
{
perror("mkfifo error");
exit(1);
}
函數mkfifo前兩個參數的含義和mknod相同。下面是使用mkfifo的示例代碼:
umask(0);
if(mkfifo("/tmp/fifo",S_IFIF0|0666)==-1)
{
perror("mkfifo error!");
exit(1);
}
"S_IFIFO|0666"指明創建一個命名管道且存取權限爲0666,即創建者、與創建者同組的用戶、其他用戶對命名管道的訪問權限是可讀可寫的。
命名管道創建後就可以使用了,命名管道和管道的使用方法基本相同。只是使用命名管道時,必須先調用open()將其打開。因爲命名管道是一個存在與硬盤上的文件,而管道是存在於內存中的特殊文件。
需注意:調用open()打開命名管道的進程可能會阻塞。但如果同時用讀寫方式(O_RDWR)打開,則一定不會導致阻塞;如果以只讀方式(O_PDONLY)打開,則調用open()函數的進程將會被阻塞直到有寫方式打開管道;同樣以寫方式(O_WRONLY)打開也會阻塞直到有讀寫方式打開管道。
總結
文件系統中的路徑名是全局的,各進程都可以訪問,因此可以用文件系統中的路徑名來標識一個IPC通道。
在Linux下一切皆文件,所以對命名管道的使用可以向平常的文件名一樣在命令中使用。
創建命名管道的函數原型如下:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* filename,mode_t mode);
int mknod(const char* filename,mode_t mode|S_IFIFO,(dev_t)0);
這兩個函數都能創建一個FIFO文件,mkfifo函數的作用是在文件系統中創建一個文件,該文件用於提供FIFO功能,即命名管道。前面講的那些管道沒有名字,因此它們被稱爲(匿名)管道。對文件系統來說,匿名管道只能實現有血緣關係的進程間通信。而命名管道是一個可見文件,因此,他可以用於任何兩個進程間的通信。
fifo read端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100
int main()
{
int fd=open(_PATH_,O_PDONLY);
if(fd<0){
printf("open file error!\n");
return 1;
}
char but[_SIZE_];
memset(buf,'\0',sizeof(buf));
while(1){
int ret=read(fd,buf,sizeof(buf));
if(ret<=0)//error or end of file
{
printf("read end or error!\n");
break;
}
printf("%s\n",buf);
if(strncmp(buf,"quit",4)==0){
break;
}
}
close(fd);
return 0;
}
fifo write端
#include<stdio.h>
#include<sys/types.h>
#include<sys/stst.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100
int main()
{
int ret=mkfifo(_PATH_,0666|S_IFIFO);
if(ret==-1){
printf("mkfifo error\n");
return 1;
}
int fd=open(_PATH_,O_WRONLY);
if(if<0){
printf("open error\n");
}
char buf[_SIZE_];
memset(but,'\0',sizeof(buf));
while(1){
scanf("%s",buf);
int ret=write(fd,buf,strlen(buf)+1);
if(ret<0){
printf("write error\n");
break;
}
if(strncmp(but,"quit",4)==0){
break;
}
}
close(fd);
return 0;
}