Linux 进程间通信之匿名管道

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux知识分享⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 


目录

前言 

一. 进程间通信介绍

1.进程间通信目的

2.进程间通信发展

3.进程间通信分类 

二.管道 

用fork来共享管道原理

匿名管道

 进程池



前言 

从进程控制篇章,我们知道了进程是具有独立性,既然各进程具有独立性,它们之间是互不联系的,那它们是怎么通过一种方式取得联系?为什么要有进程间通信?进程间通信本质是什么?

一. 进程间通信介绍

1.进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另
  • 一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

3.进程间通信分类 

管道
  • 匿名管道pipe
  • 命名管道
System V IPC
  • System V 消息队列
  • System V 共享内存
  • System V 信号量
POSIX IPC
  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

二.管道 

管道是Linux原生能提供的,管道有两种,匿名和命名。 

进程间通信的前提,是需要让不同的进程看到同一块“内存”(特定的组织结构)

所以你所谓的进程看到同一块“内存” 其实是不隶属于任何一个进程,应该更强调共享。

那如何让两个进程看到同一块“内存”?

fork来共享管道原理

         

 

 在实现之前我们需要了解一个接口函数 pipe

创建管道需要使用pipe函数。pipe函数会返回两个文件描述符,分别代表着管道的两端。这两个文件描述符可以用于在父进程和子进程之间传输数据。

pipefd[0]:读下标

pipefd[1]:   写下标 

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <unistd.h>
#define N 2

void Write(int fd)
{
    std::string str = "hello, I am child process";
    pid_t pid = getpid();
    int number = 0;
    char buf[1024];
    while (1)
    {
        sleep(1);
        buf[0] = '\0';
        snprintf(buf, sizeof(buf), "%s-%d- %d\n", str.c_str(), number++, pid);
        write(fd, buf, strlen(buf));
        // std::cout <<number << std::endl;
        // if(number > 5)
        // break;
    }
}

void Read(int fd)
{
    char buf[1024];
    int cnt = 0;
    while (1)
    {

        memset(buf, 0, sizeof(buf));
        size_t n = read(fd, buf, sizeof(buf));
        if (n > 0)
        {
            std::cout << "father get a message:[" << getpid() << "]#" << buf << std::endl;
        }
        else if (n == 0)
        {
            printf("father read file done\n");
            break;
        }
        else
        {
            std::cout << "father read error" << std::endl;
            break;
        }
        cnt++;
        if (cnt > 5)
            break;
    }
}

int main()
{
    int pipefd[N];
    int n = pipe(pipefd);
    if (n < 0)
    {
        std::cout << "pipe error" << std::endl;
        return 1;
    }
    pid_t pid = fork();
    if (pid < 0)
    {
        std::cout << "fork error" << std::endl;
    }
    else if (pid == 0)
    {
        // child process
        close(pipefd[0]);
        Write(pipefd[1]);
        close(pipefd[1]);
        exit(0);
    }
    else
    {
        // parent process
        Read(pipefd[0]);
        close(pipefd[0]);
        // wait child process
        std::cout << "father close read fd: " << pipefd[0] << std::endl;
        sleep(5); // wait child process exit
        pid_t status = 0;
        pid_t child_pid = waitpid(pid, &status, 0);
        if (child_pid < 0)
        {
            std::cout << "waitpid error" << std::endl;
            return 2;
        }
        std::cout << "wait child success: " << child_pid << " exit code: " << ((status >> 8) & 0xFF)
                  << " exit signal: " << (status & 0x7F) << std::endl;
    }

    sleep(3); // wait father process exit
    return 0;
}

运行代码

 管道的特征:

1.具有血缘关系的进程才能进行进程间通信

2.管道只能单向通信 

3.父子进程是会进程协同的,同步与互斥——保护管道内数据。

4.管道是面向字节流的 ps:这个我们后面网络在讲

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

下面我们就来挖一挖细节,基于第3点特征衍生出来的管道内的4种情况

读端正常,管道内容为空,读端就要堵塞

读端正常,管道内容写满,读端就要堵塞

读端正常,写段关闭,读端就会读到0,表明读到了文件的结尾,不会阻塞

写段正常写入,读端关闭,OS就会杀掉正在写入的进程。 

子进程写代码是有sleep1秒的 而父进程是没有sleep1秒 ,从视频我们可以得出父进程在等待子进程写入到管道中,上一次数据被读到,说明管道的内容空了,而子进程休眠1秒钟这期间对应父进程阻塞1秒钟。

第二种情况 我们让写段写快一点,读段慢一点休眠5秒钟 写段不休眠 

读端正常,管道内容写满,读端就要堵塞

