您好,登錄后才能下訂單哦!
一.概述:
管道是進程間通信的手段之一,它實際上是一個存在于內存的特殊文件,而這個文件要通過兩個已經打開的文件才能進行操作,這兩個文件分別指向管道的兩端。
管道是通過在內存中開辟一個緩存區來實現進程間的通信的,這個緩存區的大小是固定的。在linux中,這個緩存區的大小為一頁,即4k。但固定的大小會帶來問題,當緩存區已經被write操作寫滿時,之后的write操作會阻塞,等待緩存區內的某些數據被讀取,以便騰出空間給寫操作調用。
在linux中,管道并沒有專門的數據結構,而是通過file結構和inode共同實現。兩個file結構指向一個inode,而這個inode對應了內存的某個區域。
注:一個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");
return -1;
}
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);
}
}
}
結果截圖:
三.使用管道會出現的四種情況(假設都是阻塞I/O操作,沒有設置O_NONBLOCK標志):
1:當所有寫端都關閉時,但還有進程要從管道中讀取數據,那么當管道中的數據讀完后,再次read時,就會返回0,就像讀到文件末尾一樣。
代碼如下:
#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");
return -1;
}
else if(id == 0)
{
//....子進程關閉寫端
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);
if(size > 0)
{
buf[size] = '\0';
printf("%s\n",buf);
}
else
{
printf("read error");
}
}
}
else
{
//....父進程關閉讀端
close(pipefd[0]);
char* buf = "i'm father";
int count = 5;
int i = 0;
while(count--)
{
if(i == 3)
{
printf("i just want to sleep");
break;
}
i++;
write(pipefd[1],buf,strlen(buf));
sleep(1);
}
}
}
執行結果:
2.如果指向管道寫端的文件標識符沒關閉,但寫端也不向管道中寫數據時,如果此時有進程從管道中讀數據,那么當緩存區內的數據被讀完之后,再次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");
return -1;
}
else if(id == 0)
{
//....子進程關閉寫端
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);
if(size > 0)
{
buf[size] = '\0';
printf("%s\n",buf);
}
}
}
else
{
//....父進程關閉讀端
close(pipefd[0]);
char* buf = "i'm father";
int count = 5;
int i = 0;
while(count--)
{
if(i == 3)
{
printf("i just want to sleep\n");
sleep(5);
}
i++;
write(pipefd[1],buf,strlen(buf));
sleep(1);
}
}
}
執行結果:
3.如果所有指向管道的讀端都關閉了,但還有進程要往管道中寫數據,那么進程就會收到信號SIGPIPE,通常會導致進程異常終止。(因為此時再寫也沒用,又沒進程要讀)
代碼如下:
#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");
return -1;
}
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;
int i = 0;
while(count--)
{
if(i == 3)
{
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);
}
}
執行結果:
4.如果指向管道讀端的文件標識符沒有關閉,但又不讀緩存區內的內容,那么如果此時還有進程要往緩存區內寫,而緩存區已被寫滿時,寫進程會阻塞,直到緩存區內有空位置之后才能寫。
代碼如下:
#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");
return -1;
}
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;
int i = 0;
while(count--)
{
if(i == 3)
{
printf("i just want to sleep\n");
fflush(stdout);
sleep(5);
}
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);
}
}
執行結果:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。