Linux——进程通信

news2025/5/25 15:16:13

我们知道,进程具有独立性,各进程之间互不干扰,但我们为什么还要让其联系,建立通信呢?比如:数据传输,资源共享,通知某个事件,或控制某个进程。因此,让进程间建立通信是必不可少的。但如何通信呢?

根据进程的独立性,我们需要保证,让不同的进程看到同一份资源!但这个资源若是进程a建立的则b看不到,b建立的a看不到,因此,这份资源必须由操作系统提供!

   进程间通信分类

      一、.管道

管道在Linux中算是比较古老经典的一种通信方式

假设我们现在有一个进程(PCB,内存 mm_struct等结构),同时其有一个文件描述符表,对应了 某个正在加载的文件(文件在加载就有对应的struct _file、inode),现在我让进程fork出一个子进程,那么PCB等结构会以父进程为模板进行拷贝。但是内核缓冲区、inode也会拷贝吗?不会!文件只会被加载一次,(但struct_file会拷贝,子进程需要有自己的读写位置)那么这个文件是父子进程的共享资源。那么如果我们让父子进程中一方对文件进行写,一方进行读,不就形成了进程间通信了吗?这个以文件方式进行通信的方式我们称为管道。

实际上,两个进程的共享资源是文件内核缓冲区,而且,要让两个进程间通信,就没必要把数据刷新到磁盘。我们用一张图解释一下管道的原理

从图里我们也能看出,管道只能进行单向通信。

我们概念解释一下就是,从一个进程连接到另一个进程的一个数据流。

2.管道创建的接口

如果创建成功就返回0,失败返回-1,其中参数是一个输出型参数,输出管道所连的两个文件的fd。

3.按顺序创建一下管道

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

// 让父进程写
// 让子进程读
int main()
{
    // 1. 创建管道
    int fds[2] = {0};
    int n = pipe(fds); // fds:输出型参数
    if (n != 0)
    {
        //创建管道失败了
        std::cerr << "pipe error" << std::endl;
        return 1;
    }

    // Father -> read
    // Child  -> write
    // 2. 创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        std::cerr << "fork error" << std::endl;
        return 2;
    }
    else if (id == 0)
    {
        // 子进程
        // 3. 关闭不需要的fd,关闭read
        close(fds[0]);

        int cnt = 0;
        while (true)
        {
            std::string message = "hello";
            message += std::to_string(getpid());
            message += ", ";
            message += std::to_string(cnt);

            // fds[1]
            write(fds[1],message.c_str,message.size());
            cnt++;
            sleep(1);

            // break;
        }

        exit(0);
    }
    else
    {
        // 父进程
        // 3. 关闭不需要的fd,关闭write
        close(fds[1]);

        char buffer[1024];
        while (true)
        {
            sleep(1);
            ssize_t n = read(fds[0], buffer, 1024);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "child->father, message: " << buffer << std::endl;
            }
            else if(n == 0) 
            {
                
            }
            close(fds[0]);
            break;
            std::cout << std::endl;
        }

  
    }

    return 0;
}

我们想让子进程写就需要把读的接口关闭,同理就需要关闭父进程的写入接口。而通常我们的fds[0]表示读,fds[1]表示写。我们这里实现让子进程向文件写入,然后让父进程读取。

3.管道的一些情况

在管道正常的情况下,如果管道为空,那么read会阻塞,如果管道写满了,write会阻塞,其中并不会出现在写入的过程中去读,也不会在读的过程中进行写,这种保护机制在管道本身就已经形成。如果管道写端关闭(比如子进程写入后退出),读端继续,知道读到0表示读到文件结尾。如果管道写端正常,读端关闭,操作系统会直接杀掉写入的进程(此时的写入已经没有意义)

4.管道的特性

我们以上说的管道的相关问题都指的是匿名管道。因为创建管道时并没有名称。关于匿名管道我们有以下特性:


可以用来进行具有血缘关系的进程间通信

文件的生命周期随进程终止而终止,管道也是

只能做到单向数据通信

