【Linux】进程间通信(匿名管道)
🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html

目录
进程间通信目的
进程间通信发展
进程间通信分类
管道
System V IPC
POSIX IPC
管道
匿名管道
使用管道通信的demo
进程池实现
前言
💬 hello! 各位铁子们大家好哇。
今日更新了Linux进程间通信的内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展
- 管道
- System V进程间通信
- POSIX进程间通信
进程间通信的前提:先让不同的进程,看到同一份(操作系统)资源(”一段内存“)。
进程间通信一定是某一个进程先需要通信,让OS创建一个共享资源。此时OS必须提供很多系统调用。
OS创建的共享资源的不同,系统调用接口也就不同,所以进程间通信会有不同的种类。
进程间通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道
匿名管道

一个进程将同一个文件打开两次,一次以写方式打开,另一次以读方式打开。此时会创建两个struct file,而文件的属性会共用,不会额外创建。

如果此时又创建了子进程,子进程会继承父进程的文件描述符表,指向同一个文件。我们把上面父子进程都看到的文件,叫管道文件。
管道只允许单向通信。
管道里的内容不需要刷新到磁盘。

未来要用父进程写,子进程读的话,在fork之后,各自关闭掉不用的文件描述符即可。 不用的描述符建议关闭,因为未来可能会误用,或者导致文件描述符泄露。

功能:创建匿名管道
参数:
pipefd[2]:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端。它是输出型参数。
返回值:成功返回0,失败返回错误代码
使用管道通信的demo


