当前位置: 首页 > news >正文

Linux进程间通信(二)之管道1【匿名管道】

文章目录

    • 管道
      • 什么是管道
      • 匿名管道
        • 用fork来共享管道原理
          • 站在文件描述符角度-深度理解管道
          • 站在内核角度-管道本质
        • 接口
        • 实例代码
        • 管道特点
        • 管道的4种情况
        • 管道读写规则
        • 应用场景

管道

什么是管道

管道是Unix中最古老的进程间通信的形式。

我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

image-20250408220824153

| 就是管道 wc 统计有几行

image-20250408221952170

匿名管道

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

image-20250408220856534

用fork来共享管道原理

image-20250407152119049

image-20250408231241349

站在文件描述符角度-深度理解管道

父进程把文件打开两次(分别以r/w方式打开)

image-20250407152150066

站在内核角度-管道本质

image-20250407152203174

image-20250408233732261

所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件”思想。

如果两个进程没有关系就不能使用以上的原理进行通信!!!!

进程间必须是父子关系、兄弟关系、爷孙关系……!!

具有血缘关系的进程才能用上面原理进行通信。(常用于父子)

以上的工作是建立通信信道,还没有进行通信!

– 为什么建立信道的过程这么费劲?因为进程具有独立性,进程间通信是有成本的!

接口

open 打开的是磁盘级文件

pipe 打开的是内存级文件,以读/写方式把内存级文件打开。

image-20250408235123867

返回值

image-20250408235241954

参数

pipefd -> 是一个只有两个int型元素的数组
pipefd -> 还是一个输出型参数!
分别以读/写方式打开的文件的文件描述符数字带出来,让用户使用!
不考虑其他,默认情况下,是3和4

image-20250408235335781

pipefd[0] : 读下标
pipefd[1] : 写下标
实例代码

创建文件

image-20250409131205995

makefile

testpipe:testpipe.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf testpipe

验证 pipefd 的两个参数为3和4:

testpipe.cc

#include <iostream>
#include <unistd.h>
using namespace std;#define N 2
int main()
{int pipefd[N]={0};int n=pipe(pipefd);if(n<0)return 1;cout<<"pipefd[0]="<<pipefd[0]<<" , pipefd[1]="<<pipefd[1]<<endl;return 0;
}

image-20250409131748871

c语言的接口:安全格式化的接口

把按照格式的内容(size大小的)写到字符串里。

image-20250409140225598