管道自带同步互斥等保护机制(读写有相对顺序)

二、管道通信的具体场景——进程池

我们刚才说到,如果管道为空,那么read方就会阻塞,知道有人写入才会读,假设有一个父进程fork出好几个子进程,此时其又为每一个子进程建立了管道,但此时在管道中没有内容,那么作为子进程就只能阻塞,等到父进程进行写入后进行通信。此时如果父进程向管道中写入某些命令,并让子进程们去执行,那么我们就可以像分派任务一样完成操作。像这样的模型我们称为进程池。

三、命名管道

上面我们的所有管道都是匿名管道,匿名管道没有名字,那么在通信时父子进程时如何看到这个管道的呢?->因为子进程继承了父进程的资源。但如果我想让任意两个进程间进行通信呢?此时就需要对管道进行命名了。

创建命名管道:

mkfifo name

我们来用一下,此时我们写入命令:echo "hello">fifo。我们发现卡住了,我们再启动一个终端并写入"cat <fifo"我们发现两个终端恢复正常并打印hello。相当于把内容写在管道然后等待另一端进行读,这就是基本的通信流程。

1.为什么叫命名管道

我们查看管道的详细信息发现,它是一个文件,有自己的inode,文件类型以p开头,也就是说它也有唯一的路径和文件名。这样我们可以用同一个文件系统路径,让两个进程看到同一份进程进行通信。其原理就是让操作系统只使用内核文件缓冲区,只是不刷新就可以了。

我们还有用函数来创建命名管道的方式:

第一个参数就是创建管道的路径,第二个参数是权限。创建成功返回0,出错返回-1

四、system V共享内存

我们知道,一个进程有自己的虚拟地址空间,然后通过页表映射到真实的物理地址,在虚拟地址空间中,有一块共享区,前面我们提到过,一般会把动态库映射到此部分,但现在如果我们在物理内存上开辟一块空间,然后让其通过页表映射到共享区,同样地,另一个进程也进行此操作,那么最终就会形成一个物理地址对应两个虚拟地址,就成功让两个进程看到同一份资源了。这就是共享内存。

共享内存可以在任何时刻在OS内存在多个。

创建共享内存的接口:shmget

第三个参数是一个标记位,我们常用的有IPC_CREAT和IPC_EXCL(宏),前者的作用是,如果内存不存在则创建,如果存在,将其获取并返回。对于后者,如果单独使用无意义,一般二者组合使用(|)。表示,如果内存不存在则创建,如果存在,出错返回。

返回值是一个shmid(共享内存标识符),可以看成数组下标,但和文件描述符相比,这个下标可以从0开始。失败返回-1.

第一个参数必须由用户手动输入,这个参数是用来保证两个进程所映射的是同一份内存,因为我们往往会有很多对共享内存同时存在,为了保证不出错,我们需要让每一份内存都具有唯一性,我们就可以用这个参数进行编号(具体输入由用户决定,能体现唯一性即可),这样我们就可以通过路径+id即可保证映射同一份资源。同时,系统中也有用来生成key值对应的函数:key_t ftok(路径,projid)

共享内存生命周期随内核!

已经开辟的共享内存要么手动释放,要么让OS重启。

五、共享内存的管理指令

1.查看已经开辟的共享内存——ipcs -m

2.删除共享内存——ipcrm -m shmid

3.控制共享内存的相关属性——shmctl函数 

第二个参数我们传对应的宏选项即可,第三个参数用于获取共享内存的相关属性,一般置为nullptr。

比如我们可以用函数删除共享内存

shmctl( shmid,IPC_RMID,nullptr);

六、共享内存的特点 

1.通信速度最快,相比于管道,共享内存只需要一次拷贝,提高了效率。

2.可以让两个进程在各自的用户空间共享内存块,但是没有加任何保护机制!

3.这个保护机制需要由用户自己完成,其中,共享资源被保护起来的话就称临界资源,而我们的代码并不完全都是访问共享内存的,只有访问公共资源的代码我们称临界区,对应的就是非临界区  

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

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