上图是创建管道,pipe的使用的例子。
下面是测试的完整代码:
#include <iostream>
#include <string>
#include <cerrno> // errno.h
#include <cstring> // string.h
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>const int size = 1024;std::string getOtherMessage()
{static int cnt = 0;std::string messageid = std::to_string(cnt); cnt++;pid_t self_id = getpid();std::string stringpid = std::to_string(self_id);std::string message = "messageid: ";message += messageid;message += " my pid is : ";message += stringpid;return message;
}// 子进程进行写入
void SubProcessWrite(int wfd)
{int pipesize = 0;std::string message = "father, I am your son prcess!";char c = 'A';while (true){std::cerr << "+++++++++++++++++++++++++++++++++" << std::endl;std::string info = message + getOtherMessage(); // 这条消息,就是我们子进程发给父进程的消息write(wfd, info.c_str(), info.size()); // 写入管道的时候,没有写入\0,std::cerr << info << std::endl;// sleep(1); // 子进程写慢一点// write(wfd, &c, 1);// std::cout << "pipesize: " << ++pipesize << " write charator is : "<< c++ << std::endl;// // if(c == 'G') break;// sleep(1);}std::cout << "child quit ..." << std::endl;
}// 父进程进行读取
void FatherProcessRead(int rfd)
{char inbuffer[size]; // c99 , gnu g99while (true){sleep(2);std::cout << "-------------------------------------------" << std::endl;// sleep(500);ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0){inbuffer[n] = 0; // == '\0'std::cout << inbuffer << std::endl;}else if (n == 0){// 如果read的返回值是0,表示写端直接关闭了,我们读到了文件的结尾std::cout << "client quit, father get return val: " << n << " father quit too!" << std::endl;break;}else if(n < 0) //读取失败{std::cerr << "read error" << std::endl;break;}// sleep(1);// break;}
}int main()
{// 1. 创建管道int pipefd[2];int n = pipe(pipefd); // 输出型参数,rfd, wfdif (n != 0){std::cerr << "errno: " << errno << ": "<< "errstring : " << strerror(errno) << std::endl;return 1;}std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;sleep(1);// 2. 创建子进程pid_t id = fork();if (id == 0){std::cout << "子进程关闭不需要的fd了, 准备发消息了" << std::endl;sleep(1);// 子进程 --- write// 3. 关闭不需要的fdclose(pipefd[0]);// if(fork() > 0) exit(0);SubProcessWrite(pipefd[1]);close(pipefd[1]);exit(0);}std::cout << "父进程关闭不需要的fd了, 准备收消息了" << std::endl;sleep(1);// 父进程 --- read// 3. 关闭不需要的fdclose(pipefd[1]);FatherProcessRead(pipefd[0]);std::cout << "5s, father close rfd" << std::endl;sleep(5);close(pipefd[0]);int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){std::cout << "wait child process done, exit sig: " << (status&0x7f) << std::endl;std::cout << "wait child process done, exit code(ign): " << ((status>>8)&0xFF) << std::endl;}return 0;
}
管道的四种情况:
- 如果管道内部是空的,并且写端fd没有关闭,此时读取条件不具备,读进程会被阻塞,读进程会等待,直到写端写入数据。
- 如果管道被写满,并且读端fd不读且没有关闭,此时写进程会被阻塞,写端会等待,直到数据被读取。
- 如果管道一直在读并且写端关闭了wfd,读端read返回值会读到0,表示读到文件结尾。
- 如果读端rfd直接关闭,写端wfd一直在写入,那么写端进程会被OS直接用13号信号关掉,相当于进程出现了异常。
管道的特征:
- 匿名管道:只用来进行具有血缘关系的进程之间,进行通信,常用于父子进程之间通信
- 管道文件的生命周期是随进程的
- 管道内部,自带进程之间同步的机制(多执行流执行代码的时候,具有明显的顺序性)
- 管道文件在通信的时候,是面向字节流的。(写的次数和读取的次数不是一一匹配的)
- 管道的通信模式,是一种特殊的半双工模式,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
原子的意思就是这次的写入操作不会被中断。写的时候,不会写一半就被读走。在读方看来,要么不写,要么写完了。


当shell执行用管道连接起来的多条命令时,shell内部会把他们各自变成一个进程,他们是同时启动的。他们的父进程都是bash,他们是兄弟关系。所以命令行上的 | 就是匿名管道。
进程池实现

ProcessPool.cc
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"// void work(int rfd)
// {
// while (true)
// {
// int command = 0;
// int n = read(rfd, &command, sizeof(command));
// if (n == sizeof(int))
// {
// std::cout << "pid is : " << getpid() << " handler task" << std::endl;
// ExcuteTask(command);
// }
// else if (n == 0)
// {
// std::cout << "sub process : " << getpid() << " quit" << std::endl;
// break;
// }
// }
// }// master
class Channel
{
public:Channel(int wfd, pid_t id, const std::string &name): _wfd(wfd), _subprocessid(id), _name(name){}int GetWfd() { return _wfd; }pid_t GetProcessId() { return _subprocessid; }std::string GetName() { return _name; }void CloseChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid, nullptr, 0);if (rid > 0){std::cout << "wait " << rid << " success" << std::endl;}}~Channel(){}private:int _wfd;pid_t _subprocessid;std::string _name;
};// 形参类型和命名规范
// const &: 输入
// & : 输入输出型参数
// * : 输出型参数
// task_t task: 回调函数
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{for (int i = 0; i < num; i++){// 1. 创建管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)exit(1);// 2. 创建子进程pid_t id = fork();if (id == 0){if (!channels->empty()) {// 第二次之后,开始创建的管道要关闭继承下来的写端for(auto &channel : *channels) channel.CloseChannel();}// child - readclose(pipefd[1]);dup2(pipefd[0], 0); // 将管道的读端,重定向到标准输入task();close(pipefd[0]);exit(0);}// 3.构建一个channel名称std::string channel_name = "Channel-" + std::to_string(i);// 父进程close(pipefd[0]);// a. 子进程的pid b. 父进程关心的管道的w端channels->push_back(Channel(pipefd[1], id, channel_name));}
}// 0 1 2 3 4 channelnum
int NextChannel(int channelnum)
{static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void SendTaskCommand(Channel &channel, int taskcommand)
{write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}
void ctrlProcessOnce(std::vector<Channel> &channels)
{sleep(1);// a. 选择一个任务int taskcommand = SelectTask();// b. 选择一个信道和进程int channel_index = NextChannel(channels.size());// c. 发送任务SendTaskCommand(channels[channel_index], taskcommand);std::cout << std::endl;std::cout << "taskcommand: " << taskcommand << " channel: "<< channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << std::endl;
}
void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{if (times > 0){while (times--){ctrlProcessOnce(channels);}}else{while (true){ctrlProcessOnce(channels);}}
}void CleanUpChannel(std::vector<Channel> &channels)
{// int num = channels.size() -1;// while(num >= 0)// {// channels[num].CloseChannel();// channels[num--].Wait();// }for (auto &channel : channels){channel.CloseChannel();channel.Wait();}// // 注意// for (auto &channel : channels)// {// channel.Wait();// }
}// ./processpool 5
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " processnum" << std::endl;return 1;}int num = std::stoi(argv[1]);LoadTask();std::vector<Channel> channels;// 1. 创建信道和子进程CreateChannelAndSub(num, &channels, work1);// 2. 通过channel控制子进程ctrlProcess(channels, 5);// 3. 回收管道和子进程. a. 关闭所有的写端 b. 回收子进程CleanUpChannel(channels);// sleep(100);return 0;
}

