一.概述:
管道是程序間通信的手段之一,它實際上是一個存在于記憶體的特殊檔案,而這個檔案要通過兩個已經打開的檔案才能進行操作,這兩個檔案分别指向管道的兩端。
管道是通過在記憶體中開辟一個緩存區來實作程序間的通信的,這個緩存區的大小是固定的。在linux中,這個緩存區的大小為一頁,即4k。但固定的大小會帶來問題,當緩存區已經被write操作寫滿時,之後的write操作會阻塞,等待緩存區内的某些資料被讀取,以便騰出空間給寫操作調用。
在linux中,管道并沒有專門的資料結構,而是通過file結構和inode共同實作。兩個file結構指向一個inode,而這個inode對應了記憶體的某個區域。
<a href="http://s5.51cto.com/wyfs02/M02/7E/D5/wKioL1cKLCPwOnoaAAB-fvSi5wU217.png" target="_blank"></a>
注:一個file中的f_op隻對應寫或者讀操作中的一個。
而管道建立的緩存區是一個環形緩存區,即當用到緩存區的尾部時,下一個可用區間為緩存區的首部。緩存區一般采用的是數組形式,即申請的是一個線性的位址空間。而形成環狀用的是 模 緩存區長度。而通路這個緩存區的方式是一種典型的“生産者—消費者模型”,即當生産者有大量的資料要寫時,而緩存區的大小隻有1k,是以當緩存區被寫滿時,生産者必須等待,等待消費者讀取資料,以便騰出空間讓消費者寫。而當消費者發現緩存區内并沒有資料時,消費者必須等待,等待生産者往緩存區内寫資料來提供消費者讀資料。
那麼又如何判斷是“空”還是“滿”呢。當read和write指向環形緩存區的同一位置時為空或滿。為了差別空和滿,規定read和write重疊時為空,而當write比read快,追到距離read還有一個元素間隔時,就認為是滿。
摘:(不明覺厲)
并發通路
考慮到在不同環境下,任務可能對環形緩沖區的通路情況不同,需要對并發通路的情況進行分析。
在單任務環境下,隻存在一個讀任務和一個寫任務,隻要保證寫任務可以順利的完成将資料寫入,而讀任務可以及時的将資料讀出即可。如果有競争發生,可能會出現如下情況:
Case1:假如寫任務在“寫指針加1,指向下一個可寫空位置”執行完成時被打斷,此時寫指針write指向非法位置。當系統排程讀任 務執行時,如果讀任務需要讀多個資料,那麼不但應該讀出的資料被讀出,而且當讀指針被調整為0是,會将以前已經讀出的資料重複讀出。
Case2:假設讀任務進行讀操作,在“讀指針加1”執行完時被打斷,此時read所處的位置是非法的。當系統排程寫任務執行時,如果 寫任務要寫多個資料,那麼當寫指針指到尾部時,本來緩沖區應該為滿狀态,不能再寫,但是由于讀指針處于非法位置,在讀任務執行前,寫任務會任務緩沖區為 空,繼續進行寫操作,将覆寫還沒有來的及讀出的資料。
二.用管道實作程序間的通信:
代碼:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
//.....建立管道
int pipefd[2] ={-1,-1};
int ret = pipe(pipefd);
if(ret == -1)
{
perror("pipe");
return -1;
}
//....建立子程序
pid_t id = fork();
if(id < 0)
{
perror("fork");
else if(id == 0)
//....子程序關閉讀端
close(pipefd[0]);
char* buf = "i'm child";
int count = 5;
while(count--)
{
write(pipefd[1],buf,strlen(buf));
sleep(1);
}
else
//....父程序關閉寫端
close(pipefd[1]);
char buf[1024];
int count = 5;
while(count--)
{
memset(buf, '\0', sizeof(buf));
ssize_t size = read(pipefd[0],buf,sizeof(buf) - 1);
buf[size] = '\0';
printf("%s\n",buf);
}
}
}
結果截圖:
<a href="http://s5.51cto.com/wyfs02/M00/7E/D5/wKioL1cKSNjzpL_vAAAhTouD87o131.png" target="_blank"></a>
三.使用管道會出現的四種情況(假設都是阻塞I/O操作,沒有設定O_NONBLOCK标志):
1:當所有寫端都關閉時,但還有程序要從管道中讀取資料,那麼當管道中的資料讀完後,再次read時,就會傳回0,就像讀到檔案末尾一樣。
代碼如下:
//....子程序關閉寫端
if(size > 0)
{
buf[size] = '\0';
printf("%s\n",buf);
}
else
printf("read error");
//....父程序關閉讀端
char* buf = "i'm father";
int i = 0;
if(i == 3)
{
printf("i just want to sleep");
break;
}
i++;
執行結果:
<a href="http://s1.51cto.com/wyfs02/M01/7E/D6/wKioL1cKU07QXLwKAAAmTbW1SV0167.png" target="_blank"></a>
2.如果指向管道寫端的檔案辨別符沒關閉,但寫端也不向管道中寫資料時,如果此時有程序從管道中讀資料,那麼當緩存區内的資料被讀完之後,再次read,讀程序會進入阻塞狀态,直到緩存區内有資料才進行讀資料。
printf("i just want to sleep\n");
sleep(5);
<a href="http://s3.51cto.com/wyfs02/M01/7E/D9/wKiom1cKVLSRRYsfAAAoS7S6njQ365.png" target="_blank"></a>
3.如果所有指向管道的讀端都關閉了,但還有程序要往管道中寫資料,那麼程序就會收到信号SIGPIPE,通常會導緻程序異常終止。(因為此時再寫也沒用,又沒程序要讀)
//....子程序關閉讀端
close(pipefd[0]);
char buf[] = "i'm child";
write(pipefd[1],buf,strlen(buf));
sleep(1);
//....父程序關閉寫端
close(pipefd[1]);
char buf[1024];
{
close(pipefd[0]);
break;
}
i++;
int ret = 0;
memset(buf, '\0', sizeof(buf));
ret = read(pipefd[0],buf,sizeof(buf) - 1);
buf[ret] = '\0';
if(ret > 0)
{
printf("%s\n",buf);
fflush(stdout);
}
int status = 0;
pid_t pid = waitpid(id, &status, 0);
printf("pid: [%d] signal:[%d]\n",id,status|0xff);
fflush(stdout);
<a href="http://s1.51cto.com/wyfs02/M01/7E/DA/wKiom1cK-O-DrCSuAAA-zZpM7AM436.png" target="_blank"></a>
4.如果指向管道讀端的檔案辨別符沒有關閉,但又不讀緩存區内的内容,那麼如果此時還有程序要往緩存區内寫,而緩存區已被寫滿時,寫程序會阻塞,直到緩存區内有空位置之後才能寫。
printf("i just want to sleep\n");
fflush(stdout);
sleep(5);
ret = read(pipefd[0],buf,sizeof(buf) - 1);
if(ret > 0)
{
<a href="http://s1.51cto.com/wyfs02/M01/7E/DA/wKiom1cK-7bCytpqAABTg_uTl1I409.png" target="_blank"></a>
本文轉自 ye小灰灰 51CTO部落格,原文連結:http://blog.51cto.com/10704527/1762492,如需轉載請自行聯系原作者