相关文章

【免费参会合集】2025年生物制药行业展会会议表格整理

全文精心整理, 建议今年参会前都好好收藏着&#xff0c;记得点赞&#xff01; 医药人非常吃资源&#xff0c;资源从何而来&#xff1f;作为一名从事医药行业的工作者&#xff0c;可以很负责任的告诉诸位&#xff0c;其中非常重要的一个渠道就是会议会展&#xff01; 建议所有医…

腾讯云开发+MCP:旅游规划攻略

1.登录注册好之后进入腾讯云开发 2.创建环境 4.创建好环境之后点击去开发 5.进入控制台后&#xff0c;选择AI&#xff0c;找到MCP 6.点击创建MCP Server 使用腾讯云开发创建MCP目前需要云开发入门版99/月&#xff0c;我没开通&#xff0c;所以没办法往下进行。

Sklearn入门之数据预处理preprocessing

、 Sklearn全称:Scipy-toolkit Learn是 一个基于scipy实现的的开源机器学习库。它提供了大量的算法和工具&#xff0c;用于数据挖掘和数据分析&#xff0c;包括分类、回归、聚类等多种任务。本文我将带你了解并入门Sklearn下的preprocessing在机器学习中的基本用法。 获取方式…

家用打印机性价比排名及推荐

文章目录 品牌性价比一、核心参数对比与场景适配二、技术类型深度解析三、不同场景选择 相关文章 品牌 性价比 一、核心参数对比与场景适配 兄弟T436W 优势&#xff1a; 微压电技术&#xff0c;打印头寿命长&#xff0c;堵头率低。 支持A4无边距和5G WiFi&#xff0c;适合照片…

数字电子技术基础(四十七)——使用Mutlisim软件来模拟74LS85芯片

目录 1 使用74LS85N芯片完成四位二进制数的比较 1.1原理介绍 1.2 器件选择 1.3 运行电路 2 使用74LS85N完成更多位的二进制比较 1 使用74LS85N芯片完成四位二进制数的比较 1.1原理介绍 对于74LS85 是一款 4 位数值比较器集成电路&#xff0c;用于比较两个 4 位二进制数&…

关于STM32创建工程文件启动文件选择

注意启动文件只要选择这几个 而不是要把所有都选上

LLC电路工作在容性区的风险

在t0时刻之前&#xff0c;Q6Q7导通&#xff0c;回路如下所示&#xff0c;此时A点电压是低压&#xff0c;B点电压是高压 在t0时刻时&#xff0c;谐振电流相位发生变换&#xff0c;在t1时刻&#xff0c;Q5&#xff0c;Q8导通&#xff0c;对于Q8MOS管来说&#xff0c;B点电压在Q6Q…

Linux Kernel 6

clone 系统调用&#xff08;The clone system call&#xff09; 在 Linux 中&#xff0c;使用 clone() 系统调用来创建新的线程或进程。fork() 系统调用和 pthread_create() 函数都基于 clone() 的实现。 clone() 系统调用允许调用者决定哪些资源应该与父进程共享&#xff0c…

【开源项目】Excel手撕AI算法深入理解(四):AlphaFold、Autoencoder

项目源码地址&#xff1a;https://github.com/ImagineAILab/ai-by-hand-excel.git 一、AlphaFold AlphaFold 是 DeepMind 开发的突破性 AI 算法&#xff0c;用于预测蛋白质的三维结构。它的出现解决了生物学领域长达 50 年的“蛋白质折叠问题”&#xff0c;被《科学》杂志评为…

第IV部分有效应用程序的设计模式

第IV部分有效应用程序的设计模式 第IV部分有效应用程序的设计模式第23章:应用程序用户界面的架构设计23.1设计考量23.2示例1:用于非分布式有界上下文的一个基于HTMLAF的、服务器端的UI23.3示例2:用于分布式有界上下文的一个基于数据API的客户端UI23.4要点第24章:CQRS:一种…

如何编制实施项目管理章程

本文档概述了一个项目管理系统的实施计划,旨在通过统一的业务规范和技术架构,加强集团公司的业务管控,并规范业务管理。系统建设将遵循集团统一模板,确保各单位项目系统建设的标准化和一致性。 实施范围涵盖投资管理、立项管理、设计管理、进度管理等多个方面,支持项目全生…

排序(java)

一.概念 排序&#xff1a;对一组数据进行从小到大/从大到小的排序 稳定性&#xff1a;即使进行排序相对位置也不受影响如&#xff1a; 如果再排序后 L 在 i 的前面则稳定性差&#xff0c;像图中这样就是稳定性好。 二.常见的排序 三.常见算法的实现 1.插入排序 1.1 直…

【HDFS入门】HDFS副本策略:深入浅出副本机制

目录 1 HDFS副本机制概述 2 HDFS副本放置策略 3 副本策略的优势 4 副本因子配置 5 副本管理流程 6 最佳实践与调优 7 总结 1 HDFS副本机制概述 Hadoop分布式文件系统(HDFS)的核心设计原则之一就是通过数据冗余来保证可靠性&#xff0c;而这一功能正是通过副本策略实现的…

智能 GitHub Copilot 副驾驶® 更新升级!

智能 GitHub Copilot 副驾驶 迎来重大升级&#xff01;现在&#xff0c;所有 VS Code 用户都能体验支持 Multi-Context Protocol&#xff08;MCP&#xff09;的全新 Agent Mode。此外&#xff0c;微软还推出了智能 GitHub Copilot 副驾驶 Pro 订阅计划&#xff0c;提供更强大的…

【今日三题】添加字符(暴力枚举) / 数组变换(位运算) / 装箱问题(01背包)

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;每日两三题 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 添加字符(暴力枚举)数组变换(位运算)装箱问题(01背包) 添加字符(暴力枚举) 添加字符 当在A的开头或结尾添加字符直到和B长度…

Linux——消息队列

目录 一、消息队列的定义 二、相关函数 2.1 msgget 函数 2.2 msgsnd 函数 2.3 msgrcv 函数 2.4 msgctl 函数 三、消息队列的操作 3.1 创建消息队列 3.2 获取消息队列并发送消息 3.3 从消息队列接收消息recv 四、 删除消息队列 4.1 ipcrm 4.2 msgctl函数 一、消息…

领慧立芯LHE7909可兼容替代TI的ADS1299

LHE7909是一款由领慧立芯&#xff08;Legendsemi&#xff09;推出的24位高精度Δ-Σ模数转换器&#xff08;ADC&#xff09;&#xff0c;主要面向医疗电子和生物电势测量应用&#xff0c;如脑电图&#xff08;EEG&#xff09;、心电图&#xff08;ECG&#xff09;等设备。以下是…

MongoDB简单用法

图片中 MongoDB Compass 中显示了默认的三个数据库&#xff1a; adminconfiglocal 如果在 .env 文件中配置的是&#xff1a; MONGODB_URImongodb://admin:passwordlocalhost:27017/ MONGODB_NAMERAGSAAS&#x1f4a1; 一、为什么 Compass 里没有 RAGSAAS 数据库&#xff1f;…

uniapp-商城-26-vuex 使用流程

为了能在所有的页面都实现状态管理,我们按照前面讲的页面进行状态获取,然后再进行页面设置和布局,那就是重复工作,vuex 就会解决这样的问题,如同类、高度提炼的接口来帮助我们实现这些重复工作的管理。避免一直在造一样的轮子。 https://vuex.vuejs.org/zh/#%E4%BB%80%E4…

UDP概念特点+编程流程

UDP概念编程流程 目录 一、UDP基本概念 1.1 概念 1.2 特点 1.2.1 无连接性&#xff1a; 1.2.2 不可靠性 1.2.3 面向报文 二、UDP编程流程 2.1 客户端 cli.c 2.2 服务端ser.c 一、UDP基本概念 1.1 概念 UDP 即用户数据报协议&#xff08;User Datagram Protocol &…