如上图,左边是父进程,右边是子进程。创建子进程的时候,从第二个子进程开始,创建的时候会继承父进程之前的文件描述符,也就会连接到进程1的写端。随着子进程的增加,越来越多描述符指向先前的管道的写端 ,就会导致要关闭管道时,写端没关完,读端读不到,就会造成阻塞,进程就退出不了。所以要在创建第二个及以后的进程的时候,把继承的写端关掉,如下图:
Task.hpp
#pragma once#include <iostream>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>#define TaskNum 3typedef void (*task_t)(); // task_t 函数指针类型void Print()
{std::cout << "I am print task" << std::endl;
}
void DownLoad()
{std::cout << "I am a download task" << std::endl;
}
void Flush()
{std::cout << "I am a flush task" << std::endl;
}task_t tasks[TaskNum];void LoadTask()
{srand(time(nullptr) ^ getpid() ^ 17777);tasks[0] = Print;tasks[1] = DownLoad;tasks[2] = Flush;
}void ExcuteTask(int number)
{if (number < 0 || number > 2)return;tasks[number]();
}int SelectTask()
{return rand() % TaskNum;
}void work()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}void work1()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}void work2()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}相关文章:
【Linux】进程间通信(匿名管道)
🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html 目录 进程间通信目的 进程间通信发展 进程间通信分类 管道 System V IPC POSI…...
memset()函数的实现
memset()函数的实现 _CRTIMP void* __cdecl memset (void*, int, size_t); memset()函数的实现 文章目录 memset()函数的实现memset()函数 memset()函数 _CRTIMP void* __cdecl memset (void*, int, size_t);void* memset(void* src, int val, size_t count) {char *char_src…...
STM32CUBEIDE FreeRTOS操作教程(七):queue队列
STM32CUBEIDE FreeRTOS操作教程(七):queue队列 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件,不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板为例ÿ…...
类型转换与字符串操作:数据的灵活变形!
Java中的隐式与强制类型转换:让你轻松驾驭数据 在编程的世界中,数据的类型如同游戏中的角色,赋予它们不同的特性与能力。而在Java中,隐式类型转换与强制类型转换就像是两把利剑,帮助我们在这个复杂的世界中游刃有余。…...
动态规划18:188. 买卖股票的最佳时机 IV
动态规划解题步骤: 1.确定状态表示:dp[i]是什么 2.确定状态转移方程:dp[i]等于什么 3.初始化:确保状态转移方程不越界 4.确定填表顺序:根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接:188.…...
YOLOv8改进 - 注意力篇 - 引入ShuffleAttention注意力机制
一、本文介绍 作为入门性篇章,这里介绍了ShuffleAttention注意力在YOLOv8中的使用。包含ShuffleAttention原理分析,ShuffleAttention的代码、ShuffleAttention的使用方法、以及添加以后的yaml文件及运行记录。 二、ShuffleAttention原理分析 ShuffleA…...
基于Multisim的8路彩灯循环控制电路设计与仿真
1)由八个彩灯LED的明暗构成各种彩灯图形; 2)彩灯依次显示的图形: 彩灯从左至右渐亮至全亮(8个CP) 彩灯从左至右渐灭至全灭(8个CP) 彩灯从右至左渐亮至全亮(8个CP) 彩灯从右至左渐灭至全灭(8个CP) 彩灯全亮(1个CP) 彩灯全灭(1个CP) 彩灯全亮(1个CP) 彩灯全灭(1个CP) 3)彩灯图形循…...
完整的模型训练套路 pytorch
**前置知识: 1、 (1).train():将模型设置为训练模式 (2).eval():将模型设置为评估模式 不写也可以(只对特定网络模型有作用,如含有Dropout的) 2、 with…...
2024年十大前沿图像分割模型汇总:工作机制、优点和缺点介绍
《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…...
Notepad++将搜索内容所在行选中,并进行复制等操作
背景 Notepad在非常多的数据行内容中,按照指定内容检索,并定位到具体行,而后对内容行的数据进行复制、剪切、删除等处理动作。 操作说明 检索并标记所在行 弹出搜索框:按下 Ctrl F。 输入查找字符串:在搜索框中输入要…...
[Java EE] IP 协议 | NAT 机制 | 路由选择 | MAC 地址 | 域名解析服务
Author:MTingle major:人工智能 Build your hopes like a tower! 目录 一. 初识 IP 协议 IP 协议报头: 二. IP 协议如何管理地址 NAT机制 路由选择 三. 数据链路层(以太网): MAC地址 四. 域名解析系统 一. 初识 IP 协议 IP 协议工作在网络层,其目标是为了在复…...
赋能特大城市水务数据安全高速运算,深圳计算科学研究院YashanDB数据库系统斩获“鼎新杯”二等奖
第三届“鼎新杯”数字化转型应用优秀案例评选结果日前正式公布,深圳计算科学研究院联合深圳市环境水务集团有限公司申报的《深圳环境水务国产数据库YashanDB,赋能特大城市水务数据安全高速运转》案例,经过5个多月的评审,从4000申报…...
RAYDATA链接PGSQL做图表
1.拖一个脚本进去 2.拖一个柱状图进去 3.双击脚本写代码 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Ventuz.Kernel; using Npgsql; using System.Threading; using System.Threading.Tasks;public class Script…...
UE5里的TObjectPtr TSharedPtr TWeakPtr有什么区别
在 Unreal Engine(UE)编程中,TObjectPtr、TSharedPtr 和 TWeakPtr 都是 指针类型,但它们在生命周期管理和使用场景上有不同的特点。让我们详细分析这些指针的区别和用途。 TObjectPtr TObjectPtr 是 UE5 中引入的新智能指针类型…...
前端--深入理解HTTP协议
HTTP 协议简介 HTTP(HyperText Transfer Protocol,超文本传输协议)是一个应用层协议,用于在客户端(通常是浏览器)和服务器之间传输超文本数据(如 HTML、CSS、JavaScript 等)。它是万…...
线性代数 向量
一、定义 几何定义:向量是一个有方向和大小的量,通常用箭头表示。向量的起点称为原点,终点称为向量的端点。 代数定义:向量是一个有序的数组,通常表示为列向量或行向量。 行向量就是 1*n的形式(行展开&…...
go中阶乘实现时递归及迭代方式的比较
package mainimport ("fmt""time""math/big" )// 使用递归和 big.Int 计算阶乘 func FactorialRecursive(n *big.Int) *big.Int {if n.Cmp(big.NewInt(0)) 0 {return big.NewInt(1)}return new(big.Int).Mul(n, FactorialRecursive(new(big.Int…...
Jupyter notebook中更改字体大小
文章目录 方法一:局部修改方法二:全局修改 Jupyter notebook提供了一个非常方便的跨平台交互代码编译环境,但是单元格的内的代码字体往往显示较小,不利于观看。本人查了很多方法来调整字体,后来发现既不需要更改jupyte…...
关于Ubuntu服务器的时间同步设置以及Linux什么时候开始使用swap虚拟内存
一、关于Ubuntu服务器的时间同步设置 首先我们检查一下服务器的时区设置和当前时间值,获取/etc/timezone 配置以及使用date命令查看当前时间。 rootiZ2ze7n2ynw18p6bs92fziZ:~# cat /etc/timezone Asia/Shanghai rootiZ2ze7n2ynw18p6bs92fziZ:~# date Wed Dec 21 …...
Java Stream API 详解
Java Stream API 详解 1. 什么是 Stream API? Stream API 是 Java 8 引入的一种用于处理集合(如数组、列表)的强大工具。它提供了一种声明性方式处理数据,可以简化代码并提高可读性。Stream 不是数据结构,它只是一种…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...


