管道的读写行为

使用管道需要注意以下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.mzph.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/ …