管道的讀寫行為

使用管道需要注意以下4種特殊情況(默認都是阻塞I/O操作,沒有設置O_NONBLOCK標志):

1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端引用計數為0),而仍然有進程從管道的讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣。

2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端引用計數大于0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據并返回。

3. 如果所有指向管道讀端的文件描述符都關閉了(管道讀端引用計數為0),這時有進程向管道的寫端write,那么該進程會收到信號SIGPIPE,通常會導致進程異常終止。當然也可以對SIGPIPE信號實施捕捉,不終止進程。具體方法信號章節詳細介紹。

4. 如果有指向管道讀端的文件描述符沒關閉(管道讀端引用計數大于0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那么在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據并返回。

總結:

讀管道:1.管道中有數據,read返回實際讀到的字節數。2.管道中無數據:管道寫端被全部關閉,read返回0(好像讀到文件結尾);寫端沒有全部被關閉,read阻塞等待(不久的將來可能有數據遞達,此時會讓出cpu)。

寫管道:1.管道讀端全部被關閉,進程異常終止(也可使用捕捉SIGPIPE信號,使進程不終止)。2. 管道讀端沒有全部關閉:管道已滿,write阻塞;管道未滿,write將數據寫入,并返回實際寫入的字節數。

重點注意:

如果寫入的數據大小n<=PIPE_BUF時,linux保證寫入的原子性,即要么不寫,要么全寫入。如果沒有足夠的空間供n個字節全部寫入,則會阻塞直到有足夠空間供n個字節全部寫入;如果寫入的數據大小n>PIPE_BUF時,寫入不再具有原子性,可能中間有其它進程穿插寫入,其自身也會阻塞,直到將n字節全部寫入在才返回寫入的字節數,否則阻塞等待。

讀數據時,如果請求讀取的數據(read函數的緩沖區)大小>=PIPE_BUF,則直接返回管道中現有的數據字節數(即將管道中的數據全部讀出);如果< PIPE_BUF,則返回管道中現有的數據字節數(此時管道中的實際數據量<=請求的數據量大小),或者返回請求數據量的大小。

練習1:父子進程使用管道通信,父寫入字符串,子進程讀出并打印到屏幕。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main(void)
{int ret,fd1;char *p="zhangshuxiong\n";int fd[2];ret = pipe(fd);if(ret == -1){perror("pipe");exit(1);}fd1 = fork( );if(fd1 == -1){perror("fork");exit(1);}else if(fd1 == 0) {sleep(3);   //子進程睡3秒close(fd[1]);  //子進程關閉寫端char buff[1024]={0};ret =  read(fd[0],buff,1024);  //子進程讀數據if(ret == -1){perror("read");exit(1);}else if(ret == 0) {printf("父進程沒有向管道里寫入數據\n");}else {int res= write(STDOUT_FILENO,buff,ret);  //將讀出的數據輸出到屏幕if(res == -1){perror("write");exit(1);}}close(fd[0]);  //子進程結束前關閉掉文件描述符}else {close(fd[0]);int rer = write(fd[1],p,strlen(p));  //父進程寫入數據if(rer == -1){perror("write");exit(1);}close(fd[1]);  //父進程結束前關閉掉文件描述符wait( NULL );  //父進程回收(阻塞等待)}return 0;
}

[root@localhost pipe]# ./pip

zhangshuxiong

[root@localhost pipe]#???? //可見,如果沒有wait,則父進程會先結束,正因為有了wait,父進程會等待子進程結束,最后shell進程才會收回前臺,等待與用戶交互。注意,即使沒有sleep函數,依然能保證子進程運行時一定會讀到數據,因為是阻塞讀。

?

練習2:使用管道實現父子進程間通信,完成:ls | wc –l。假定父進程實現ls,子進程實現wc

[root@localhost pipe]# ls

makefile? pip? pip.c? pipe? pipe1? pipe1.c? pipe2? pipe2.c? pipe3? pipe3.c? pipe.c? pipe_test? pipe_test.c? test

[root@localhost pipe]# ls | wc –l? ?//統計文件的字數

14

其實 ls | wc –l命令執行后,shell進程會創建兩個子進程,并創建一個管道,用于兩子進程通信,下面給出詳細實現過程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(void)
{int ret,fd1;int fd[2];ret = pipe(fd);if(ret == -1){perror("pipe");exit(1);}fd1 = fork( );if(fd1 == -1){perror("fork");exit(1);}else if(fd1 == 0) {close(fd[1]);int as = dup2(fd[0],STDIN_FILENO);  //將標準輸入重定向到管道讀端if(as == -1){perror("dup2");exit(1);}close(fd[0]);  //只是關了fd[0],不關也可以,進程結束會自動關閉execlp("wc","wc","-l",NULL);  //該命令從標準輸入讀取文本}else {close(fd[0]);int as = dup2(fd[1],STDOUT_FILENO);  //將標準輸出重定向到管道寫端if(as == -1){perror("dup2");exit(1);}execlp("ls","ls",NULL);  ///該命令結果會寫到標準輸出}return 0;
}

[root@localhost pipe]# ./pip

14???????????????????? ?//可見,跟ls | wc –l的結果一樣

注意,上述程序并沒有考慮到子進程的回收問題,如果父進程比子進程先結束,子進程會被init進程回收;后結束,子進程會先變為僵尸進程,等父進程結束了,再被init進程回收。

ls命令正常會將結果集寫出到stdout,但現在會寫入管道的寫端;wc –l 正常應該從stdin讀取數據,但此時會從管道的讀端讀。

也有可能會出現這種情況:程序執行,發現程序執行結束,shell還在阻塞等待用戶輸入。這是因為,shell → fork → ./pipe1, 程序pipe1的子進程將stdin重定向給管道,父進程執行的ls會將結果集通過管道寫給子進程。若父進程在子進程打印wc的結果到屏幕之前被shell調用wait回收,shell就會先輸出$提示符。

?

練習3使用管道實現兄弟進程間通信。 兄:ls? 弟: wc -l? 父:等待回收子進程。要求,使用“循環創建N個子進程”模型創建兄弟進程,使用循環因子i標示。注意管道讀寫行為。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>int main(void)
{int i,ret,fd1;int n=2;int fd[2];ret = pipe(fd);if(ret == -1){perror("pipe");exit(1);}for(i=0;i<n;i++){fd1 = fork( );if(fd1 == -1){perror("fork");exit(1);}else if(fd1 == 0)break;}if(i == n){close(fd[0]);close(fd[1]);  //特別強調,父進程不用管道,必須要關掉,否則運行出錯(為了維護管道的單向通信)int status;do {pid_t pid=waitpid(-1,&status,0);if(pid > 0)n--;if(pid == -1){perror("waitpid");exit(1);}if(WIFEXITED(status))printf("the child process of exit with %d\n",WEXITSTATUS(status));else if(WIFSIGNALED(status))printf("the child process was killed by %dth signal\n",WTERMSIG(status));}while(n>0);}else if(i == 1) {close(fd[1]);int as = dup2(fd[0],STDIN_FILENO);if(as == -1){perror("dup2");exit(1);}close(fd[0]);execlp("wc","wc","-l",NULL);}else {close(fd[0]);int as = dup2(fd[1],STDOUT_FILENO);if(as == -1){perror("dup2");exit(1);}execlp("ls","ls",NULL);}return 0;
}

[root@localhost pipe]# ./pip

14

the child process of exit with 0

the child process of exit with 0

強調一點:在使用管道傳遞數據之前,不用的管道讀或寫端都必須要關閉,這是為了維護管道的正常運行(單向通信)。

?

測試:是否允許,一個pipe有一個寫端,多個讀端呢?是否允許有一個讀端多個寫端呢?

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>int main(void)
{pid_t pid;int fd[2], i, n;char buf[1024];int ret = pipe(fd);if(ret == -1){perror("pipe error");exit(1);}for(i = 0; i < 2; i++){if((pid = fork()) == 0)break;else if(pid == -1){perror("pipe error");exit(1);}}if (i == 0) {close(fd[0]);write(fd[1], "1.hello\n", strlen("1.hello\n"));} else if(i == 1) {close(fd[0]);write(fd[1], "2.world\n", strlen("2.world\n"));} else {close(fd[1]);       //父進程關閉寫端,留讀端讀取數據    //sleep(1);   //這條語句是很關鍵的n = read(fd[0], buf, 1024);     //從管道中讀數據write(STDOUT_FILENO, buf, n);for(i = 0; i < 2; i++)          //兩個兒子wait兩次wait(NULL);}return 0;
}

如果父進程不睡眠:

[root@localhost pipe]# ./pipe3

2.world

1.hello

[root@localhost pipe]# ./pipe3

1.hello

[root@localhost pipe]# ./pipe3

2.world

可見:三個進程的執行順序是隨機的,如果兩個子進程在父進程讀之前,都先寫入,那么兩個都會讀出。為了確保兩個都讀出,可以使用讀兩次的方法,也可以讓父進程先睡眠一會,如下:

如果父進程睡眠:

[root@localhost pipe]# ./pipe3

1.hello

2.world

[root@localhost pipe]# ./pipe3

1.hello

2.world

?

最終練習:統計當前系統中進程ID大于10000的進程個數。

提示: 采用awk命令,可以統計文本中符合條件列的個數及和。運用ps aux和管道。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385350.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C++ Primer | 08】課后習題答案

文章目錄練習8.13練習8.13 include <iostream> #include <sstream> #include <fstream> #include <string> #include <vector> using namespace std;struct PersonInfo {string name;vector<string> phones; };bool valid(const string&…

管道緩沖區大小

可以使用ulimit –a 命令來查看當前系統中創建管道文件所對應的內核緩沖區大小。通常為&#xff1a;pipe size 4K&#xff0c;即一個頁面大小。也可以使用fpathconf函數來查看&#xff1a; #include <unistd.h> long fpathconf(int fd, int name); 當需要查看管道的大…

FIFO(命名管道)

FIFO常被稱為命名管道&#xff0c;以區分管道(pipe)。管道(pipe)只能用于“有血緣關系”的進程間。但通過FIFO&#xff0c;不相關的進程也能交換數據。FIFO是Linux基礎文件類型中的一種&#xff08;p,管道文件&#xff09;。但FIFO文件在磁盤上沒有數據塊&#xff0c;僅僅用來標…

文件進程間通信

使用文件也可以完成IPC&#xff0c;理論依據是&#xff0c;fork后&#xff0c;父子進程共享文件描述符。也就共享打開的文件。 //父子進程共享打開的文件。借助文件進行進程間通信&#xff08;可先打開文件&#xff0c;再創建子進程&#xff09; #include <unistd.h> #…

mmap內存映射、system V共享內存和Posix共享內存

linux內核支持多種共享內存方式&#xff0c;如mmap內存映射&#xff0c;Posix共享內存&#xff0c;以system V共享內存。當內核空間和用戶空間存在大量數據交互時&#xff0c;共享內存映射就成了這種情況下的不二選擇。它能夠最大限度的降低內核空間和用戶空間之間的數據拷貝&a…

mmap、munmap函數

#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); int munmap(void *addr, size_t length); void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); 返回&#xff1a;成功&…

mmap和munmap對文件進行操作(讀寫等)

//mmap、munmap函數的使用 #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h>void sys_err(char *str) {perror(str);exit(1); }…

1017. A除以B (20)

本題要求計算A/B&#xff0c;其中A是不超過1000位的正整數&#xff0c;B是1位正整數。你需要輸出商數Q和余數R&#xff0c;使得A B * Q R成立。 輸入格式&#xff1a; 輸入在1行中依次給出A和B&#xff0c;中間以1空格分隔。 輸出格式&#xff1a; 在1行中依次輸出Q和R&#…

mmap父子進程間通信

父子等有血緣關系的進程之間也可以通過mmap建立的映射區來完成數據通信。但相應的要在創建映射區的時候指定對應的標志位參數flags&#xff1a;MAP_PRIVATE&#xff1a;&#xff08;私有映射&#xff09;父子進程各自獨占映射區&#xff1b;MAP_SHARED&#xff1a;&#xff08;…

匿名映射

通過使用我們發現&#xff0c;使用映射區來完成文件讀寫操作十分方便&#xff0c;父子進程間通信也較容易。但缺陷是&#xff0c;每次創建映射區一定要依賴一個文件才能實現。通常為了建立映射區要open一個temp文件&#xff0c;創建好了再unlink、close掉&#xff0c;比較麻煩。…

mmap無血緣關系進程間通信

實質上mmap是內核借助文件幫我們創建了一個映射區&#xff0c;多個進程之間利用該映射區完成數據傳遞。由于內核空間多進程共享&#xff0c;因此無血緣關系的進程間也可以使用mmap來完成通信。只要設置相應的標志位參數flags即可。若想實現共享&#xff0c;當然應該使用MAP_SHA…

【C++ Primer | 13】課后習題答案

文章目錄13.1.4節目練習13.2節練習13.2.2練習13.1.4節目練習 練習13.14 #include <iostream> using namespace std;class numbered { private: static int seq; public:numbered() { mysn seq; }int mysn; };int numbered::seq 0;void f(numbered s) { cout <…

信號的概念與機制

信號的共性&#xff1a;1. 簡單&#xff08;開銷小&#xff0c;且在用或者不用的情況下&#xff0c;開銷是一樣的&#xff09;&#xff1b;2. 不能攜帶大量信息&#xff08;如程序執行過程中&#xff0c;出現段錯誤時&#xff0c; 就會發送一個相關的信號&#xff08;編號為11&…

信號的產生和狀態

信號的產生&#xff1a;1.按鍵產生&#xff0c;如&#xff1a;Ctrlc&#xff08;內核向進程發送信號&#xff0c;殺死該進程&#xff09;、Ctrlz、Ctrl\&#xff1b;2.系統調用產生&#xff0c;如&#xff1a;kill、raise、abort&#xff1b;3.軟件條件產生&#xff0c;如&…

【C++ Priemr | 15】虛函數常見問題

1. 在成員函數中調用虛函數&#xff1a; #include <iostream> using namespace std; class CBase { public:void func1(){func2();}virtual void func2() { cout << "CBase::func2()" << endl; } }; class CDerived : public CBase { public:virt…

965. 單值二叉樹

如果二叉樹每個節點都具有相同的值&#xff0c;那么該二叉樹就是單值二叉樹。 只有給定的樹是單值二叉樹時&#xff0c;才返回 true&#xff1b;否則返回 false。 示例 1&#xff1a; 輸入&#xff1a;[1,1,1,1,1,null,1] 輸出&#xff1a;true示例 2&#xff1a; 輸入&#…

信號四要素

與變量三要素&#xff08;類型、名字、值&#xff09;類似的&#xff0c;每個信號也有其必備4要素&#xff0c;分別是&#xff1a;1.編號&#xff1b;2.名稱&#xff08;即編號的宏定義&#xff09; &#xff1b;3.事件&#xff08;引起信號產生的事件&#xff0c;如段錯誤&…

958. 二叉樹的完全性檢驗

給定一個二叉樹&#xff0c;確定它是否是一個完全二叉樹。 百度百科中對完全二叉樹的定義如下&#xff1a; 若設二叉樹的深度為 h&#xff0c;除第 h 層外&#xff0c;其它各層 (1&#xff5e;h-1) 的結點數都達到最大個數&#xff0c;第 h 層所有的結點都連續集中在最左邊&a…

信號的產生

&#xff08;1&#xff09;終端按鍵產生信號&#xff08;與終端交互的進程&#xff09; Ctrl c → 2) SIGINT&#xff08;終止/中斷&#xff09; "INT" ----Interrupt Ctrl z → 20) SIGTSTP&#xff08;暫停/停止&#xff09; "T" ----Termin…

897. 遞增順序查找樹

給定一個樹&#xff0c;按中序遍歷重新排列樹&#xff0c;使樹中最左邊的結點現在是樹的根&#xff0c;并且每個結點沒有左子結點&#xff0c;只有一個右子結點。 示例 &#xff1a; 輸入&#xff1a;[5,3,6,2,4,null,8,1,null,null,null,7,9]5/ \3 6/ \ \2 4 8/ …