【Linux】进程间通信(IPC)——匿名管道

news2024/12/25 9:15:54

目录

为什么要进行进程间通信?

匿名管道的具体实现

pipe创建内存级文件形成管道

pipe的简单使用

匿名管道的四种情况和五种特性

四种情况

五种特性

PIPE_BUF

命令行管道 |

功能代码:创建进程池


为什么要进行进程间通信?

1.数据传输:一个进程需要将它的数据发送给另一个进程,比如我们有两个进程,一个负责获取数据,另一个负责处理数据,这时第一个进程就要将获取到的数据交给第二个进程

2.资源共享:多个进程间共享同样的资源

3.通知事件:一个进程需要给其他进程发送消息,通知他们发生了某种事件

4.进程控制:有些事件需要完全控制另一个进程,比如我们在使用gdb调试时,gdb就是一个进程,它控制了我们要调试的进程

进程之前是有互相传递信息的需求,但是进程之间又是独立的,一个进程不可能去另一个进程的地址空间中取信息,所以这就要求操作系统去提供一块交换数据的空间来供进程之间使用

OS提供空间有不同的样式,这就有了不同的通信方式

1.管道(分为匿名和命名)

2.共享内存

3.消息队列

4.信号量

那么我们就先来谈一谈匿名管道

匿名管道的具体实现

在谈之前,我们要有一些之前的知识作为理论基础,就是父进程创建子进程PCB和文件描述符表是要拷贝一份的,并且里边的值不会进行修改,就相当于浅拷贝;而管理文件的结构体对象不会拷贝。因为前者是跟进程相关的,而后者是跟文件系统相关的。我们把这段话用图来描述就是这样的:

通过这样的操作父子进程就可以看到同一块文件的缓冲区了,这样进程就可以读写了,但是两个文件由读又写容易发生混乱,所以我们一般关掉一个进程的读端,关掉另一个进程的写端,这样就实现了单向通信,就是因为它是单向通信,就像管道一样,所以这样的通信方式就被命名为管道。

pipe创建内存级文件形成管道

我们上面的操作是基于一个实实在在的磁盘文件的,我们必须得这样吗?肯定不是的,OS就提供了一个系统调用负责提供一个内存级的文件它没有名字,只能通过文件描述符来访问,这个系统调用叫pipe()

它的参数是一个输出型参数,就是pipe这个函数把内存级文件的读写文件描述符放到这个数组中,我们来取来用。

并且,规定0下标放的是r方法,1下标放的是w方法

由上面我们可以看出,匿名管道是只能具有血缘关系的进程之间使用,因为文件描述符表是要靠父进程创建子进程拷贝下来的。

pipe的简单使用

那么下面我们就写一段代码来验证上面所说的内容,并且演示管道究竟应该如何使用,下面的代码就是子进程往管道里写,父进程往管道里读

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
void writer(int wfd)
{
    const char *str = "i am child,this is the ";
    int cnt = 0;
    char buffer[128] = {0};
    while (1)
    {
        snprintf(buffer, sizeof(buffer), "%s%d message", str, cnt);
        write(wfd, buffer, strlen(buffer));
        sleep(1);
        cnt++;
    }
}
void reader(int rfd)
{
    while (1)
    {
        char buffer[1024] = {0};
        read(rfd, buffer, sizeof(buffer));
        printf("I am father,I get a message:%s\n", buffer);
    }
}

int main()
{
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    if (n < 0)
        return 1;
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程负责w
        close(pipefd[0]);
        writer(pipefd[1]);
        exit(0);
    }

    // 父进程负责r
    close(pipefd[1]);
    reader(pipefd[0]);
    return 0;
}

匿名管道的四种情况和五种特性

有了上面的一些基本使用,下面我们来演示一下管道的四种情况以及说明五种特性

四种情况

第一种:管道中没有数据,并且子进程不关闭自己的写端,这时父进程会进行阻塞等待,直到管道中有数据

第二种:子进程一直写,父进程不读,但是父进程不关闭读端,当管道被写满时就要进行阻塞等待,直到管道中的数据被读出去才会继续写

我们就让子进程一次写一个字符,看看它一共能写多少个字符

这里printf如果不给换行的话一定要fflush,否则有的打印的东西会在缓冲区中打印不出来