testpipe.cc

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <string.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
#define N 2
#define NUM 1024//child
void Writer(int wfd)
{string s="hello,I am child";pid_t id=getpid();int number=0;char buffer[NUM];while(true){buffer[0]=0;//字符串清空,只是为了提醒阅读代码的人,把这个数组当祖字符串了。snprintf(buffer,strlen(buffer),"%s-%d-%d",s.c_str(),id,number++);write(wfd,buffer,strlen(buffer));//不需要+1,管道是文件!c语言的规定与文件无关!只需要把内容写入!sleep(1);}
}//father
void Reader(int rfd)
{char buffer[NUM];while(true){buffer[0]=0;ssize_t n=(rfd,buffer,sizeof(buffer));//sizeof!=strlen//sizeof代表缓冲区的大小if(n>0){buffer[n]=0;//把buffer当字符串使用。0=='\0'cout<<"father get a message["<<getpid()<<"]# "<<buffer<<endl;}}
}int main()
{int pipefd[N]={0};int n=pipe(pipefd);if(n<0)return 1;// cout<<"pipefd[0]="<<pipefd[0]<<" , pipefd[1]="<<pipefd[1]<<endl;// child->w    father->rpid_t id=fork();if(id<0)return 2;if(id==0){//childclose(pipefd[0]);//IPC codeWriter(pipefd[1]);close(pipefd[1]);exit(0);}//fatherclose(pipefd[1]);//IPC codeReader(pipefd[0]);pid_t rid=waitpid(id,nullptr,0);if(rid<0)return 3;close(pipefd[0]);return 0;
}

image-20250409142335606

经历了几次拷贝?

写:buffer(用户缓冲区) -> 系统缓冲区
读:系统缓冲区 -> buffer(用户缓冲区)

更新完善后的代码:

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <string.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
#define N 2
#define NUM 1024//child
void Writer(int wfd)
{string s="hello,I am child";pid_t id=getpid();int number=0;char buffer[NUM];while(true){sleep(1);// buffer[0]=0;//字符串清空,只是为了提醒阅读代码的人,把这个数组当祖字符串了。// snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),id,number++);//资源是系统提供的,所以必须使用系统调用接口,system call// write(wfd,buffer,strlen(buffer));char c='a';write(wfd,&c,1);//不需要+1,管道是文件!c语言的规定与文件无关!只需要把内容写入!// sleep(1);number++;cout<<number<<endl;if(number>=5){break;}}
}//father
void Reader(int rfd)
{char buffer[NUM];while(true){buffer[0]=0;ssize_t n=read(rfd,buffer,sizeof(buffer));//sizeof!=strlen//sizeof代表缓冲区的大小if(n>0){buffer[n]=0;//把buffer当字符串使用。0=='\0'cout<<"father get a message["<<getpid()<<"]# "<<buffer<<endl;}else if(n==0){cout<<"father read done!\n"<<endl;break;}else break;// cout<<"n : "<<n<<endl;}
}int main()
{int pipefd[N]={0};int n=pipe(pipefd);if(n<0)return 1;// cout<<"pipefd[0]="<<pipefd[0]<<" , pipefd[1]="<<pipefd[1]<<endl;// child->w    father->rpid_t id=fork();if(id<0)return 2;if(id==0){//childclose(pipefd[0]);//IPC codeWriter(pipefd[1]);close(pipefd[1]);exit(0);}//fatherclose(pipefd[1]);//IPC codeReader(pipefd[0]);pid_t rid=waitpid(id,nullptr,0);if(rid<0)return 3;close(pipefd[0]);sleep(5);return 0;
}

image-20250409202245121

管道特点
  1. 具有亲缘关系的进程之间进行通信

    通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

  2. 管道只能进行单向通信

    管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

  3. 管道通信需要让不同的进程看到同一份资源,多执行流是共享的,难免会出现访问冲突的问题。

临界资源竞争。父子进程是会进行协同的,内核会对管道操作进行同步与互斥。

– 保护管道文件的数据安全。

  1. 管道是面向字节流的。管道提供流式服务。

不管写了多少次,是以字节的方式一次就读完。

读端不管格式,读端看来就是一个一个的字节。(由上层区分内容)

  1. 管道是基于文件的,而文件的生命周期是随进程的!

如果进程退出了,文件会怎么办?

文件会被操作系统自动回收。

两个进程都退出了,管道就会被操作系统回收释放,所以管道的生命周期随进程。

管道的4种情况
  1. 读写端正常,写很慢,读很快,管道为空,读端就要阻塞。
  2. 读写端正常,写很快,读很慢,管道为满,写端就要阻塞。

​ 写完之后打印number

read读之前先sleep(5)

void Writer(int wfd)
{string s="hello,I am child";pid_t id=getpid();int number=0;char buffer[NUM];while(true){buffer[0]=0;//字符串清空,只是为了提醒阅读代码的人,把这个数组当祖字符串了。snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),id,number++);//资源是系统提供的,所以必须使用系统调用接口,system callwrite(wfd,buffer,strlen(buffer));//不需要+1,管道是文件!c语言的规定与文件无关!只需要把内容写入!// sleep(1);cout<<number<<endl;}
}//father
void Reader(int rfd)
{char buffer[NUM];while(true){sleep(5);buffer[0]=0;ssize_t n=read(rfd,buffer,sizeof(buffer));//sizeof!=strlen//sizeof代表缓冲区的大小if(n>0){buffer[n]=0;//把buffer当字符串使用。0=='\0'cout<<"father get a message["<<getpid()<<"]# "<<buffer<<endl;}}
}

image-20250409144718367

​ 很明显,管道是有大小的!

  1. 读端正常,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞。

    //child
    void Writer(int wfd)
    {string s="hello,I am child";pid_t id=getpid();int number=0;char buffer[NUM];while(true){char c='a';write(wfd,&c,1);number++;cout<<number<<endl;if(number>=5){break;}}
    }//father
    void Reader(int rfd)
    {char buffer[NUM];while(true){sleep(1);buffer[0]=0;ssize_t n=read(rfd,buffer,sizeof(buffer));//sizeof!=strlen//sizeof代表缓冲区的大小if(n>0){buffer[n]=0;//把buffer当字符串使用。0=='\0'cout<<"father get a message["<<getpid()<<"]# "<<buffer<<endl;}}
    }
    

    image-20250409160459624

    image-20250409160521339

    image-20250409160833364

  2. 写端正常,读端关闭。操作系统就要杀掉正在写入的进程。通过13号信号杀掉。

操作系统是不会做低效、浪费等类似的工作的。如果做了就是系统bug

验证:

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <string.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
#define N 2
#define NUM 1024//child
void Writer(int wfd)
{string s="hello,I am child";pid_t id=getpid();int number=0;char buffer[NUM];while(true){sleep(1);buffer[0]=0;//字符串清空,只是为了提醒阅读代码的人,把这个数组当祖字符串了。snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),id,number++);//资源是系统提供的,所以必须使用系统调用接口,system callwrite(wfd,buffer,strlen(buffer));// char c='a';// write(wfd,&c,1);//不需要+1,管道是文件!c语言的规定与文件无关!只需要把内容写入!// sleep(1);// number++;// cout<<number<<endl;// if(number>=5)// {//     break;// }}
}//father
void Reader(int rfd)
{char buffer[NUM];int cnt=0;while(true){buffer[0]=0;ssize_t n=read(rfd,buffer,sizeof(buffer));//sizeof!=strlen//sizeof代表缓冲区的大小if(n>0){buffer[n]=0;//把buffer当字符串使用。0=='\0'cout<<"father get a message["<<getpid()<<"]# "<<buffer<<endl;}else if(n==0){cout<<"father read done!\n"<<endl;break;}else break;// cout<<"n : "<<n<<endl;cnt++;if(cnt>5)break;}
}int main()
{int pipefd[N]={0};int n=pipe(pipefd);if(n<0)return 1;// cout<<"pipefd[0]="<<pipefd[0]<<" , pipefd[1]="<<pipefd[1]<<endl;// child->w    father->rpid_t id=fork();if(id<0)return 2;if(id==0){//childclose(pipefd[0]);//IPC codeWriter(pipefd[1]);close(pipefd[1]);exit(0);}//fatherclose(pipefd[1]);//IPC codeReader(pipefd[0]);close(pipefd[0]);cout<<"father close read fd:"<<pipefd[0]<<endl;sleep(5);int status=0;pid_t rid=waitpid(id,&status,0);if(rid<0)return 3;cout<<"wait child success:"<<rid<<" ,exit code: "<<((status>>8)&0xFF)<<" ,exit signal: "<<((status&0x7F))<<endl;sleep(5);cout<<"father quit"<<endl;return 0;
}

image-20250410170243599

image-20250410170452130

管道读写规则

管道大小

在不同内核里,管道大小有差别。

//child
void Writer(int wfd)
{string s="hello,I am child";pid_t id=getpid();int number=0;char buffer[NUM];while(true){char c='a';write(wfd,&c,1);number++;cout<<number<<endl;}
}//father
void Reader(int rfd)
{char buffer[NUM];while(true){sleep(50);buffer[0]=0;ssize_t n=read(rfd,buffer,sizeof(buffer));//sizeof!=strlen//sizeof代表缓冲区的大小if(n>0){buffer[n]=0;//把buffer当字符串使用。0=='\0'cout<<"father get a message["<<getpid()<<"]# "<<buffer<<endl;}}
}

image-20250409154709093

由此可知管道的大小是65536字节 也就是64KB

image-20250409155206792

PIPE_BUF 单次向管道写入的大小。

当没有数据可读时O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。当管道满的时候O_NONBLOCK disable: write调用阻塞,直到有进程读走数据O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

原子性:要读就把规定的数据全部读走,要么就不读。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

image-20250409153705790

这里的pipe size 就是 PIPE_BUF (4KB)

应用场景

管道与我们之前学的知识哪些是有关系的呢?

1.cat test.txt | head -10 | tail -5

image-20250410171202620

这批 sleep 进程具有血缘关系!创建两个管道,三个进程,然后输入输出重定向。

命令行 | 底层就是pipe。

以上就是匿名管道。

  1. 自定义shell – 我们想让我们的shell支持 | 管道,代码该如何写。

    1.分析输入的命令字符串,获取有几个|,分割出多个子命令字符串
    2.malloc申请空间,pipe先申请多个管道
    3.循环创建多个子进程,分析每一个子进程的重定向情况。a.首个:就是输出重定向,1->指定的管道写端b.中间:输入输出重定向,0标准输入重定向到上一个管道的读端 1标准输出重定向到下一个管道的写端c.末尾:输入重定向,0标准输入重定向到最后一个管道的读端。
    4.分别让不同的子进程执行不同的命令 -- exec* -- exec* 不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向。
    

相关文章:

  • batch normalization和layer normalization区别
  • 驱动总裁v2.19(含离线版)驱动工具软件下载及安装教程
  • Windows系统下Node.js环境部署指南:使用nvm管理多版本
  • Vmware设置静态IP和主机访问
  • 【连载10】基础智能体的进展与挑战综述-自我进化
  • 《Spring Boot实战指南:从零开始构建现代Java应用》
  • 泰迪杯特等奖案例学习资料:基于时空图卷积网络的城市排水系统水位精准重建与异常检测
  • Oracle无法正常OPEN(四)
  • 【文献阅读】中国湿地随着保护和修复的反弹
  • Mysql常用语句汇总
  • C++虚函数完全指南:从内存布局到动态多态的实现奥秘
  • AVIOContext 再学习
  • Linux之基础开发工具(yum,vim,gcc,g++)
  • C与指针5——字符串合集
  • 第二章:一致性基础 A Primer on Memory Consistency and Cache Coherence - 2nd Edition
  • 共铸价值:RWA 联合曲线价值模型,撬动现实资产生态
  • 【算法应用】基于灰狼算法优化深度信念网络回归预测(GWO-DBN)
  • 快速掌握--cursor
  • C# 编程核心:控制流与方法调用详解
  • word论文排版常见问题汇总
  • 谢晖不再担任中超长春亚泰队主教练:战绩不佳主动请辞
  • 涉个人信息收集使用问题,15款App和16款SDK被通报
  • 中国医药科技出版社回应发布“男性患子宫肌瘤”论文:正在核查
  • 女租客欠租后失联,房东开门后无处下脚:40平公寓变垃圾场
  • 五一假期上海虹桥边检站出入境近4.7万人次,韩国入境旅客同比增118%
  • 从陈毅杯到小应氏杯,五一长假上海掀起围棋热