文章模塊:
1.進程間通信的實質
2.管道
3.管道的特點
4.管道的四種情況
5.命名管道
6.匿名管道的代碼實現
7命名管道的代碼實現
一、進程間通信的實質
每個進程都有各自不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到。所以進程之間要交換數據必須要通過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間考到內核緩衝區,進程2載從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信。
如圖:
二、管道
管道是一種最基本的IPC機制,由pipe函數創建。
調用pipe函數,在內核中開闢一塊緩衝區(稱爲管道)用於通信,它有一個讀端和一個寫端,然後通過函數參數傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端。所以,管道在用戶程序看起來就像一個打開的文件。可通過read、write函數向文件讀寫數據。向這個文件讀寫數據其實是在讀寫內核緩衝區。pipe函數調用成功返回0,失敗返回-1。
pipe函數的調用過程:
1.父進程創建管道
2.創建一個子進程
3.父進程關閉讀端fd[0],子進程關閉寫端fd[1]
父進程調用pipe函數開闢管道,得到兩個文件描述符指向管道的兩端。
父進程調用fork創建一個子進程,子進程繼承父進程的文件描述符集,則子進程也有兩個文件描述符指向同一管道的兩端。
父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道里寫,子進程可以從管道里讀。管道是用環形隊列實現的,數據從寫端流入,從讀端流出。這樣就實現了進程間通信。
三、管道的特點
1.只能單向通信
2.匿名管道只適用於具有血緣關係的進程間通信
3.依賴於文件系統,生命週期隨進程
4.自帶同步功能,防止讀到垃圾數據,以互斥爲前提
5.基於無格式字節流
6.管道的緩衝區是有限的(管道存在於內存中,在管道創建時,爲緩衝區分配一個頁面大小)
下面解釋一下
爲什麼管道只能單向通信?
來看下Linux的實現,數據只能單向移動的意思是FIFO,於是Linux實際中構建了一個循環隊列。具體的則是,在內核申請一個緩衝區,作爲pipe()操作中匿名管道的實體,緩衝區設置兩個指針,一個讀指針,一個寫指針。並保證讀指針向前移動不能超過寫指針,否則喚醒寫進稱並讓讀指針睡眠,直到讀滿需要的字節數。同理,讀指針向前移動也不能超過寫指針,否則喚醒寫進程並讓讀進程睡眠,直到寫滿要求的字節數。說白了,管道在操作系統內部就是一個環形隊列。
pipe()返回的兩個文件句柄最後指向的其實是一個inode,只不過是一個read only一個是write only。試想同時有兩個進程讀[或者寫,假設只有兩個進程]的後果。由於i_count會等於2--如果小於2則說明兩個進程同時關閉了寫句柄,因此會退出讀函數。此時兩個進程會分別認爲對方纔是寫者而反覆醒來,反覆監測,然而沒有數據,於是反覆睡眠。如果有多個進程,兩進程同時讀[或者寫],會造成數據混亂,因爲讀指針只有一個,而你不能保證讀寫的順序。
所以,想要使用管道完成雙向通信,只需要創建兩個管道就可以了。
爲什麼管道只適用於有血緣關係的進程間通信?
因爲管道是通過文件描述符來控制數據讀寫的。所以只有血緣關係的進程纔會產生指向同一個管道的文件描述符。
管道容量是多少?
用命令 ulimit -a可以查看,----->512Bytes*8 = 4096Bytes
也可以用程序代碼驗證
四、管道的四種情況
1.讀端不讀了,但不關閉讀文件描述符,當管道寫滿時
如果指向管道讀端的文件描述符沒有關閉,(管道寫端的引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程從管道的寫端寫數據,那麼管道在被寫滿時再次write會阻塞,直到管道中有了空位置才寫入數據並返回。
2.寫端不寫了,但不關閉寫文件描述符,當管道中數據讀完
如果指向管道寫端的文件描述符沒有關閉,(管道寫端的引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會被阻塞,直到管道中有數據可讀了纔會讀取數據並返回。
3.寫端一直寫,讀端關閉讀文件描述符
如果所有指向管道讀端的文件描述符都關閉了,(管道寫端的引用計數等於0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。
4.讀端一直讀,寫端關閉寫文件描述符
如果所有指向管道寫端的文件描述符都關閉了,(管道寫端的引用計數等於0),而仍有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾一樣。
1、2體現了同步機制
五、命名管道 FIFO
可以用於兩個無血緣關係的進程間通信。命名管道和管道不通的在於它提供一個路徑名與之關聯,以FIFO的文件形式存儲於2文件系統中,命名管道是一個設備文件,因此,即使進程與創建命名管道的進程之間無任何血緣關係,只要可以訪問該路徑,就能夠通過命名管道相互通信。命名管道總是按照先進先出的原則工作。
六、代碼實現--------------匿名管道
mypipe
/*************************************************************************
> File Name: mypipe.c
> Author: ZX
> Mail: [email protected]
> Created Time: Sat 13 May 2017 06:49:35 PM PDT
************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe failed");
}
pid_t id = fork();
if(id == 0)
{
//child
close(fd[0]);//write -- close read
int i = 0;
char* msg = "hello world!";
while(i < 10)
{
ssize_t s = write(fd[1], msg, strlen(msg));
printf("i am writing! %d\n",i++);
fflush(stdout);
}
}
else
{
//father
// close(fd[1]);
// char buf[1024];
// while(1)
// {
// ssize_t s = read(fd[0], buf, sizeof(buf));
// if(s > 0)
// {
// buf[s] = 0;
// printf("father read: %s\n", buf);
// }
// if(s == 0)
// {
// printf("end of file!\n");
// }
// }
int status = 0;
waitpid(id, &status, 0);
}
return 0;
}
七、代碼實現--------命名管道
server
/*************************************************************************
> File Name: server.c
> Author: ZX
> Mail: [email protected]
> Created Time: Sat 13 May 2017 07:26:10 PM PDT
************************************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int ret = mkfifo("./mypipe", S_IFIFO | 0666);
if(ret < 0)
{
perror("mkfifo failed!");
return 1;
}
int fd = open("./mypipe", O_RDONLY);
if(fd < 0)
{
perror("open failed!");
return 2;
}
else
{
while(1)
{
char buf[1024];
memset(buf, '\0', sizeof(buf));
ssize_t s = read(fd, buf, sizeof(buf));
if(s > 0)
{
buf[s-1] = 0;
printf("server read:%s", buf);
}
else if(s == 0)
{
printf("client closed!server is closing!\n");
break;
}
}
}
return 0;
}
/*************************************************************************
> File Name: client.c
> Author: ZX
> Mail: [email protected]
> Created Time: Sat 13 May 2017 07:26:10 PM PDT
************************************************************************/
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("./mypipe", O_WRONLY);
if(fd < 0)
{
perror("open failed!");
return 1;
}
else
{
while(1)
{
printf("Please Enter: ");
fflush(stdout);
char msg[1024];
memset(msg, '\0', sizeof(msg)-1);
ssize_t s = read(0, msg, sizeof(msg));
write(fd, msg, sizeof(msg));
}
}
return 0;
}