我们可以看到最终是打印到了65536byte,正好是64kb,我们就可以推断出管道的大小是64kb

第三种:子进程不写了并且关掉了写端,这时读端读完了管道中的数据后,read的返回值就为0,这时我们就可以人为的退出了,这和第一种情况是不同的第一种情况是阻塞等待

我们让子进程写10秒就退出,read返回值为0父进程就退出

第四种:让写端一直写,但是读端不读并且关闭读端,这时的结果就是写端也会退出,因为没人读了写就没意义了。

至于说写端是如何退出的呢?其实是收到了退出信号,我们也可以通过wait的方式来看一下退出信号是什么

我们让写端一直写,读端读5秒后退出,然后通过wait的方式获取子进程(写端)的退出信号

五种特性

通过上面的一些介绍,我们就可以总结出管道的五种特性

1.自带同步机制:写满了就不写了,等待读,等待它们之间的同步,读不到就不读了,等待写

2.具有血缘关系的进程间进行通信

3.pipe是面向字节流的:我可以一个字符一个字符的写,同时可以一下读很多个字节,就是说读的次数和写的次数之间是没有关系的,它们是面向管道中的数据的

4.进程退出,管道自动释放,文件的生命周期是随着进程的

5.管道只能单向通信,就是一个写,一个读,这也叫半双工

PIPE_BUF

PIPE_BUF是一个常量,它是由大小的

就是说:每次写入管道的字节数如果小于这个值,那么就认为本次写入是原子的(安全的),就是保证内容是完整的,不会被分割

命令行管道 |

之前我们说的命令 | ,本质上就是这篇博客说的pipe

比如 

就是同时创建三个进程,两个进程之间创建好管道,第一个进程的输出当作第二个进程的输入,第二个进程的输出当作第三个进程的输入,最终效果是睡眠三秒

功能代码:创建进程池

进程池就是一次创建多个进程,然后父进程负责分发任务给各个子进程各个子进程处理完一个任务后还可以处理下一个任务,而不需要创建新的进程,这样就减少了创建和销毁进程的开销。

我们之前写bash的时候就是有一个任务bash就创建子进程,然后子进程进行进程程序替换,执行完后就退出了。

我们今天写的呢就是创建子进程后子进程一直等待父进程的命令然后执行任务,执行完任务后继续等待。

//task.hpp
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <vector>
#include<ctime>
using namespace std;

typedef void(*task)();
void task1()
{
    cout<<"do task one successfully"<<endl;
}
void task2()
{
    cout<<"do task two successfully"<<endl;
}void task3()
{
    cout<<"do task three successfully"<<endl;
}
task tasks[3]={task1,task2,task3};

//processpool.cc
#include "task.hpp"

void Usage(char *argv)
{
    cout << "please input : " << argv << " and processnum\n";
}
enum error
{
    USAGE_failed = 1,
    PIPE_failed,
};

class channel
{
public:
    channel(int wfd, int id, int n)
        : _wfd(wfd), _id(id), _name("channel-" + to_string(n))
    {}
    void print()
    {
        cout << "name is " << _name << "id is " << _id << "  wfd is " << _wfd << endl;
    }

    int wfd()
    {
        return _wfd;
    }
    ~channel() {}

private:
    int _wfd;
    pid_t _id;
    string _name;
};

class processpool
{
public:
    processpool(int size)
        : _size(size)
    {
        for (int i = 1; i <= size; i++)
        {
            int pipefd[2] = {0};
            int ret = pipe(pipefd);
            if (ret == -1)
            {
                cout << "pipe failed : errno is " << errno << "error describe is " << strerror(errno) << endl;
                exit(PIPE_failed);
            }
            pid_t id = fork();
            if (id == 0)
            {
                // 子进程
                for (int j = pipefd[0] + 1; j <= pipefd[1]; j++)
                {
                    close(j);
                }
                // 等待任务
                while (1)
                {
                    int buffer = 0;
                    int n = read(pipefd[0], &buffer, sizeof(buffer));
                    if (n != 0)
                    {
                        cout << "child" << i << " " << getpid() << " "; 
                        tasks[buffer]();//执行任务
                    }
                    if (n == 0)
                        break;
                }
                exit(0);
            }
            channels.push_back({pipefd[1], id, i});
            close(pipefd[0]);
        }
    }
    void print()
    {
        for (auto &e : channels)
        {
            e.print();
        }
    }
    void get_wfd(vector<int> &f)
    {
        for (auto &e : channels)
        {
            f.push_back(e.wfd());
        }
    }

private:
    vector<channel> channels;
    int _size;
};
void give_task(vector<int> &wfd)
{
    int n = wfd.size();
    for (int i = 0; i < 100; i++)
    {
        int tasknum = rand() % (sizeof(tasks) / sizeof(tasks[0]));
        write(wfd[i % n], &tasknum, sizeof(tasknum));//按顺序选择管道,派发随机任务
        sleep(1);
    }
}

