IPC(InterProcess Communication)進程間通信
每個進程各?自有不同的?用戶地址空間,任何?一個進程的全局變量在另?一個進程中都看不到所以進 程之間要交換數據必須通過內核,在內核中開辟?一塊緩沖區,進程1把數據從?用戶空間拷到內核緩 沖區,進程2再從內核緩沖區把數據讀?走,內核提供的這種機制稱為進程間通信。
linux下進程間通信的幾種主要?手段簡介:
1 管道(Pipe)及有名管道(named pipe):管道可?用於具有親緣關系進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關系進程間的通信;
2 信號(Signal):信號是比較復雜的通信?方式,用於通知接受進程有某種事件發?生,除了用於進程間通信外,進程還可以發送信號給進程本?身;linux除了?支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標准的信號函數sigaction(實際上,該函數是基於BSD的,BSD為了實現可靠信號機制,又能夠統一對外接口,?用sigaction函數重新實現了signal函數);
3 報?文(Message)隊列(消息隊列):消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區?大?小受限等缺點。
4 共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,如信號量結合使?用,來達到進程間的同步及互斥。
5 信號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步手段。
6 套接口(Socket):更為一般的進程間通信機制,可用於不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。
本篇文章先來學習下管道個命名管道。
管道是?一種最基本的IPC機制,由pipe函數創建:
#include
int pipe(int filedes[2]);
調?用pipe函數時在內核中開辟一塊緩沖區(稱為管道)用於通信,它有一個讀端一個寫端,然後通過filedes參數傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端(很好記,就像0是標准輸?入1是標准輸出一樣)。所以管道在用戶程序看起來就像一個打開的文件,通過read(filedes[0]);或者write(filedes[1]);向這個文件讀寫數據其實是在讀寫內核緩沖區。pipe函數調用成功返回0,調用失敗返回-1。
我們來演示下Pipe的通信功能,首先
1. 父進程調?用pipe開辟管道,得到兩個文件描述符指向管道的兩端。
2. 父進程調?用fork創建?子進程,那麼子進程也有兩個文件描述符指向同一管道。
3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道裡寫,子進程可以從管道裡讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。
代碼如下:
#include
2 #include
3 #include
4 #include
5
6 int main()
7 {
8 int _pipe[2];
9 int ret=pipe(_pipe);
10 if(ret==-1)//
11 {
12 printf("creat 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 {
23 close(_pipe[0]);
24 int i=0;
25 char* _mesg_c=NULL;
26 while(i<100)
27 {
28 _mesg_c="i im coder";
29 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
30 sleep(1);
31 i++;
32 }
33 }
34 else{//father
35 close(_pipe[1]);
36 char _mesg[100];
37 int j=0;
38 while(j<100)
39 {
40 memset(_mesg,'\0',sizeof(_mesg));
41 read(_pipe[0],_mesg,sizeof(_mesg));
42 printf("%s\n",_mesg);
43 j++;
44 }
45 }
46
47 return 0;
48 }
運行結果如下:
使?用管道有一些限制:
兩個進程通過一個管道只能實現單向通信。比如上面的例子,父進程寫子進程讀,如果有時候也需要子進程寫父進程讀,就必須另開一個管道。
管道的讀寫端通過打開的?文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那?裡繼承管道?文件描述符。上?面的例子是父進程把?文件描述符傳給子進程之後父子進程之間通信,也可以父進程fork兩次,把?文件描述符傳給兩個子進程,然後兩個子進程之間通信, 總之需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。 也就是說,管道通信是需要進程之間有關系。
使?用管道需要注意以下4種特殊情況(假設都是阻塞I/O操作,沒有設置O_NONBLOCK標志):
1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端的引?用計數等於0),而仍然有進程 從管道的讀端讀數據,那麼管道中剩余的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣。
代碼和上面的代碼類似,只是修改子進程中寫入的循環條件,部分代碼如下:
else if(id==0)//child
22 {
23 close(_pipe[0]);
24 int i=0;
25 char* _mesg_c=NULL;
26 while(i<10)
27 {
28 _mesg_c="i im coder";
29 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
30 sleep(1);
31 i++;
32 }
close(_pipe[1]);
33 }
運行結果如下:
2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端的引用計數?大於0),而持有管道寫端的 進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩余的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。
3. 如果所有指向管道讀端的文件描述符都關閉了(管道讀端的引?用計數等於0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。
#include
2 #include
3 #include
4 #include
5 #include
6 void fun()
7 {
8 printf("zhongzhi\n");
9 }
10 int main()
11 {
12 signal(13,fun);
13 int _pipe[2];
14 int ret = pipe(_pipe);
15 if(ret == -1){
16 printf("create pipe error! errno code is : %d\n",errno);
17 return 1;
18 }
19 pid_t id = fork();
20 if( id < 0 ){
21 printf("fork error!");
22 return 2;
23 }else if( id == 0 ){ //child
24 close(_pipe[0]);
25 int i =0;
26 char *_mesg_c=NULL;
27 while(i<20){
28 if( i < 10 ){
29 _mesg_c="i am child!";
30 write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
31 }
32 sleep(1);
33 i++;
34 }
35 }else{ //father
36 close(_pipe[1]);
37 char _mesg[100];
38 int j = 0;
39 while(j<3){
40 memset(_mesg, '\0', sizeof(_mesg));
41 int ret = read(_pipe[0], _mesg, sizeof(_mesg));
42 printf("%s : code is : %d\n",_mesg, ret);
43 j++;
44 }
45 close(_pipe[0]);
46 sleep(10);
47 if ( waitpid(id, NULL, 0)< 0)
48 {
49 return 3;
50 }
51 }
52 return 0;
53 }
運行結果如下:
vcHLv8u3/qGjRklGT7K7zazT2rnctcDWrrSm1NrT2sv8zOG5qdK7uPbCt762w/vT69auudjBqqOs0tRGSUZPtcTOxLz+0M7KvbTmtKLT2s7EvP7Ptc2z1tCho8P8w/u53LXAysfSu7j2yeixuM7EvP6jrNLytMujrLy0yrm9+LPM0+u0tL2oRklGT7XEvfizzLK7tObU2sfX1LW52M+1o6zWu9Kqv8nS1LfDzsq4w8K3vrajrL7NxNy5u82ouf1GSUZPz+C7pc2o0MWho9a1tcPXotLitcTKx6OsRklGTyhmaXJzdCBpbnB1dCBmaXJzdCBvdXRwdXQp19zKx7C01dXPyL34z8iz9rXE1K3U8rmk1/ejrLXa0ru49rG70LTI67XEyv2+3b2rP8rXz8i007nctcDW0LbBs/ahozxiciAvPg0KMiDD/MP7udy1wLXEtLS9qNPrtsHQtDxiciAvPg0KTGludXjPwtPQwb3W1re9yr20tL2ow/zD+7nctcCho9K7ysfU2lNoZWxsz8K9u7ultdi9qMGi0ru49sP8w/u53LXAo6y2/srH1NqzzNDy1tDKuT/Tw8+1zbO6r8r9vag/waLD/MP7udy1wKGjU2hlbGy3vcq9z8K/ycq508Nta25vZLvybWtmaWZvw/zB7qOsz8LD5sP8we7KudPDbWtub2S0tL2owcvSu7j2w/zD+7nctcCjum1rbm9kIG5hbWVkcGlwZbS0vajD/MP7udy1wLXEz7XNs7qvyv3T0MG9uPajum1rbm9kus1ta2ZpZm+ho8G9uPa6r8r9vvm2qNLl1NrNtz/OxLz+c3lzL3N0YXQuaKOsPGJyIC8+DQq6r8r91K3Qzcjnz8KjujwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
#include
函數mknod參數中path為創建的命名管道的全路徑名:mod為創建的命名管道的模式,指明其存取權限;dev為設備值,該值取決於文件創建的種類,它只在創建設備文件時才會?用到。這兩個函數調用成功都返回0,失敗都返回-1。下面使用mknod函數創建了一個命名管道:
umask(0);
if (mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
perror("mkfifo error");
exit(1);
}
umask(0);
if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
{
perror("mkfifo error!");
exit(1);
}
“S_IFIFO|0666”指明創建?一個命名管道且存取權限為0666,即創建者、與創建者同組的?用戶、其他用戶對該命名管道的訪問權限都是可讀可寫( 這?裡要注意umask對生成的管道?文件權限的影響 )。命名管道創建後就可以使用了,命名管道和管道的使用方法基本是相同的。只是使?用命名管道時,必須先調用open()將其打開。因為命名管道是一個存在於硬盤上的?文件,而管道是存在於內存中的特殊?文件。 需要注意的是,調用open()打開命名管道的進程可能會被阻塞。但如果同時用讀寫方式(O_RDWR)打開,則?一定不會導致阻塞;如果以只讀方式(O_RDONLY)打開,則調?用open()函數的進程將會被阻塞直到有寫方打開管道;同樣以寫?方式(O_WRONLY)打開也會阻塞直到有讀?方式打開管道。
3、總結
mkfifo函數的作?用是在?文件系統中創建?一個?文件,該?文件?用於提供FIFO功能,即命名管道。
前邊講的那些管道都沒有名字,因此它們被稱為匿名管道,或簡稱管道。對?文件系統來說,
匿名管道是不可見的,它的作?用僅限於在?父進程和?子進程兩個進程間進?行通信。?而命名管
道是?一個可見的?文件,因此,它可以?用於任何兩個進程之間的通信,不管這兩個進程是不
是?父?子進程,也不管這兩個進程之間有沒有關系。
下面我們通過程序演示下FIFO的功能
FIFO server端:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8 #define _PATH_ "./file.tmp"
9 #define _SIZE_ 100
10
11 int main()
12 {
13 int ret=mkfifo(_PATH_,0666|S_IFIFO);
14 printf("%d\n",ret);
15 if(ret==-1)
16 {
17 printf("mkfifo error\n");
18 return 1;
19 }
20 int fd=open(_PATH_,O_WRONLY);
21 if(fd<0)
22 {
23 printf("open file error!\n");
24 return 1;
25 }
26 char buf[_SIZE_];
27 memset(buf,'\0',sizeof(buf));
28 while(1)
29 {
30 scanf("%s",buf);
31 int ret=write(fd,buf,sizeof(buf));
32 if(ret<=0)
33 {
34 printf("raed end or error!\n");
35 break;
36 }
37 printf("%s\n",buf);
38 if(strncmp(buf,"quit",4)==0)
39 {
40 break;
41 }
42 }
43 close(fd);
44 return 0;
45 }
FIFO client端:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8 #define _PATH_ "./file.tmp"
9 #define _SIZE_ 100
10
11 int main()
12 {
13 int fd=open(_PATH_,O_RDONLY);
14 if(fd<0)
15 {
16 printf("open file error!\n");
17 return 1;
18 }
19 char buf[_SIZE_];
20 memset(buf,'\0',sizeof(buf));
21 while(1)
22 {
23 int ret=read(fd,buf,sizeof(buf));
24 if(ret<=0)
25 {
26 printf("raed end or error!\n");
27 break;
28 }
29 printf("%s\n",buf);
30 if(strncmp(buf,"quit",4)==0)
31 {
32 break;
33 }
34 }
35 close(fd);
36 return 0;
37 }
運行結果如下: