Linux 进程间通信之管道

news2024/11/14 21:39:34

个人主页:仍有未知等待探索-CSDN博客

专题分栏: Linux

目录

一、通信

1、进程为什么要通信?

1.数据的类型

2.父进程和子进程算通信吗?

2、进程如何通信?

3、进程通信的常见方式?

二、管道 

1、概念

1.管道的4种情况:

2.管道的5种特征:

2、匿名管道

1.为什么父子进程会向同一个显示器终端打印数据?

2.进程默认会打开三个标准输入输出:0,1,2 ?

 3.为什么我们子进程主动close(0/1/2),不影响父进程继续使用显示器文件呢?

4.什么是管道文件?

5.管道文件的特点?

6.管道文件结构图

7.父子既然要关闭不需要的fd,那为什么要打开?

8.可以不关不需要的fd吗?

9.如果想双向通信,怎么办?

10.为什么要单向通信?

11.管道的使用(*)

12.read、write的注意点:

read的返回值:

write:

13.命令行中的管道符号:‘ | ’

14.管道多次创建的示意图(*)

 3、命名管道

1.原理

2.匿名管道和命名管道的区别

3.怎么保证两个毫不相干的进程,打开了同一个文件呢?

4.命名管道操作 --- 系统调用篇

5.命名管道操作 --- 指令篇

三、管道的项目 --- 进程池

process_pool.cc

task.hpp

Makefile


一、通信

1、进程为什么要通信?

进程也是需要某种协同的,所以如何协同的前提是通信。

1.数据的类型

通知就绪的、单纯的要传递的数据、控制相关的信息...

2.父进程和子进程算通信吗?

答案:不算。

1、一直通信能通信不是同一个概念。

2、父子进程只能读父子进程共享的变量内容,而且还不能进行修改,修改了就会发生写时拷贝,数据不一致。

2、进程如何通信?

进程间通信的前提是

让两个进程能同时看到同一份资源(也就是操作系统的一段空间)。

因为不同的进程之间互相是独立的,所以不同的进程独自创建的空间,其他进程是不知道的。所以需要第三方的协助(也就是操作系统)。

但是,操作系统不能被用户直接的访问资源,所以操作系统需要提供对应的系统调用。

如果对于下列的图不明白的话,建议去看看进程相关的知识。

这两篇博客讲的都是和进程相关的内容

Linux 冯诺依曼体系、操作系统、进程概念、进程状态、进程切换-CSDN博客

Linux 进程优先级、程序地址空间、进程控制-CSDN博客

3、进程通信的常见方式?

二、管道 

1、概念

1.管道的4种情况:

1、如果管道内部时空的 && write fd没有关闭,读取条件不具备,读进程会被阻塞 --- wait --- 等到读取条件具备 --- 再进行写入 --- 之后再进行读数据。管道空且写未关,读阻塞

2、如果管道被写满 && 不读且没有关闭,写条件不具备,写进程会被阻塞 --- wait --- 等到写条件具备 --- 写入数据。管道满且读未关,写阻塞

3、管道一直在读&&写端关闭了wfd,读端read返回值会读到0,表示读到文件结尾。读且写关闭,读到文件尾,结束

4、rfd直接关闭,写端wfd一直进行写入。这种管道叫broken pipe,os不做浪费时空的事情,os会杀掉对应的进程,给目标发送(13 SIGPIPE)信号。写且读关闭,写进程被杀掉。

2.管道的5种特征:

1、匿名管道:只能用来进行具有父子进程之间通信。(这里的父子进程是泛泛的,爷孙进程也可以)。

2、管道内部,自带进程之间的同步机制。同步:多执行流执行代码的时候,具有明显的顺序性。

3、管道的生命周期是随进程的

4、管道在通信的时候,是面向字节流的。 write的次数和读取的次数不是一一匹配的。

5、管道的通信模式,是一种特殊的半双工模式。(半双工:在同一时刻,只能有一个方向的数据传输)

2、匿名管道

1.为什么父子进程会向同一个显示器终端打印数据?

因为父子进程代码和数据在没有修改的时候是属于共享的,所以子进程创建的时候,会直接将父进程的task_struct拷贝一份,当然,也包括其中的文件描述符表,所以只要父进程的文件描述符表中存储了标准输入、标准输出、标准错误,子进程也就会有标准输入、标准输出、标准错误。

bash进程可以想象成树形结构的根节点。所以只要将bash的文件描述符表设置好,所有的进程就都会在运行的之后默认打开这三个标准文件。

2.进程默认会打开三个标准输入输出:0,1,2 ?

bash打开了,所有子进程默认也就打开了,我们只需要做好约定即可!

 3.为什么我们子进程主动close(0/1/2),不影响父进程继续使用显示器文件呢?

因为,struct file中也有内存级引用计数。

父子同时打开了显示器文件,所以显示器文件的引用计数为2,当关闭了子进程的显示器文件,引用计数减一,只有当引用计数为0的时候,才会释放资源。

struct file -> ref_count; if (ref_count==0) 释放文件资源。

4.什么是管道文件?

管道文件:管道文件是一种特殊的文件,它存在于内存中,而不是磁盘上。它允许一个进程的输出直接作为另一个进程的输入,从而实现进程间的数据交换和协同工作。

5.管道文件的特点?

  • 管道只允许单向通信。

  • 管道文件不需要刷新到磁盘中(所以需要单独设计通信接口)。

6.管道文件结构图

7.父子既然要关闭不需要的fd,那为什么要打开?

为了让子进程继承下去。

8.可以不关不需要的fd吗?

可以,但是建议关闭,因为可能会误写。

9.如果想双向通信,怎么办?

可以建立两个管道。

一个管道,父读子写;一个管道父写子读。

10.为什么要单向通信?

因为简单,并且保证数据的安全性。

公共资源可能会存在被多个进程同时访问的情况。数据不一致问题。比如说子进程写了一半,父进程就开始读。

11.管道的使用(*)

#include <iostream>
#include <string>
#include <cerrno>  // errno.h
#include <cstring> // string.h
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>

void subprocess_write(int);
void fatherprocess_read(int);

// 子写父读
int main()
{
    // 创建管道
    int pipefd[2] = {0};
    int exitcode = pipe(pipefd);

    std::cout << "pipefd[0]: " << pipefd[0] << " pipefd[1]: " << pipefd[1] << std::endl;
    
    // 创建子进程
    int id = fork();

    // 子写父读
    if (id == 0)
    {
        std::cout << "子进程关闭不需要的fd, 准备发消息" << std::endl;
        sleep(1);
        // child
        // 关闭其他权限
        close(pipefd[0]);

        // 子进程写
        subprocess_write(pipefd[1]);

        close(pipefd[1]);
        exit(0);
    }
    std::cout << "父进程进程关闭不需要的fd, 准备收消息" << std::endl;
    sleep(1);
    // father
    // 关闭不需要的fd
    close(pipefd[1]);

    // 父进程读
    fatherprocess_read(pipefd[0]);

    close(pipefd[0]);

    pid_t rid = waitpid(id, nullptr, 0);
    if (rid > 0)
    {
        std::cout << "wait sucessfully\n" << std::endl;
    }
    return 0;
}

std::string other()
{
    static int cnt = 0;
    std::string messageid = std::to_string(cnt);
    cnt++;
    std::string stringid = std::to_string(getpid());
    std::string message = messageid + " my pid is " + stringid;

    return message;
}
void subprocess_write(int wfd)
{
    std::string message = "i am child: ";
    while (true)
    {
        std::string info = message + other();
        write(wfd, info.c_str(), info.size());// 写入管道的时候没有写入'\0'
        sleep(1);
    }
}
void fatherprocess_read(int rfd)
{
    char inbuffer[1024] = {0};
    while (true)
    {
        std::cout << "---------------------------------" << std::endl;
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            std::cout  << inbuffer << std::endl;
        }
        else if (n == 0)
        {
            std::cout << "读端关闭" << std::endl;
            break;
        }
        else 
        {
            std::cout << "读入失败" << std::endl;
            break;
        }
    }
}

12.read、write的注意点:

read的返回值:
  • 返回值 > 0,读取成功,且返回值是读取的字节数。
  • 返回值 = 0,读取结束。写端关闭,导致读端读到文件尾结束。
  • 返回值 < 0,读取失败。
write:

