Linux进程间通信(一):匿名管道的原理和使用

news2025/1/23 13:01:24

文章目录

    • 一、前言
    • 二、什么是匿名管道?
    • 三、匿名管道的原理
    • 三、匿名管道的创建
    • 四、匿名管道实现数据传输
    • 五、匿名管道实现进程控制
    • 六、匿名管道特点总结

一、前言

(在阅读本文前,需要具备Linux基础IO的基本知识)
 在某些特定情况下,我们需要多进程协同处理任务,此时我们就需要进程交互。然而我们知道进程之间是具有的独立性的,因此数据交互的成本较高。在这种矛盾的推动下,进程间通信就应运而生了,它主要有以下四种目的:

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

​ 两个进程之间是没有交集的,因此进程间通信的本质在于如何让两个进程看到同一份资源,而不在于如何通信,资源的不同决定了通信方式的不同。

​ 当我们使用 cat() 或者 echo() 指令打印某个文件的数据时,其实就是一种非常原始的通信——cat进程和echo进程看到了同一份磁盘资源。但是磁盘的读写速度是非常慢的,我们期待实现内存级别的进程间通信,因此需要以内存作为数据的缓冲区。

二、什么是匿名管道?

 匿名管道想必大家都接触过,其实就是命令行窗口中的 | ,例如下图这个例子:匿名管道 |yum list 指令的输出结果作为 grep ncurses 指令的输入数据。为什么要叫它管道呢?其实也不难理解,谈到管道大家可能会联想到石油,而数据就是计算机世界中的石油,传输数据的媒介自然叫管道。

 一个指令其实就对应着一个进程,因此,通过匿名管道,我们实现了 yum 进程和 grep 进程间的数据交互
在这里插入图片描述
从上面的例子中,我们也能初步总结出管道的特点(对命名管道同样适用):

  • 管道是用于传输数据的
  • 管道传递数据都是单向

三、匿名管道的原理

 当我们使用 fork() 函数创建子进程的时候,子进程的内核数据结构基本拷贝自父进程,自然而然的子进程也会继承父进程的 文件描述符表 (Linux基础IO(二):深入理解Linux文件描述符),这也就就意味着父进程所打开的文件和子进程打开的是完全一样的。父子进程向同一个文件中读取或写入,由此实现进程间通信。

 然而上文中我们也谈到,磁盘的访问速度太慢,我们需要实现内存级别的进程间通信。因此父进程所打开的管道文件其实是一种特殊的文件,它与磁盘去关联,写入时直接向缓冲区中写入,读取时也直接从缓冲区中读取,由此实现内存级别的通信。

[问题一]:如何区分普通文件和管道文件?

// 在inode结构体下可以看到如下的联合体(adree_space包含有struct inode)
struct inode
{
  union {
		struct pipe_inode_info	*i_pipe;  // 管道设备
		struct block_device	*i_bdev;      // 磁盘设备
		struct cdev		*i_cdev;          // 字符设备
	};
}

底层是通过一个联合体来区分文件属性的,当创建一个管道的时候,对应的管道字段就会生效

image-20221113195145304

[问题二]:为什么父进程要先后以读写的方式打开同一个管道文件?
 答:子进程拷贝父进程后,就不需要再以读或者写的方式打开管道文件了。


[问题三]:为什么父子进程要分别关闭读端和写端
 答:确保管道通信的单向性,例如上图中数据只能从父进程流向子进程

[问题四]:父进程究竟是关闭读端还是关闭写端是由什么决定的?
 答:由用户需求决定。如果希望数据从父进程流向子进程,就关闭父进程的读端,子进程的写端;如果希望数据从子进程流向父进程,就关闭父进程的写端,子进程的读端

三、匿名管道的创建

image-20221113222841865
[作用]:创建匿名管道
[参数说明]: pipefd[2]是一个返回型参数

  • pipefd[0]存储以读的方式打开管道文件所返回的文件描述符
  • pipefd[1]存储以写的方式打开管道文件所返回的文件描述符

(记忆方式:0 → 嘴巴 → 读     1 → 笔 → 写)
[返回值]:创建管道成功,返回0;失败,返回-1
[函数说明]:

  • pipe() 函数自动以读写的方式打开同一个管道文件并将其返回给 pipefd[2]
  • pipe函数属于系统调用,因此操作系统可以直接在内核中将文件类型设置为管道