int main(int argc, char *argv[])
{
    srand((unsigned int)time(nullptr));
    if (argc != 2)
    {
        Usage(argv[0]);
        return USAGE_failed;
    }
    int processnum = stoi(argv[1]);
    processpool pool(processnum);//创建进程池

    vector<int> wfd;
    pool.get_wfd(wfd);//都可以去给哪个文件描述符给任务
    give_task(wfd);//发送任务

    return 0;
}

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

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

相关文章

1.27、基于径向基神经网络的曲线拟合(matlab)

1、基于径向基神经网络的曲线拟合简介及原理 1)原理简介 基于径向基神经网络(Radial Basis Function Neural Network, RBFNN)的曲线拟合是一种常用的非线性拟合方法,通过在输入空间中使用径向基函数对数据进行处理,实现对非线性关系的拟合。 RBFNN的基本原理是将输入空…

opencascade AIS_InteractiveContext源码学习8 trihedron display attributes

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是&#xff0c;对于已经被交互上下文识别的交互对象&#xff0c;必须使用上下文方法进行…

Hash表(C++)

本篇将会开始介绍有关于 unordered_map 和 unordered_set 的底层原理&#xff0c;其中底层实现其实就是我们的 Hash 表&#xff0c;本篇将会讲解两种 Hash 表&#xff0c;其中一种为开放定址法&#xff0c;另一种为 hash 桶&#xff0c;在unordered_map 和 unordered_set 的底层…

【Git从入门到精通】——Git常用命令总结

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

火星全球彩色影像图介绍(中分辨率相机)

一、数据基本信息 该数据是利用天问一号轨道器中分辨率相机获取的影像经光度校正、几何校正、全球制图等制作而成的全火星地图数据DOM&#xff0c;每个数据包含一个tif数据文件。该影像图分辨率为76米。 任务型号&#xff1a;天问一号 搭载平台&#xff1a;环绕器 数据获…

批量提取网页表格内容至excel文件

问题背景 将网页的表格内容&#xff08;5237个股票信息&#xff09;复制粘贴到excel文件中 网址&#xff1a;A股上市公司名单-A股上市公司名录-A股上市公司大全-商业计划书-可研报告-中商产业研究院数据库-中商情报网 实现代码 # 导入包 import pandas as pd import time# 创…

[安洵杯 2019]easy_web1

知识点&#xff1a; 1.base64加解密 2.md5加解密 3.md5碰撞绕过强类型比较 4.Linux命令绕过 进入页面发现url地址中存在 img参数和一个cmd参数&#xff0c;img参数看上去像是base64编码&#xff0c;可以去尝试一下解码. 进行了两次base64解密得到3535352e706e67看着像16进制那么…

SSM整合--笔记总结

1.概述 ssm(springmvc spring mybatis)这三个框架的整合。 spring和springmvc他们隶属于一家公司&#xff0c;他们无需整合。 spring和mybatis框架的整合。 spring把mybatis中的配置内容放到自己的配置文件中。因为我们可以让tomcat加载spring配置文件。 思考:mybatis配置文件…

SD card知识学习

一、基础知识 1、简介 SD Card 全称(Secure Digital Memory Card)&#xff0c;日本电子公司松下&#xff08;Panasonic&#xff09;、瑞典公司爱立信&#xff08;Ericsson&#xff09;、德国公司西门子&#xff08;Siemens&#xff09;共同开发的&#xff0c;于1999年发布根…