如果每次写的数据的大小 小于 pipe_buf(<= 512Byte,在Linux中是4096Byte),写入操作的过程是原子的。(原子的意思就是不可再分,证明这个操作是线程安全的)

13.命令行中的管道符号:‘ | ’

命令行中的 |,就是匿名管道,‘|’ 左侧命令的stdout 作为 右侧命令的stdin。

14.管道多次创建的示意图(*)

创建了两个管道:

创建了三个管道:

 3、命名管道

1.原理

是不是感觉很眼熟,和匿名管道的文件结构图差不多,其实就是差不多。

只不过这两个管道有一个非常显著的不同。

2.匿名管道和命名管道的区别

  • 匿名管道:两个通信的进程之间是有血缘关系的。(父子进程,爷孙进程...)
  • 命名管道:两个通信的进程之间没有血缘关系。
  • 匿名管道:不需要文件路径。
  • 命名管道:需要文件路径。

3.怎么保证两个毫不相干的进程,打开了同一个文件呢?

通过文件的路径来确认。每一个文件,都有文件路径(唯一性)。

4.命名管道操作 --- 系统调用篇

5.命名管道操作 --- 指令篇

三、管道的项目 --- 进程池

有 n 个管道,n 个任务,要保证每条管道都负载均衡。

process_pool.cc


#include "Task.hpp"

// 父进程写,子进程读
class Channel
{
private:
    int _wfd;
    int _id;

public:
    Channel(int wfd, int id)
        : _wfd(wfd), _id(id)
    {
    }
    void close_subprocess()
    {
        close(_wfd);
    }
    int get_wfd()
    {
        return _wfd;
    }
    int get_id()
    {
        return _id;
    }
};

void work(int rfd);
void create_ChannelAndSubprocess(std::vector<Channel> *channels, int child_number, work_t work);
int next_channel(std::vector<Channel> &channels);
void send_command(Channel &channel, int option_task);
void ctrl_channel(std::vector<Channel> &channels);
void clean_ChannelAndSubprocess(std::vector<Channel> *channels);
int main(int argv, char* argc[])
{
    std::vector<Channel> channels;
    load_task();

    if (argv != 2)
        return -1;
    int num = std::stoi(argc[1]);
    // 1、创建信道和子进程
    create_ChannelAndSubprocess(&channels, num, work);

    // 2、通过信道控制子进程
    ctrl_channel(channels);

    // 3、回收管道和子进程
    clean_ChannelAndSubprocess(&channels);
    return 0;
}

void create_ChannelAndSubprocess(std::vector<Channel> *channels, int child_number, work_t work)
{
    for (int i = 0; i < child_number; i++)
    {
        int pipefd[2] = {0};
        int exit_code = pipe(pipefd);
        if (exit_code < 0)
        {
            std::cout << "errno : " << exit_code << std::endl;
            exit(1);
        }
        pid_t id = fork();
        if (id == 0)
        {
            // child;
            close(pipefd[1]);
            work(pipefd[0]);
            exit(0);
        }
        // father
        close(pipefd[0]);

        (*channels).emplace_back(pipefd[1], id);
    }

    std::cout << "创建了:" << channels->size() << "个信道和子进程" << std::endl;
}
int next_channel(std::vector<Channel> &channels)
{
    static int count = 0;
    int option = count;
    count++;
    count %= channels.size();
    return option;
}
void send_command(Channel &channel, int option_task)
{
    write(channel.get_wfd(), &option_task, sizeof(option_task));
}
void ctrl_channel(std::vector<Channel> &channels)
{
    // 选择任务
    srand(time(nullptr));
    int n = task_number;
    while (n -- )
    {
        int option_task = rand() % task.size(); // task[option_task]
        // 选择信道
        int option_channel = next_channel(channels); // channels[option_task]
        // 发送任务
        send_command(channels.at(option_channel), option_task);
        sleep(1);
    }
}
void clean_ChannelAndSubprocess(std::vector<Channel> *channels)
{
    for (auto& channel:*channels)
    {
        channel.close_subprocess();
    }
    for (auto& channel:*channels)
    {
        waitpid(channel.get_id(), 0, 0);
    }
}

task.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>

typedef void (*task_t)();
typedef void (*work_t)(int);