第三种情况 我们写代码的number等于5时直接break;

读端正常,写段关闭,读端就会读到0,表明读到了文件的结尾,不会阻塞

第四情况 我们让读端变量cnt == 5时,读端退出。

从第4个结论来说确实OS会杀掉进程,资源有限,都没有人读了,写入后还要写时拷贝,浪费资源。 

匿名管道

 

从上面我们看到3个sleep的父进程是bash 那这样我们可以知道它们是有血缘关系的,

我们在shell打命令行,执行后,然后shell解释我们的命令看到两个|直接创建两个管道,然后再程序替换 然后3个sleep根据重定向原理重定向到管道中。

所以我们以前在命令行执行的管道 | 就是传说之中的匿名管道!!!

 进程池

 根据前面程序控制,和本节的管道知识,我们可以用fork创建多个子进程,父进程写入,子进程读取,根据读取的内容,子进程完成一些相应的事情。这些子进程就好比池子里的水,我们要用的时候直接就可以拿来用。

代码实现

#include "task.hpp"
#include <string>
#include <unistd.h>
#include <cstdlib>

#define ProcessNum 5 // 进程个数

// 先描述
class channle
{
public:
    channle(int cmdfd, int slaverid, const std::string &processname)
        : _cmdfd(cmdfd), _slaverid(slaverid), _processname(processname)
    {
    }

public:
    int _cmdfd;               // 发送任务的文件描述符
    pid_t _slaverid;          // 子进程的PID
    std::string _processname; // 子进程的名字
};
对于一个进程池来说,进程多了,我们肯定是要管理起来的,所以定义一个对象方面我们管理,对象定义出来了后,我们就要创建管道和子进程。

void InitProcessPool(std::vector<channel> *channels)
{
    std::vector<int> oldfds;
    for (int i = 0; i < ProcessNum; i++)
    {
        // 创建管道
        int pipefd[2];
        int n = pipe(pipefd);
        if (n < 0)
        {
            perror("pipe");
            exit(1);
        }
        // 创建子进程
        pid_t id = fork();
        if (id < 0)
        {
            perror("fork");
            exit(2);
        }
        else if (id == 0)
        { // 子进程
            for (auto fd : oldfds) //关闭之前继承下来的写端
            {
                close(fd);
            }
            close(pipefd[1]);   // 子进程读,关闭写端。
            dup2(pipefd[0], 0); // 管道的读端替换成标准输入0
            close(pipefd[0]);
            slaver();
            exit(0);
        }
        else
        {
            // 父进程
            close(pipefd[0]); // 父进程写,关闭读端。
            // 添加channle字段
            std::string name = "process-" + std::to_string(i);
            channels->push_back(channel(pipefd[1], id, name)); // 利用零时对象初始化
            oldfds.push_back(pipefd[1]);                       // 子进程会继承父进程的写端 方便我们在fork之后关闭写端
        }
    }
}

我们再写个Debug测试一下。

void Debug(const std::vector<channel> &channels)
{
    for (auto &it : channels)
    {
        std::cout << it._cmdfd << ' ' << it._slaverid << ' ' << it._processname << std::endl;
    }
}

int main()
{
   
    std::vector<channel> channels;

    InitProcessPool(channels);
    
    Debug(channels);
    return 0;
}

 

5个子进程创建完毕。那么下一步就是通过cmdfd这个文件描述符父进程写入,子进程读取 

我们可以用dup2,我们从键盘读入输入的内容,从管道读取。这样做的好处就是slaver这个函数不用传参

 else if (id == 0)
        {                     // 子进程
            close(pipefd[1]); // 子进程读,关闭写端。
            dup2(pipefd[0],0) //管道的读端替换成标准输入0
            slaver();
            exit(0);
        }

slaver这个函数就是获取任务的函数,怎么获取系统调用read获取,我们通过dup2原本是从标准输入读取,现在从管道里读取。 然后执行相应的任务

void slaver()
{
    int cmdcode = 0;
    while (true)
    {
        int n = read(0, &cmdcode, sizeof(int));
        if (n == sizeof(int))
        {
            std::cout << "slaver say@ get a cmdcode: " << getpid() << " : cmdcode:" << cmdcode << std::endl;
            if (cmdcode > = 0 && cmdcode < task.size())
                task.[cmdcode]();
        }
        if (n == 0)
            break;
    }
}
#pragma once
#include <vector>
#include <iostream>

typedef void (*task_t)();

