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* 不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向。
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78380.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Xilinx FPGA | 管脚约束 / 时序约束 / 问题解析

注&#xff1a;本文为 “Xilinx FPGA | 管脚约束 / 时序约束 / 问题解析” 相关文章合辑。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 Xilinx FPGA 管脚 XDC 约束之&#xff1a;物理约束 FPGA技术实战 于 2020-02-04 17:14:53 发布 说明&#x…

家用服务器 Ubuntu 服务器配置与 Cloudflare Tunnel 部署指南

Ubuntu 服务器配置与 Cloudflare Tunnel 部署指南 本文档总结了我们讨论的所有内容&#xff0c;包括 Ubuntu 服务器配置、硬盘扩容、静态 IP 设置以及 Cloudflare Tunnel 的部署步骤。 目录 硬盘分区与扩容设置静态 IPCloudflare Tunnel 部署SSH 通过 Cloudflare Tunnel常见…

分享5款开源、美观的 WinForm UI 控件库

前言 今天大姚给大家分享5款开源、美观的 WinForm UI 控件库&#xff0c;助力让我们的 WinForm 应用更好看。 WinForm WinForm是一个传统的桌面应用程序框架&#xff0c;它基于 Windows 操作系统的原生控件和窗体。通过简单易用的 API&#xff0c;开发者可以快速构建基于窗体…

PHP盲盒商城系统源码从零搭建部署:专业级开发与优化实践

【导语&#xff1a;技术驱动商业创新】 在2025年社交电商全面升级的浪潮下&#xff0c;基于PHP的盲盒系统凭借其高开发效率与低成本优势&#xff0c;成为中小企业的首选方案。本文将深度拆解盲盒源码从开发到部署的全流程技术细节&#xff0c;涵盖架构设计、性能优化与安全防护…

(33)VTK C++开发示例 ---图片转3D

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 这是 VTK 测试 clipArt.tcl 的改编版本。 提供带有 2D 剪贴画的 jpg 文件&#xff0c;该示例将创建 3D 多边形数据模…

2025东三省B题深圳杯B题数学建模挑战赛数模思路代码文章教学

完整内容请看文章最下面的推广群 已经完成全部问题的代码和建模 一、问题一的模型构建与优化&#xff08;RGB颜色空间转换模型&#xff09; 基础模型&#xff08;线性映射模型&#xff09;/高斯过程回归模型&#xff08;GPR&#xff09;&#xff1a; 针对高清视频源&#xff0…

linux netlink实现用户态和内核态数据交互

1&#xff0c;内核态代码 #include <linux/module.h> #include <linux/netlink.h> #include <net/sock.h> #define NETLINK_TEST 31 struct sock *nl_sk NULL; static void nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; int pid; …

LeetCode:DP-多状态问题

简单 面试题 17.16. 按摩师 一个有名的按摩师会收到源源不断的预约请求&#xff0c;每个预约都可以选择接或不接。在每次预约服务之间要有休息时间&#xff0c;因此她不能接受相邻的预约。给定一个预约请求序列&#xff0c;替按摩师找到最优的预约集合&#xff08;总预约时间最…

Spring AOP---面向切面编程由认识到使用

1. AOP AOP(Aspect-Oriented Programming), 是一种思想, 面向切面编程。 在前文统一异常处理&#xff0c;统一结果返回就是使用了这一思想&#xff08;都是在集中处理某一类事情, 但又不影响原有代码的正常运行&#xff09;&#xff0c;但他们不是AOP&#xff0c;只是应用了这…

专题二十四:虚拟专用网络

一、VPN简介 VPN&#xff08;Virtual Personal Network&#xff09;即虚拟专用网&#xff0c;泛指通过VPN技术在公用网络上构建的虚拟专用网络。VPN用户在此虚拟网络中传输私网流量&#xff0c;在不改变网络现状的情况下实现安全、可靠的连接。其主要功能是在公用网络上建立专…

Milvus(12):分析器