四、匿名管道实现数据传输

// 使用案例:数据从父进程传递给子进程
int main()
{
    int pipefd[2] = {0};
    if(pipe(pipefd) != 0)         // 创建管道失败
    {
        cerr << "pipe" << endl;
        return 1;
    }

    pid_t pid = fork();

    if(pid == 0) // 子进程关闭写端
    {
        close(pipefd[1]);
        char buff[20];
        while(true)
        {
            ssize_t ss = read(pipefd[0], buff, sizeof(buff));
            if(ss == 0)
            {
                cout << "父进程不写入了,我也不读取了"  << endl;
                break;
            }
            buff[ss] = '\0';
            cout << "子进程收到消息:" << buff << " 时间:" << time(nullptr) << endl;
        }
    }
    else        // 父进程关闭读端
    {
        close(pipefd[0]);   
        char msg[] = "hello world!";
        for(int i = 0; i < 5; i++)
        {
        	// 不要写入字符串末尾的'\0'
        	// ‘\0’结尾是C语言的标准,文件可不吃这一套
            write(pipefd[1], msg, sizeof(msg) - 1);   
            sleep(1);
        }
        cout << "父进程写入完毕" << endl;   
        close(pipefd[1]);
        waitpid(pid, nullptr, 0);   
    }
    
    return 0;
}

在这里插入图片描述

[问题五]:为什么子进程没有sleep,但是会随着父进程休眠呢?(观察时间戳)
 答:pipe函数自带访问控制机制,父子读写时具有一定的顺序性:

  • 当一个进程尝试从一个空的管道中读取时,read接口会被阻塞直到管道内有数据为止
  • 当一个进程尝试向一个满的管道中写入时,write接口会被阻塞直到足量的数据从管道中被读取走为止


[问题六]:子进程如何感知父进程关闭管道了呢?
 答:每当一个进程打开一个文件的时候,该文件的引用计数会加一;每当一个进程关闭一个文件的时候,该文件的引用计数会减一。当一个文件的引用计数减为0时,表明没有进程打开这个文件,那么这个文件会真正被关闭。

 当管道文件的引用计数为1时,表明父进程已经关闭管道文件,子进程读完当前消息就可以作为文件的结尾而退出了。因此子进程是可以感知父进程是否关闭写端的。

五、匿名管道实现进程控制

  1. 创建多个管道文件(其实一个也可以)
  2. 创建多个子进程
  3. 父进程随机分配任务(指定哪个进程,指定什么任务)
  4. 子进程处理任务

在这里插入图片描述

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <ctime>
#include <cassert>
#include <vector>

using namespace std;

typedef void(*function)();

vector<function> task;   

void func1()
{
    cout << "正在执行网络任务……" << "  时间" << time(nullptr) << endl;
}

void func2()
{
    cout << "正在执行磁盘任务……" << "  时间" << time(nullptr) << endl;
}

void func3()
{
    cout << "正在执行驱动任务……" << "  时间" << time(nullptr) << endl;
}

void LoadFunc()           // 加载任务到vector中
{
    task.push_back(func1);
    task.push_back(func2);
    task.push_back(func3);
}

int main()
{
    LoadFunc();
    srand((unsigned int)time(nullptr) ^ getpid());
    int pipefd[2] = {0};
    if(pipe(pipefd) != 0)
    {
        cerr << "pipe" << endl;
        return 1;
    }

    pid_t pid = fork();
    
    if(pid == 0)
    {
        close(pipefd[1]);
        while(true)
        {
            uint32_t n = 0;
            ssize_t ss = read(pipefd[0], &n, sizeof(uint32_t));
            if(ss == 0)
            {
                cout << "我是打工人,老板走了,我也下班了" << endl;
                break;
            }

            assert(ss == sizeof(uint32_t));

            task[n]();
        }
    }
    else 
    {
        close(pipefd[0]);
        for(int i = 0; i < 10; i++)
        {
            uint32_t ss = rand() % 3;
            write(pipefd[1], &ss, sizeof(uint32_t));
            sleep(1);
        }
        cout << "任务全部处理完毕" << endl;
        close(pipefd[1]);
        waitpid(pid, nullptr, 0);
    }

    return 0;
}