const int size = 4096;
const int task_number = 7;
// 加载
void download()
{
    std::cout << "This is a downlaod task!" << std::endl;
}
// 打印
void print()
{
    std::cout << "This is a print task!" << std::endl;
}
// 刷新
void flush()
{
    std::cout << "This is a flush task!" << std::endl;
}

std::vector<task_t> task;

void load_task()
{
    task.push_back(download);
    task.push_back(print);
    task.push_back(flush);
}
void excute_task(int option_task)
{
    if (option_task < 0 || option_task >= task.size())
        return;
    task.at(option_task)();
}

void work(int rfd)
{
    int t = task_number;
    int option_task = 0;
    int n = read(rfd, &option_task, sizeof(option_task));
    if (n > 0)
    {
        excute_task(option_task);
        std::cout << "--------------------------------" << std::endl;
    }
}

Makefile

process_pool:process_pool.cc
	g++ $^ -o $@ -std=c++11

.PHONY:clean
clean:
	rm -rf process_pool

谢谢大家!

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

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

相关文章

SpringBoot中整合Mybatis

一、Mybatis快速入门 1.1、在相应的模块中添加依赖的坐标 首先创建一个maven项目 在对应的pom.xml文件中引入下面的依赖 <dependencies><!--mybatis 依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artif…

【已解决】如何解决雅马哈机械手无法连接到在线模式的问题

前言 在一个项目中用到了雅马哈机器手&#xff0c;但是现场操作的时候进场出现连接不上的问题&#xff0c;经过与官方技术讨论得出以下的解决方法。 解决方法 避免这个问题出现在操作完之后必须将机械手控制器切换成离线模式。主要原因&#xff1a;1、机械手控制器本身是属…

什么是知识中台?如何搭建企业知识中台?(企业必懂术语)

一、什么是知识中台&#xff1f; 知识中台是一个集中管理、整合和共享企业知识资源的平台。它利用云计算、大数据和人工智能等技术&#xff0c;将企业内部各部门、各员工手中的知识资源进行集中整合&#xff0c;形成统一的知识库。通过这个平台&#xff0c;员工可以快速查找、…

BGP边界网关协议基础详细

1.BGP概述 1.1 BGP基础 应用层协议&#xff0c;基于TCP&#xff0c;源端口号是随机&#xff0c;目的端口是179。 无类路由协议、外部网关路由协议、采用距离(路径)矢量算法、单播路由协议 核心作用为传递路由。 1.2 AS自治系统 AS是指由同一个技术管理机构管理&#xff0…

数据可视化:解锁数据奥秘的钥匙与实战指南

如何有效地解读和利用这些数据成为了企业和个人面临的重大挑战。数据可视化&#xff0c;作为连接数据与洞察的桥梁&#xff0c;正逐步成为数据分析与决策支持不可或缺的工具。本文将深入探讨数据可视化的本质、用途分类、设计原则、高效制作技巧&#xff0c;并通过Axure产品设计…

四数相加2 | LeetCode-454 | 哈希集合 | Java详细注释

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f579;️思路&#xff1a;四数相加 > 两数相加 &#x1f4cc;LeetCode链接&#xff1a;454. 四数相加 II 文章目录 1.题目描述&#x1f34e;2.题解&#x…

事件驱动系统设计之将事件检索与事件处理解耦

0 前言 part1讨论了集成过程中遇到的挑战以及幂等事件处理的作用。解决集成问题之后&#xff0c;我们需要反思事件检索的问题。我们的经验教训表明&#xff0c;将事件检索与事件处理解耦至关重要。 1 事件处理与请求/响应 API 紧耦合 part1讨论了将请求/响应 API 集成到事件…

变量的注意或许需要调试

输入一个自然数N&#xff08;1<N<9&#xff09;&#xff0c;从小到大输出用1~N组成的所有排列&#xff0c;也就说全排列。例如输入3则输出 123 132 213 231 312 321 输入格式: 输入一个自然数N&#xff08;1<N<9&#xff09; 输出格式: N的全排列&#xff0c;每行一…

8.12 day bug

bug1 一定要记得改变this指向&#xff0c;否则调用时this不再指向实例而是调用的上下文。 This.handleChange this.handleChange.bind(this); bug2 同样的代码莫名其妙就是没行&#xff0c;复制粘贴到通义千问&#xff0c;再复制回来居然就行了&#xff0c;prompt是“编写代…