Java中如何发送短信?(荣耀典藏版)

大家好&#xff0c;我是月夜枫~~ 本来是没计划写这方面的文章&#xff0c;奈何粉丝经常私信要求整理一篇发短信的文章&#xff0c;今天他来了。 很多业务场景里&#xff0c;我们都需要发送短信&#xff0c;比如登陆验证码、告警、营销通知、节日祝福等等。 这篇文章&#xf…

JavaSE——集合框架二(4/6)-Map集合的遍历方式(键找值,键值对,Lambda)、Map集合案例(需求与分析,问题解决)

目录 Map集合的遍历方式 键找值 键值对 Lambda Map集合案例 需求与分析 问题解决 Map集合的遍历方式 键找值 先获取Map集合全部的键&#xff0c;再通过遍历键来找值。 键值对 把“键值对”看成一个整体进行遍历&#xff08;较为复杂&#xff09; Lambda JDK 1.8 开…

C嘎嘎:函数模版和类模版

目录 泛型编程 函数模版 函数模版概念 函数模版的格式 函数模版的原理 函数模版的实例化 函数参数的匹配原则 类模版 类模版的定义格式 类模版的实例化 泛型编程 如何实现一个通用的交换函数呢 void Swap(int& left, int& right) {int temp left;left rig…

【每日一练】python之sum()求和函数实例讲解

在Python中&#xff0c; sum()是一个内置函数&#xff0c;用于计算可迭代对象&#xff08;如列表、元组等&#xff09;中所有元素的总和。如下实例&#xff1a; """ 收入支出统计小程序 知识点:用户输入获取列表元素添加sum()函数&#xff0c;统计作用 "&…

快捷:通过胶水语言实现工作中测试流程并行、加速

通过胶水语言实现工作中测试流程并行、加速 通过胶水语言实现工作中测试流程并行、加速工作场景&#xff08;背景&#xff09;问题抽象&#xff08;挑战&#xff09;如何做&#xff08;行动&#xff09;获得了什么&#xff08;结果&#xff09;后记相关资源 通过胶水语言实现工…

Oracle 性能诊断包收费依据

Which Data Dictionary or Dynamic Performance Views Require Purchase of the Diagnostics and / or Tuning Pack? (Doc ID 2082355.1)​编辑To Bottom In this Document Goal Solution References APPLIES TO: Oracle Database - Enterprise Edition - Version 10.2.0.5 …

AI口语练习APP主要功能

AI口语练习APP主要功能可以分为以下几个方面&#xff0c;AI口语练习APP可以帮助用户克服练习口语的场地、时间、语言环境等限制&#xff0c;更方便、高效地练习口语&#xff0c;提高英语口语水平。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎…

Profibus协议转Profinet协议网关模块连接智能电表通讯案例

一、背景 在工业自动化领域&#xff0c;Profibus协议和Profinet协议是两种常见的工业通讯协议&#xff0c;而连接智能电表需要用到这两种协议之间的网关模块。本文将通过一个实际案例&#xff0c;详细介绍如何使用Profibus转Profinet模块&#xff08;XD-PNPBM20&#xff09;实…

电脑案件冲突问题

一.故障展示 有一天我打开了电脑,发现3这个数字按键一直在输入,拔了外界的键盘,他这个按键还是会冲突 ,就如同上面的图一样 ,可能是电脑内部的键位进了灰卡住了什么东西导致的,于是我果断就电脑上的按键给扣下来了,扣的时候不知道里面的结构非常的谨慎,所以没导致里面的结构被损…

Amazon EC2 部署Ollama + webUI

最近和同事闲聊&#xff0c;我们能不能内网自己部署一个LLM&#xff0c;于是便有了Ollama webUI的尝试 对于Linux&#xff0c;使用一行命令即可 curl -fsSL https://ollama.com/install.sh | shollama --help Large language model runnerUsage:ollam…

C语言 ——— const关键字

目录 const修饰变量 const修饰指针变量 const放在指针类型之前 const放在指针类型之后 小结 const修饰变量 当 const 修饰 int类型 的 变量a 后&#xff0c;此时的 变量a 就具有长属性&#xff0c;就不能被赋值为其他的值 将 变量a的地址 存储到 指针变量pa 中&#xff…