在这里插入图片描述

六、匿名管道特点总结

  1. 匿名管道只能用于具有血缘关系的进程之间。通常用于父子进程之间通信(兄弟之间也可以:父进程打开管道后创建两次子进程,再将父进程的管道的读写端关闭,就可以实现两个兄弟进程间的通信)
  2. 管道必须是单向的,Linux内核在设计的时候就是当管道为单向的
  3. 管道自带同步机制——自带访问控制
  4. 管道是面向字节流的。先写的字符一定是先被读取的、读取时没有格式边界需要用户来限制内容的边界(例如在使用read函数时我们要指定读取多少个字节)
  5. 管道也是文件,当进程退出管道的引用计数被减为0时管道的生命周期就结束了

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

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

相关文章

【Linux】Linux下基本指令(二)

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《Linux》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1. Linux基本指令&#xff1a;&#xff08;续&#xff09; 1.1man指令&am…

英国访问学者AV签证超期怎么办?

英国AV签证只给12个月整&#xff0c;关于 AV签证超期怎么办&#xff1f;知识人网访问学者老师和大家聊一聊&#xff1a; 一、呆不满一年(360天)会遇到的问题主要有&#xff1a; 1、无法申请留学回国科研启动基金。 2、需要向CSC返还一定的资助费用。 3、国内一些地方判断一个…

数字化转型如何认清本质少被忽悠:小步快跑看到项目效果再推下一步

这些年&#xff0c;我们见识了太多新概念&#xff1a;数据智能、Data Fabric、数据虚拟化还有最著名的“数据中台”。 然而&#xff0c;跟几年前疯狂追逐这些热词不同&#xff0c;或者说因为已经踩坑踩到晕厥。动辄一个千万的中台项目投入进去&#xff0c;上线后高层看数还是要…

【网络安全】Redis未授权访问查看敏感文件

前言 什么是未授权访问漏洞呢&#xff1f;也就是没有权限&#xff0c;但是可以访问站点或者服务器的漏洞&#xff0c;称之为未授权访问漏洞&#xff0c;这个属于高危漏洞。比如我们常见的路由器未授权访问等等。 一、准备阶段 service httpd.service start 开启apache服务se…

【文档编辑】积累

文章目录WordExcel填充表格样式函数Markdownsublime搜索jsonhexWord Word 2016 撰写论文(1): 公式居中、编号右对齐 Excel 填充 EXCEL excel中运用ctrlD、ctrlenter、ctrlE批量填充数据 表格样式 Excel技法&#xff1a;三招实行Excel隔行填色 函数 COUNTA 函数 step1&…

最小生成树算法:Kruskal 与 Prim算法

Ⅰ. 最小生成树 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成树就不再连通&#xff1b;反之&#xff0c;在其中引入任何一条新边&#xff0c;都会形成一条回路。 若连通图由 n 个顶点组成&…

向前主动防御 | 云蜜罐年底感恩回馈活动

安全事件频发&#xff0c;防御能力备受考验2022年接近尾声&#xff0c;回顾全年&#xff0c;数据泄露、网络攻击、漏洞发现等各个层面呈爆发态势&#xff0c;无论在数量还是影响面上&#xff0c;均超过以往任何年度。信息泄露创历史新高&#xff0c;2022年仅上半年泄露或被盗的…

计算机组成原理——期末复习题

113、计算机系统如何进行多级划分&#xff1f;这种分级观点对计算机设计会产生什么影响&#xff1f;答案&#xff1a;计算机系统通常由五个以上不用的级组成&#xff0c;具体如下&#xff1a; 第1级是微程序设计级或逻辑电路级&#xff0c;该级由硬件直接执行&#xff1b; 第…

异步代码处理

在Spring中&#xff0c;实现异步调用主要有三种方式&#xff1a; 方式一&#xff1a;注解方式 要开启异步支持&#xff0c;首先得在Spring Boot入口类上加上EnableAsync注解&#xff1a; SpringBootApplication EnableAsync public class DemoApplication {public static voi…

算法leetcode|24. 两两交换链表中的节点(rust重拳出击)

