管道可用於具有親緣關係進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係進程間的通信。
管道是Linux支持的最初Unix IPC形式之一,具有以下特點:
管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;
只能用於父子進程或者兄弟進程之間(具有親緣關係的進程);
單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。
數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。
管道兩端用描述字fd[0]以及fd[1]來描述,管道的兩端是固定了任務的。fd[0]只能用於讀,稱其爲管道讀端;另一端則只能用於寫,由描述字fd[1]來表示,稱其爲管道寫端。
從管道中讀取數據:
如果管道的寫端不存在,則認爲已經讀到了數據的末尾,讀函數返回的讀出字節數爲0;
當管道的寫端存在時,如果請求的字節數目大於PIPE_BUF,則返回管道中現有的數據字節數,如果請求的字節數目不大於PIPE_BUF,則返回管道中現有數據字節數(此時,管道中數據量小於請求的數據量);或者返回請求的字節數(此時,管道中數據量不小於請求的數據量)。
如果管道沒有數據,且管道的寫端口是打開狀態,則讀操作被阻塞直到有數據寫入爲止。
向管道中寫入數據:
對於設置了阻塞標誌的寫操作:
當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閒緩衝區不足以容納要寫入的字節數,則進入睡眠,直到當緩衝區中能夠容納要寫入的字節數時,纔開始進行一次性寫操作。
當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據,寫操作在寫完所有請求寫的數據後返回。
對於沒有設置阻塞標誌的寫操作:
當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閒緩衝區後,寫操作返回。
當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。如果當前FIFO空閒緩衝區能夠容納請求寫入的字節數,寫完後成功返回;如果當前FIFO空閒緩衝區不能夠容納請求寫入的字節數,則返回EAGAIN錯誤,提醒以後再寫;
管道的主要侷限性正體現在它的特點上:
只支持單向數據流;
只能用於具有親緣關係的進程之間;
沒有名字;
管道的緩衝區是有限的(管道制存在於內存中,在管道創建時,爲緩衝區分配一個頁面大小);
管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等;
有名管道以FIFO的文件形式存在於文件系統中。這樣,即使與FIFO的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char **argv)
{
int len;
pid_t pid;
int pfd[2];
char buffer[1024] = {0};
if (pipe(pfd) < 0) {
printf("pipe error\n");
exit(0);
}
if ((pid = fork()) < 0) {
printf("fork error\n");
exit(0);
}
if (pid == 0) { /* 子進程 */
write(pfd[1], "hello\n", 6);
close(pfd[1]);
sleep(2);
} else { /* 父進程 */
close(pfd[1]);
while ((len = read(pfd[0], buffer, 1023)) > 0) {
buffer[len] = '\0';
printf("len = %d, %s", len, buffer);
}
printf("read done\n");
wait(pid); /* 等待子進程退出才能看到效果 */
}
return 0;
}
1 只有管道寫端的引用計數變成0,纔會關閉管道得寫端.
2 進程結束後,自動關閉打開的文件
3 如果父進程先退出,而且沒有調用wait等待子進程, 那麼子進程就會變成僵死進程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char **argv)
{
int len;
pid_t pid;
int pfd[2];
char buffer[1024] = {0};
char *envp[] = {"PATH=/tmp", "QUERY_STRING=m=18&n=19", NULL};
if (pipe(pfd) < 0) {
printf("pipe error\n");
exit(0);
}
if ((pid = fork()) < 0) {
printf("fork error\n");
exit(0);
}
if (pid == 0) { /* 子進程 */
close(pfd[1]);
while ((len = read(pfd[0], buffer, 1023)) > 0) {
buffer[len] = '\0';
printf("len = %d, %s", len, buffer);
}
} else { /* 父進程 */
write(pfd[1], "hello\n", 6);
close(pfd[1]);
wait(pid);
}
return 0;
}
1 如果父進程不關閉管道的寫端, 即便子進程關閉了管道的寫端, 還是會阻塞在read上
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char **argv)
{
int len;
pid_t pid;
int pfd[2];
char buffer[1024] = {0};
if (pipe(pfd) < 0) {
printf("pipe error\n");
exit(0);
}
if ((pid = fork()) < 0) {
printf("fork error\n");
exit(0);
}
if (pid == 0) { /* 子進程 */
len = read(pfd[0], buffer, 10);
printf("## %s", buffer);
len = write(pfd[1], "from child hello\n", 17);
} else { /* 父進程 */
len = write(pfd[1], "from parent hello\n", 18);
sleep(1);
len = read(pfd[0], buffer, 20);
printf("@@ %s", buffer);
close(pfd[1]);
close(pfd[0]);
wait(pid);
}
return 0;
}
1 使用一個管道進行雙向通信是不行的, 會產生混亂, 要想使用管道實現雙向通信,必須創建兩個管道
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd1[2],fd2[2],cld_pid,status;
char buf[200], len;
if (pipe(fd1) == -1) {// 創建管道1
printf("creat pipe1 error\n");
exit(1);
}
if (pipe(fd2) == -1) {// 創建管道2
printf("creat pipe2 error\n");
exit(1);
}
if ((cld_pid=fork()) == 0) {//子進程
close(fd1[1]); // 子進程關閉管道1的寫入端
close(fd2[0]); // 子進程關閉管道1的讀出端
//子進程讀管道1
len = read(fd1[0],buf,sizeof(buf));
printf("%s",buf);
//子進程寫管道2
strcpy(buf,"hi, father, this is your son!\n");
write(fd2[1],buf,strlen(buf));
exit(0);
}
else {//父進程
close(fd1[0]); // 父進程關閉管道1的讀出端
close(fd2[1]); // 父進程關閉管道2的寫入端
//父進程寫管道1
strcpy(buf,"hey, son, I'm your father\n");
write(fd1[1],buf, strlen(buf));
//父進程讀管道2
len = read(fd2[0],buf,sizeof(buf));
printf("%s",buf);
exit(0);
}
}