四川建设安全监督管理局网站,环保网站怎么做,wordpress网址采集,旺道seoLinux——匿名管道 什么是管道匿名管道的底层原理观察匿名管道现象读写端的几种情况写端慢#xff0c;读端快写端快#xff0c;读端慢 管道的大小写端关闭#xff0c;读端一直读写端一直写#xff0c;读端关闭 我们之前一直用的是vim来编写代码#xff0c;现在有了vscode这… Linux——匿名管道 什么是管道匿名管道的底层原理观察匿名管道现象读写端的几种情况写端慢读端快写端快读端慢 管道的大小写端关闭读端一直读写端一直写读端关闭 我们之前一直用的是vim来编写代码现在有了vscode这样强大的编辑器我们可以把我们的vim放一边了如果还有小伙伴还没有配置好vscode的远端可以点击这里 https://blog.csdn.net/qq_67693066/article/details/136368891 我们今天进入管道的学习
什么是管道
在计算机领域管道Pipeline是一种将多个命令连接在一起以形成数据流的机制。它允许一个命令的输出成为另一个命令的输入从而实现命令之间的数据传递和处理。
在 Unix/Linux 系统中管道通常用竖线符号 | 表示。通过管道可以将一个命令的输出传递给另一个命令进行处理从而构建复杂的数据处理流程。
例如假设我们有两个命令 command1 和 command2我们可以使用管道将它们连接起来
command1 | command2这将会把 command1 的输出作为 command2 的输入command2 将处理 command1 的输出并生成最终的结果。
管道的优势包括 简化复杂任务 管道可以将多个简单的命令组合成一个复杂的任务使得任务的实现更加简单和高效。 模块化和可重用性 通过将命令连接在一起可以更好地组织代码并提高代码的可重用性。每个命令都可以专注于完成一个特定的任务。 减少临时文件 管道可以避免将数据存储到临时文件中从而减少了文件 I/O 的开销和磁盘空间的占用。 实时处理 管道允许命令之间的实时数据传递这对于需要连续处理数据的任务非常有用比如日志处理、数据流分析等。 简单来说管道就是连接多个指令。我们之前也在频繁使用管道比如我们想统计当前登录到系统的用户数量。 who指令的结果作为wc -l的输入。
匿名管道的底层原理
我们这里讲的简单一点现在我们有一个进程它自身会被以读和写的方式分别打开一次 然后这个读和写都会往一个缓冲区输入输出数据
这个时候父进程创建子进程子进程发生浅拷贝指向没有发生变化 这里注意一下管道一般是单向的所以我们现在想让父进程读让子进程写 这样形成了一个单向通道这个就是一个基本的匿名管道。 匿名管道Anonymous Pipe是一种用于进程间通信的机制特别是在 Unix 和类 Unix 系统中。它允许一个进程将输出发送到另一个进程的输入从而实现进程间的数据传输。 以下是匿名管道的一些关键特点 单向通信匿名管道是单向的只能支持单向数据流。它只能用于单一方向的通信通常是父进程到子进程或者相反。 创建匿名管道通过调用系统调用 pipe() 来创建。这个系统调用创建了一个管道返回两个文件描述符其中一个用于读取管道另一个用于写入管道。 父子进程通信通常匿名管道用于父子进程之间的通信。在创建子进程后父进程可以将数据写入管道而子进程则可以从管道中读取这些数据。 半双工匿名管道是半双工的意味着数据只能在一个方向上流动。如果需要双向通信则需要创建两个管道或者使用其他的进程间通信机制比如命名管道或套接字。 进程同步匿名管道通常用于进程间的同步和协作。一个进程可能会阻塞在读取管道上直到另一个进程写入数据到管道中为止。 匿名管道在 Unix 系统中被广泛应用特别是在 shell 编程和进程间通信方面。它提供了一种简单而有效的方式允许不同进程之间进行数据交换和协作 我也有专门创建管道的函数pipe 我们可以来试一下
#includeiostream
#includeunistd.h
#includecassert
using namespace std;int main()
{int pipefd[2] {0};int n pipe(pipefd);assert(n 0);coutpipefd[0]---pipefd[0]pipefd[1]---pipefd[1]endl;return 0;
}运行 这里我们发现pipefd[0]指代的是3而我们的pipefd[1]指代的是4。其实也很好理解因为012被标准输入标准输出标准错误占了。所以从3开始。
同时如果我么查手册会看到这样一段话 这段话的主要意思是pipefd[0]是读端而pipefd[1]是写端。这为我们以后哪个开哪个关提供了依据。
观察匿名管道现象
我们先搭建架子来观察我们匿名管道的现象
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
using namespace std;int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);}if(id 0){//子进程要做的事exit(0);}//父进程要做的事//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
}
现在我们想让子进程写父进程读我们把相应用不到的管道关闭
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
using namespace std;int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
}
我们让子进程写入一些东西然后让父进程来读看看行不行
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
#includestdio.h
#includestring.h
using namespace std;
#define MAX 1024int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道//向管道写入int cnt 10;while(cnt){//缓冲区char message[MAX];//向缓冲区里写snprintf(message,sizeof(message),hello father I am child my pid:%d cnt:%d , getpid(),cnt);cnt--;//向管道写write(pipefd[1],message,strlen(message));sleep(1);}exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//从管道中读取数据char buffer[MAX];while(true){ssize_t n read(pipefd[0],buffer,sizeof(buffer));if(n 0){cout getpid() , chid say : buffer to me endl;}}//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
} 我们看到父进程真的拿到了子进程写的东西这就是一个最基本的管道的应用。
读写端的几种情况
写端慢读端快
我们模拟一下写端慢读端快的情况
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
#includestdio.h
#includestring.h
using namespace std;
#define MAX 1024int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道//向管道写入int cnt 10;while(cnt){//缓冲区char message[MAX];//向缓冲区里写snprintf(message,sizeof(message),hello father I am child my pid:%d cnt:%d , getpid(),cnt);cnt--;//向管道写write(pipefd[1],message,strlen(message));sleep(100); //模拟写端慢}exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//从管道中读取数据char buffer[MAX];while(true){ssize_t n read(pipefd[0],buffer,sizeof(buffer));if(n 0){cout getpid() , chid say : buffer to me endl;}}//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
} 我们发现父进程处于一个休眠的状态很明显它是在等待我们的子进程进行写入。
这里我们可以得出匿名管道具有同步机制读端和写端是协同工作的。
写端快读端慢
我们调换一下让写端快读端快
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
#includestdio.h
#includestring.h
using namespace std;
#define MAX 1024int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道//向管道写入int cnt 10000;while(cnt){//缓冲区char message[MAX];//向缓冲区里写snprintf(message,sizeof(message),hello father I am child my pid:%d cnt:%d , getpid(),cnt);cnt--;//向管道写write(pipefd[1],message,strlen(message));coutwriting......endl;}exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//从管道中读取数据char buffer[MAX];while(true){sleep(2); //睡眠2秒ssize_t n read(pipefd[0],buffer,sizeof(buffer));if(n 0){cout getpid() , chid say : buffer to me endl;}}//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
}
执行 过了2秒之后 数据一瞬间出来了。
这里我们可以得出匿名管道是面向字节流的它没有硬性规定我写一条你必须马上读一条而是以字节流的形式读或写。
管道的大小
我们可以写一段代码来测试我们管道的大小
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
#includestdio.h
#includestring.h
using namespace std;
#define MAX 1024int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道//向管道写入int cnt 0;while(1){// //缓冲区// char message[MAX];// //向缓冲区里写// snprintf(message,sizeof(message),hello father I am child my pid:%d cnt:%d , getpid(),cnt);// cnt--;// //向管道写// write(pipefd[1],message,strlen(message));// coutwriting......endl;char c a;write(pipefd[1], c, 1);cnt;cout write ....: cnt endl;}exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//从管道中读取数据char buffer[MAX];while(true){// sleep(2); //睡眠2秒// ssize_t n read(pipefd[0],buffer,sizeof(buffer));// if(n 0)// {// cout getpid() , chid say : buffer to me endl;// }}//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
} 我们发现最后结果是65536折合下来也就是64kb左右的大小。
我们也可以用指令来查看管道大小ulimit -a
我们查看的管道大小为512 * 8 4kb好像比我们看到的小。这个其实不是真正的大小。
写端关闭读端一直读
我们现在让写段写一段时间后直接关闭但是读端没有关闭
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
#includestdio.h
#includestring.h
using namespace std;
#define MAX 1024int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道//向管道写入int cnt 0;while(1){//缓冲区char message[MAX];//向缓冲区里写snprintf(message,sizeof(message),hello father I am child my pid:%d cnt:%d , getpid(),cnt);cnt;//向管道写write(pipefd[1],message,strlen(message));//跳出if(cnt 3) break;// char c a;// write(pipefd[1], c, 1);// cnt;// cout write ....: cnt endl;}//关闭写端close(pipefd[1]);exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//从管道中读取数据char buffer[MAX];while(true){//sleep(2); //睡眠2秒ssize_t n read(pipefd[0],buffer,sizeof(buffer));if(n 0){cout getpid() , chid say : buffer to me endl;}coutfather return value: n endl;sleep(1);}//回收子进程pid_t rid waitpid(id,nullptr,0);if(rid id){coutwait successendl;}return 0;
} 这样表示写端关闭读端一直读取, 读端会读到read返回值为0 表示读到文件结尾。
同时注意进程退出管道自动关闭。
写端一直写读端关闭
#includeiostream
#includeunistd.h
#includecassert
#includesys/types.h
#includesys/wait.h
#includestdio.h
#includestring.h
using namespace std;
#define MAX 1024int main()
{//建立管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);//创建子进程pid_t id fork();//子进程if(id 0){perror(fork fail);return 1;}if(id 0){//子进程要做的事close(pipefd[0]); //关闭读的通道//向管道写入int cnt 0;while(true){//缓冲区char message[MAX];//向缓冲区里写snprintf(message,sizeof(message),hello father I am child my pid:%d cnt:%d , getpid(),cnt);cnt;//向管道写write(pipefd[1],message,strlen(message));sleep(1);//跳出//if(cnt 3) break;// char c a;// write(pipefd[1], c, 1);// cnt;// cout write ....: cnt endl;sleep(1);}//关闭写端//close(pipefd[1]);exit(0);}//父进程要做的事close(pipefd[1]); //关闭写的通道//从管道中读取数据char buffer[MAX];while(true){//sleep(2); //睡眠2秒ssize_t n read(pipefd[0],buffer,sizeof(buffer));if(n 0){cout getpid() , chid say : buffer to me endl;}coutfather return value: n endl;sleep(1);//直接跳出break;}//关闭读端close(pipefd[0]);sleep(5);int status 0;pid_t rid waitpid(id, status, 0);if (rid id){cout wait success, child exit sig: (status0x7F) endl;}// //回收子进程// pid_t rid waitpid(id,nullptr,0);// if(rid id)// {// coutwait successendl;// }return 0;
}
我们得到一下它的信号 我们查一下13号信号 13号信号是SIGPIPE SIGPIPE 是在进程向一个已经被关闭的管道或者其他的类似的通信方式写入数据时内核向该进程发送的信号。这个信号的默认行为是终止进程。 常见的场景是一个进程向另一个进程通过管道发送数据但接收数据的进程提前退出导致写入数据的进程尝试往已关闭的管道写入数据。在这种情况下内核会发送 SIGPIPE 信号给写入数据的进程通知它目标进程已经退出不再接收数据。 所以我们才有上述现象。
总结一下管道有4种情况 管道的4种情况 正常情况如果管道没有数据了读端必须等待直到有数据为止(写端写入数据了)正常情况如果管道被写满了写端必须等待直到有空间为止(读端读走数据)写端关闭读端一直读取, 读端会读到read返回值为0 表示读到文件结尾读端关闭写端一直写入OS会直接杀掉写端进程通过想目标进程发送SIGPIPE(13)信号终止目标进程 5种特性 管道的5种特性 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信常用与父子,仅限于此匿名管道默认给读写端要提供同步机制 — 了解现象就行面向字节流的 — 了解现象就行管道的生命周期是随进程的管道是单向通信的半双工通信的一种特殊情况