文章目录24. 两两交换链表中的节点&#xff1a;样例 1&#xff1a;样例 2&#xff1a;样例 3&#xff1a;提示&#xff1a;分析&#xff1a;题解&#xff1a;rustgoccpythonjava24. 两两交换链表中的节点&#xff1a; 给你一个链表&#xff0c;两两交换其中相邻的节点&#xf…

百倍加速IO读写!快使用Parquet和Feather格式!⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 数据分析实战系列&#xff1a;https://www.showmeai.tech/tutorials/40 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/409 &#x1f4e2; 声明&#xff1a;版权所有&#xff0c;转…

你碰到过这8种Spring事务失效的场景的那几种?

前言 作为Java开发工程师&#xff0c;相信大家对Spring种事务的使用并不陌生。但是你可能只是停留在基础的使用层面上&#xff0c;在遇到一些比较特殊的场景&#xff0c;事务可能没有生效&#xff0c;直接在生产上暴露了&#xff0c;这可能就会导致比较严重的生产事故。今天&a…

产业智能化创新标杆 2022年度“飞桨产业应用创新奖”颁布

随着AI进入工业大生产阶段&#xff0c;更多垂直行业正在与AI深度融合&#xff0c;更多创新实践不断涌现。日前&#xff0c;WAVE SUMMIT 2022深度学习开发者峰会在线上召开&#xff0c;百度发布了飞桨产业级深度学习平台和文心大模型的生态成果和最新进展&#xff0c;重磅颁发了…

MySQL高级【索引分类】

目录 1&#xff1a;索引分类 1.1&#xff1a;索引分类 1.2&#xff1a;聚集索引&二级索引 2&#xff1a;索引语法 1&#xff1a;索引分类 1.1&#xff1a;索引分类 在MySQL数据库&#xff0c;将索引的具体类型主要分为以下几类&#xff1a;主键索引、唯一索引、常规索…

(2022年12月最新)spring-core-rce漏洞复现CVE-2022-22965

1、漏洞简介 2022年3月29日&#xff0c;Spring框架曝出RCE 0day漏洞。已经证实由于 SerializationUtils#deserialize 基于 Java 的序列化机制&#xff0c;可导致远程代码执行 (RCE)&#xff0c;使用JDK9及以上版本皆有可能受到影响。 通过该漏洞可写入webshell以及命令执行。在…

数据结构C语言版 —— 二叉树的顺序存储堆的实现

二叉树顺序结构实现(堆) 1. 堆的概念 堆在物理上是一个一维数组&#xff0c;在逻辑上是一颗完全二叉树满足父亲节点小于等于孩子节点的叫做小堆或者小根堆满足父亲节点大于等于孩子节点的叫做大堆或者大根堆 堆的孩子和父亲的下标关系 已知父亲(parent)的下标 左孩子(left)下…

基于昇思MindSpore实现使用胶囊网络的图像描述生成算法

基于昇思MindSpore实现使用胶囊网络的图像描述生成算法 项目链接 https://github.com/Liu-Yuanqiu/acn_mindspore 01 项目描述 1.1 图像描述生成算法 人类可以轻易的使用语言来描述所看到的场景&#xff0c;但是计算机却很难做到&#xff0c;图像描述生成任务的目的就是教…

昇思MindSpore动静结合中list和dict方法实现

01 概述 静态图和动态图是神经学习框架中的重要概念&#xff0c;昇思MindSpore同时支持动态图和静态图两种模式&#xff0c;在动态图与静态图的结合方面做了很多工作。本文以昇思MindSpore框架中图模式下list和dict的实现方式为例&#xff0c;介绍昇思MindSpore框架中的动静结…

C与C++如何互相调用

个人主页&#xff1a;董哥聊技术我是董哥&#xff0c;嵌入式领域新星创作者创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01;文章目录1、为什么会有差异&#xff1f;2、extern "C"3、C调用C正确方式4、C调用C5、总结在项目开发过…

[第十二届蓝桥杯/java/算法]C——卡片

&#x1f9d1;‍&#x1f393;个人介绍&#xff1a;大二软件生&#xff0c;现学JAVA、Linux、MySQL、算法 &#x1f4bb;博客主页&#xff1a;渡过晚枫渡过晚枫 &#x1f453;系列专栏&#xff1a;[编程神域 C语言]&#xff0c;[java/初学者]&#xff0c;[蓝桥杯] &#x1f4d6…