R语言中的命名规则

在R语言中&#xff0c;为变量、元素、函数或对象命名时&#xff0c;遵循一定的规则是至关重要的。这些规则确保代码的可读性、可维护性&#xff0c;并避免与R语言的内部函数和保留字产生冲突。下面我们将详细探讨R中命名的具体规则和注意事项。 1、名字中允许的字符 R语言的命…

数据库方式实现实时排行榜

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.排行榜的设计1.实时榜单1.数据库统计2.redis 的 sorted set …

25届秋招还有金九银十?会是“史上最难”?

吉祥学安全&#xff1a;知星&#x1f517;http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247483727&idx1&sndb05d8c1115a4539716eddd9fde4e5c9&chksmc0e47813f793f105017fb8551c9b996dc7782987e19efb166ab665f44ca6d900210e6c4c0281&scene21#wechat…

友元的一些资料

友元&#xff1a;可以访问私有权限下的一种代码 全局函数作友元&#xff1a; 我们创建了两个属性&#xff0c;并且通过无参构造函数给属性赋了值&#xff0c; 这个时候再创建一个goodgay类&#xff0c;形参是应用的buil1的对象&#xff0c;用这个引用的对象调用属性。所以下面…

笔记(day17)集合概述、List、Set、比较器

集合Collection 一.概述 ​ 集合可以理解为数据结构的封装,根据不同的特性及操作性能进行分类 二.继承体系 三.Collection中常用方法 ​ collection是集合中的父类,所以collection中的方法是所有集合中都有的 ​ 集合中只能保存引用类型(Object),无法保存基本类型 ​ Colle…

记一次CSDN认证模块后端未校验漏洞

前言 作为一个程序员&#xff0c;一直充满好奇心&#xff0c;没事就喜欢找找漏洞&#xff0c;试想一下某些程序是否存在某些鉴权等漏洞&#xff0c;目前该漏洞已提交官方&#xff0c;且影响不大&#xff0c;现分享分析过程用于各位技术学习。 漏洞分析 https://i.csdn.net/#…

【Hot100】LeetCode—3. 无重复字符的最长子串

目录 1- 思路滑动窗口 2- 实现⭐3. 无重复字符的最长子串——题解思路 3- ACM 实现 原题链接&#xff1a;3. 无重复字符的最长子串 1- 思路 滑动窗口 借助 HashSet 来实现判重通过指针 i 和 right 指针实现一个滑动窗口 2- 实现 ⭐3. 无重复字符的最长子串——题解思路 clas…

webrtc一对一视频通话功能实现

项目效果 实现原理 关于原理我就不做说明&#xff0c;直接看图 WebRTC建立的时序图 系统用例逻辑 搭建环境 turn服务器&#xff1a;Ubuntu24.04搭建turn服务器 mkcert的安装和使用&#xff1a;配置https访问 必须使用https协议&#xff0c; 由于浏览器的安全策略导致的&am…

图像处理中的图像梯度和幅值是什么???(通俗讲解)

在边缘检测和特征提取等任务中&#xff0c;图像的梯度和幅值是图像处理中非常重要的概念。 目录 一、图像的梯度1.1 专业解释1.2 通俗理解1.3 计算方式 二、梯度的幅值2.1 专业解释2.2 通俗理解2.3 计算方式 一、图像的梯度 1.1 专业解释 图像的梯度可以看作是图像中亮度或颜…

基于智能手机的3D模型快速生成技术

摘要&#xff1a; 本文介绍了一种创新技术&#xff0c;该技术允许用户通过智能手机拍摄视频&#xff0c;快速将2D图像转换为3D模型。这项技术为3D内容创作提供了一种高效且用户友好的解决方案。 关键词&#xff1a; 3D建模&#xff0c;智能手机&#xff0c;AI处理&#xff0c;…

【递归】1.汉诺塔问题

面试题 08.06. 汉诺塔问题 leetcode链接&#xff1a;https://leetcode.cn/problems/hanota-lcci/description/在经典汉诺塔问题中&#xff0c;有 3 根柱子及 N 个不同大小的穿孔圆盘&#xff0c;盘子可以滑入任意一根柱子。 一开始&#xff0c;所有盘子自上而下按升序依次套在第…