1 分析器概述 在文本处理中&#xff0c;分析器是将原始文本转换为结构化可搜索格式的关键组件。每个分析器通常由两个核心部件组成&#xff1a;标记器和过滤器。它们共同将输入文本转换为标记&#xff0c;完善这些标记&#xff0c;并为高效索引和检索做好准备。 在 Milvus 中&a…

Power Query精通指南1:查询结构设计、数据类型、数据导入与迁移(平面文件、Excel、Web)

文章目录 零、Power Query简介0.1 Power Query 主要功能0.2 Power Query 的优势0.3 Power Query 组件 一、Power Query数据处理基本流程1.1 前期准备1.2 提取1.3 转换1.3.1 Power Query 编辑器界面1.3.2 默认转换1.3.3 自定义转换 1.4 加载1.4.1 自动检测数据类型1.4.2 重命名查…

WebRTC 服务器之Janus概述和环境搭建

1 概述 Janus 是由 Meetecho 开发的通用 WebRTC 服务器&#xff0c;它为构建 WebRTC 应用程序提供了一个模块化框架。服务器目标&#xff1a;Janus WebRTC 网关被设计为轻量级、通用的 WebRTC 服务器&#xff0c;除了实现以下方法外&#xff0c;它本身不提供任何功能&#xff1…

19:常见的Halcon数据格式

遍历文件夹与文件选择 1&#xff09;遍历文件夹&#xff1a; list_files( : : Directory, Options : Files) Directory&#xff1a;目录&#xff08;文件夹路径&#xff09; Options&#xff1a;选项 files 指定搜索的格式为文件 directories 指定搜索的格式为文件夹 re…

QML图像提供器 (Image Provider)

QML 中的图像提供器是一种自定义图像加载机制&#xff0c;允许你从非文件源&#xff08;如数据库、网络或程序生成的内容&#xff09;提供图像数据。 主要类型 QQuickImageProvider - 基础图像提供器 QPixmapImageProvider - 提供 QPixmap 图像 QImageImageProvider - 提供 …

计算机视觉与深度学习 | 双目立体匹配算法理论+Opencv实践+matlab实践

双目立体匹配 一、双目立体匹配算法理论与OpenCV、matlab实践一、双目立体匹配理论二、OpenCV实践三、优化建议四、算法对比与适用场景二、双目立体匹配算法理论及Matlab实践指南一、双目立体匹配理论二、Matlab实践步骤三、算法对比与优化建议四、完整流程示例五、常见问题与解…

AI国学智慧语录视频,条条视频10W+播放量

家人们&#xff01;图书类带货玩法真的非常多&#xff0c;之前也分享过蛮多&#xff0c;例如情感语录、育儿教育、爆款图书金句类、AI历史人物解说类等等。 本期继续来分享一个对于普通人来说&#xff0c;上手相当简单&#xff0c;容易起号&#xff0c;可作为长线深耕的AI带货…

echart图表使用

2、接口编写 该部分代码定义了UserController控制器类&#xff0c;用于处理与用户相关的请求。包含一个用于跳转页面的方法和一个返回用户详细数据&#xff08;以 JSON 格式呈现&#xff09;的接口。前者负责将用户导航至指定页面&#xff0c;后者通过构建ChartVO对象并填充数…

Android短信监控技术实现:合法合规的远程采集方案

一年经验的全栈程序员&#xff0c;目前头发健在&#xff0c;但不知道能撑多久。 该项目已成功部署并稳定运行于企业生产环境&#xff0c;如需个性化定制方案&#xff0c;欢迎联系作者进行深度合作。 文章目录 前言 一、页面设计 1.页面显示 2.代码实现 二、具体代码实现 1.添加…

前端跨域问题怎么在后端解决

目录 简单的解决方法&#xff1a; 添加配置类&#xff1a; 为什么会跨域 1. 什么是源 2. URL结构 3. 同源不同源举&#x1f330; 同源例子 不同源例子 4. 浏览器为什么需要同源策略 5. 常规前端请求跨域 简单的解决方法&#xff1a; 添加配置类&#xff1a; packag…