void task1()
{
    std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{
    std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{
    std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{
    std::cout << "lol 用户释放技能,更新用户的血量和蓝量" << std::endl;
}

void LoadTask(std::vector<task_t> *tasks)
{
    tasks->push_back(task1);
    tasks->push_back(task2);
    tasks->push_back(task3);
    tasks->push_back(task4);
}

 现在还有没有任务,我们可以写一个简单的函数把函数的指针放进vector这个容器中然后根据cmdcode下标访问进行函数调用。

 有了任务列表我们就要派发任务,这里博主选择随机派发任务,当然你下去实现的时候可以选择轮循方式派发任务。

void ctrlSlaver(const std::vector<channel> &channels)
{
    while (true)
    {
        std::cout << "Please Enter@ ";
        // 1. 选择任务
        int cmdcode = rand() % tasks.size();
        // 2. 选择进程
        int processpos = rand() % channels.size();

        std::cout << "father say: "
                  << " cmdcode: " << cmdcode << " already sendto " << channels[processpos]._slaverid << " process name: "
                  << channels[processpos]._processname << std::endl;
        // 3. 发送任务
        write(channels[processpos]._cmdfd, &cmdcode, sizeof(cmdcode));
        // sleep(1);
    }
}

 

 最后我们还再利用wiatpid这个函数回收子进程

void QuitProcess(const std::vector<channel> &channels)
{
    for (const auto &c : channels)
    {
        close(c._cmdfd);
        waitpid(c._slaverid, nullptr, 0);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

富唯智能案例|双3D相机引导衔架抓取铝型材

随着制造业的快速发展和自动化水平的不断提升&#xff0c;铝型材的自动化抓取和加工成为行业内的一大技术难题。铝型材因其轻便、耐腐蚀、易加工等特点&#xff0c;广泛应用于建筑、汽车、电子等领域。然而&#xff0c;铝型材的形状多样、尺寸不一&#xff0c;以及生产线上的高…

4G小车的公网直播推流

一直想做一个小车, 可以通过4G推流, 没想到现在很多云服务提供商, SRS云服务器已经可以一键搭建了. 硬件方面, 就是一个1126驮着一个3516, 1126负责4G连接, 转流到Intenet, 3516负责vi_venc_rtsp 思路如下, 我的1126的摄像头一直没能横过来, 所以就不用1126的摄像头了, 先用35…

SpringBoot配置HTTPS及开发调试

前言 在实际开发过程中&#xff0c;如果后端需要启用https访问&#xff0c;通常项目启动后配置nginx代理再配置https&#xff0c;前端调用时高版本的chrome还会因为证书未信任导致调用失败&#xff0c;通过摸索整理一套开发调试下的https方案&#xff0c;特此分享 后端配置 …

项目管理-项目管理科学基础

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 1.项目管理科学基础--主要内容 项目管理科学基础&#xff0c;以下讲解两方面的内容&#xff1a;工程经济学、运筹学。 2.具体知识点 2…

使用Postman对@RequestPart和HttpServletRequest组合传参方式

使用Postman对RequestPart和HttpServletRequest组合传参方式 方法代码如下&#xff1a; /*** 发布*/ApiOperation("发布")ApiImplicitParams({ApiImplicitParam(name "req", value "json格式", dataType "Map", dataTypeClass Ma…

Docker-Compose概述与简单编排部署

目录 前言 一、Docker-Compose 概述 1、Docker-Compose 概念 2、Docker-Compose 优缺点 2.1 Docker-Compose 优点 2.2 Docker-Compose 缺点 3、Docker-Compose与Docker-Swarm的区别 二、两大文件格式 1、YAML 文件格式 2、JOSON 文件格式 3、YAML 与 JOSON 格式的区…

【C++】:const成员,取地址及const取地址操作符重载

目录 一&#xff0c;const成员二&#xff0c;取地址及const取地址操作符重载 一&#xff0c;const成员 将const修饰的“成员函数”称之为const成员函数&#xff0c;const修饰类成员函数&#xff0c;实际修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的…

力扣刷题第0天:只出现一次的数字

目录 第一部分:题目描述 ​第二部分:题目分析 第三部分:解决方法 3.1思路1: 双指针暴力求解 3.2 思路2&#xff1a;异或运算 第四部分:总结收获 第一部分:题目描述 第二部分:题目分析 由图片分析可得&#xff0c;该题目对算法时间复杂度有一定的要求时间复杂度为O(N)&a…

Linux搭建mysql环境

搭建 MySQL 环境 1、使用 wget 下载安装包&#xff0c;下载到 opt 目录中 wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm2、安装 MySQL 公钥 rpm -i mysql57-community-release-el7-10.noarch.rpmrpm --import https://repo.mysql.com/RPM-GP…

【算法刷题 | 动态规划02】5.02(不同路径、不同路径||、整数拆分、不同的二叉搜索树)

文章目录 5.不同路径5.1题目5.2解法一&#xff1a;深度搜索5.2.1深度搜索思路5.2.2代码实现 5.3解法二&#xff1a;动规5.3.1动规思路5.3.2代码实现 6.不同路径||6.1题目6.2解法&#xff1a;动规6.2.1动规思路&#xff08;1&#xff09;dp数组以及下标含义&#xff08;2&#x…

python学习笔记B-18:序列结构之集合--集合的创建、操作与删除

集合的创建、常用操作和删除方法&#xff1a; s {1,2,3,4,5,} print(s)s set() #创建了一个空集合 print(s,type(s))s {} #创建了一个空字典 print(s, type(s))s set("helloworld") print(s) #集合内容是无序的&#xff0c;不重复&#xff0c;所以顺序混乱…

VG做mirror引起的块偏移

事件起因 Oracle10.2环境 Aix操作系统使用aix的lvm技术。制作vg的mirror。以此来替换掉老的存储。 做mirror前&#xff0c;数据库已完全关闭 故障现象 在启动数据库时&#xff0c;发现IO错误。该系统的spfile&#xff0c;ctl&#xff0c;dbf均是用lv做的裸设备。其中dbf是使…

STM32项目设计:基于stm32f1的智能门锁(附项目视频全套教程)

最近假期比较闲,拿着之前剩下的模块做了一个小玩具, 先制定一下此次玩具的规划,也可以理解为简易项目书。 开发软件&#xff1a;keil 硬件选型&#xff1a;STM32F103C8T6、RFID读卡器、oled屏幕、按键模块、蓝牙通信模块、蜂鸣器、舵机; 上位机&#xff1a; 1.上位机可以对密…

pkpmbs 建设工程质量监督系统 Ajax_operaFile.aspx 文件读取漏洞复现

0x01 产品简介 pkpmbs 建设工程质量监督系统是湖南建研信息技术股份有限公司一个与工程质量检测管理系统相结合的,B/S架构的检测信息监管系统。 0x02 漏洞概述 pkpmbs 建设工程质量监督系统 Ajax_operaFile.aspx接口处存在文件读取漏洞,未经身份认证的攻击者可以利用漏洞读…

【C++】拷贝复制:拷贝构造函数的使用

欢迎来到CILMY23的博客 本篇主题为&#xff1a;拷贝复制&#xff1a;拷贝构造函数的使用 博客主页&#xff1a;CILMY23-CSDN博客 个人专栏&#xff1a;Python | C | C语言 | 数据结构与算法 感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 写在前头…

【趣味实践】KataGo+Sabaki搭建Ai围棋助手

前言 最近和同门在比试围棋&#xff0c;结果被爆虐&#xff0c;于是想借助Ai治治“嚣张”的他。 KataGo简介 继2016年AlphaGo出圈以来&#xff0c;已有不少Ai模型&#xff0c;其中部分如下图[1]所示。 在线围棋对弈网站OGS上&#xff0c;使用KataGo(https://online-go.com/)这…

什么是限流?常见的限流算法

目录 1. 什么是限流 2. 常见限流算法 3. 固定窗口算法 4. 滑动窗口算法 5. 漏桶算法 6. 令牌桶算法 7. 限流算法选择 1. 什么是限流 限流&#xff08;Rate Limiting&#xff09;是一种应用程序或系统资源管理的策略&#xff0c;用于控制对某个服务、接口或功能的访问速…

硬件知识积累 DP 接口简单介绍以及 DP信号飞线到显示屏的问题

1. DP 接口的介绍 定义与起源&#xff1a; DP接口是由PC及芯片制造商联盟开发&#xff0c;并由视频电子标准协会&#xff08;VESA&#xff09;标准化的数字式视频接口标准。它的设计初衷是为了取代传统的VGA、DVI和FPD-Link&#xff08;LVDS&#xff09;接口&#xff0c;以满足…

QT-QTCreator环境配置

准备工作&#xff1a; 下载QT: 链接&#xff1a;https://pan.baidu.com/s/1prJcsC4DGqhKiXvLuPQFVA?pwd60b3 提取码&#xff1a;60b3下载WindowsKits&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1QNiS3HpbH5M5kXx5AhkqnQ?pwde2h8 提取码&#xff1a;e2h8安装的…

Java数组深度剖析:掌握数据结构的基石

引言 在编程世界中&#xff0c;数仅仅是一种数据类型&#xff0c;它是理解内存分配、多维数据处理以及性能优组像是构建复杂数据结构的基本积木。它们简洁、高效&#xff0c;是管理元素集的首选方式。在Java中&#xff0c;数组不化的关键。 这篇文章致力于深入探讨Java数组的各…
最新文章