Linux进程间通信(管道+共享内存)

进程间通信(interprocess communication,简称 IPC)指两个进程之间的通信。系统中的每一个进程都有各自的地址空间,并且相互独立、隔离,每个进程都处于自己的地址空间中。所以同一个进程的不同模块(譬如不同的函数)之间进行通信都是很简单的,譬如使用全局变量等。

但是,两个不同的进程之间要进行通信通常是比较难的,因为这两个进程处于不同的地址空间中;通常情况下,大部分的程序是不要考虑进程间通信的,因为大家所接触绝大部分程序都是单进程程序(可以有多个线程),对于一些复杂、大型的应用程序,则会根据实际需要将其设计成多进程程序,譬如 GUI、服务区应用程序等。

在一些中小型应用程序中通常不会将应用程序设计成多进程程序,自然而然便不需要考虑进程间通信的问题,所以,本章内容以了解为主、了解进程间通信以及内核提供的进程间通信机制,并不详解介绍进程间通信,如果大家在今后的工作当中参与开发的应用程序是一个多进程程序、需要考虑进程间通信的问题,此时再去深入学习这方面的知识!

参考:

https://blog.csdn.net/weixin_51983604/article/details/123306293

进程间通信

1.目的

数据传输:一个进程需要将它的数据发送给另一个进程。

资源共享:多个进程之间共享同样的资源。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(们)发生了某种事件(如进程终止时要通知父进程)。

进程控制:有些进程希望完全控制另一个进程的执行(如用gdb对一个进程debug),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.如何通信

进程运行时具有独立性(数据层面具有独立性,即使是父子进程操作数据时也要写时拷贝)。

进程间的通信一般要借助第三方资源(OS)

通信的本质是数据的“拷贝”(进程A将数据“拷贝”给操作系统,操作系统再将数据“拷贝”给进程B,所以操作系统一定要提供一段内存区域且两个进程都能看到这段区域)。

进程间通信的本质是让不同的进程能看到同一份资源(内存、文件缓冲等等)。这一个资源由不同的部分提供,就有了不同的进程间通信方式。

常见的通信方式

管道pipe:

管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

命名管道FIFO:

有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

共享内存SharedMemory:

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

信号量Semaphore:

信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

套接字Socket:

套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

信号 ( sinal ) :

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

Linux 内核提供了多种 IPC 机制,基本是从 UNIX 系统继承而来,而对 UNIX 发展做出重大贡献的两大主力 AT&T 的贝尔实验室及 BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对 UNIX 早期的进程间通信手段进行了系统的改进和扩充,形成了“System V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接字(Socket,也就是网络)的进程间通信机制。Linux 则把两者继承了下来,如下如所示:

图 10.2.1 Linux 锁继承的进程间通信手段

其中,早期的 UNIX IPC 包括:管道、FIFO、信号;System V IPC 包括:System V 信号量、System V消息队列、System V 共享内存;上图中还出现了 POSIX IPC,事实上,较早的 System V IPC 存在着一些不足之处,而 POSIX IPC 则是在 System V IPC 的基础上进行改进所形成的,弥补了 System V IPC 的一些不足之处。POSIX IPC 包括:POSIX 信号量、POSIX 消息队列、POSIX 共享内存。

总结如下:

⚫ UNIX IPC:管道、FIFO、信号;

⚫ System V IPC:信号量、消息队列、共享内存;

⚫ POSIX IPC:信号量、消息队列、共享内存;

⚫ Socket IPC:基于 Socket 进程间通信。

针对消息队列、信号量、socket和信号,本人会用单独的文章来说明。

因此本文重点介绍管道和共享内存。

匿名管道

概念

管道是Unix中最古老的进程间通信的形式。把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

管道通信本质是文件,操作系统没有做过多的工作。

管道只能进行单向通信。

匿名管道

实现父子进程间通信

首先大致了解一下pipe函数:

头文件:<unistd.h>

功能:创建一无名管道

原型:int pipe(int fd[2]);

参数:

fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端

返回值:成功返回0,失败返回错误代码

下面简单使用一下pipe函数。

然后用代码实现子进程向父进程发送字符串。

几个小问题

可不可以定义一个全局的数据区,子进程向其中写入内容,然后父进程再从中读取。

不可以,因为进程间具有独立性,不能相互干扰,子进程在写入内容时会写时拷贝,这样代码中看起来是同一块数据区,但映射到物理内存后是不同的两块内容,互相不可访问。

既然会有写时拷贝,那为什么上面代码中的写入没有写时拷贝呢?

因为上面在write时将内容写进了操作系统提供的文件区内,而不是写入到了子进程自己的数据区内,写入的内容不属于父进程或子进程,不需要写时拷贝。

sleep(1)在子进程中才有,但父进程打印时却也是每隔一秒打印一次,为什么?

在多执行流(比如这里同时有父子进程)下,访问同一份资源,这个资源叫临界资源。

在上面的代码中,可能会发生子进程写入到一半,父进程就来读取,这显然是不被允许的,所以就需要同步与互斥来解决。

这里主要是互斥,即任何时刻都只能有一个进程正在使用某种资源,管道内部自动提供了同步与互斥机制。当子进程写入并sleep时,父进程阻塞地等待,由于子进程sleep,导致父进程也看起来sleep。

如果写端关闭,那么读端read就会返回0,代表读取结束。

图解

既然最后只能留一个读写端,那么父进程最开始为什么要把读写端都打开呢?

父进程打开读写端是为了创建子进程时子进程得到的读写端也都打开,而最后各自只剩一个读写端是因为进程间的通信是单向的。

通常规定fd[0]是读文件描述符,而fd[1]是写文件描述符。

匿名管道特点

只能用于具有共同祖先的进程之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

管道提供流式服务(在网络部分中会详谈)。

一般而言,进程退出,管道释放,所以管道的生命周期随进程变化而变化。

一般而言,内核会对管道操作进行同步与互斥。

管道的数据只能向一个方向流动(半双工);需要双方通信时(全双工),需要建立起两个管道。

四种情况

如果管道空间被写满,负责write的进程就会被挂起。

如果管道空间什么都没有,负责read的进程就会被挂起。

如果负责write的进程写完内容后关闭,负责read的进程的read返回值为0。

如果负责read的进程关闭,而负责write的进程一直写,那么负责write的进程就会被系统kill掉,因为继续写入也没有进程会来读,是没有意义的行为,所以直接kill掉,由于这是不正常退出,所以一定有信号参与。

命名管道

匿名管道使用的限制就是只能在具有共同祖先的进程间通信。如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

命名管道本质是一种特殊类型的文件。

命令行使用命名管道

用mkfifo可以创建一个命名管道。

也就是文件类型之一的pipe管道文件。

代码使用命名管道

代码创建命名管道使用的仍是mkfifo,只不过这时它是个函数。

创建命名管道文件很简单,代码如下:

下面实现简单的server和client之间的通信,重点只有创建命名管道的部分,剩下的对文件进行读写都是常规内容

server.c的代码:

client.c的代码:

本质上就是对文件的操作。

命令行中的管道|

命令行中连接两条命令(运行起来也是进程)的管道是匿名管道。

|

管道符,当用此连接符连接多个命令时,前面命令执行的正确输出,会交给后面的命令继续处理。若前面的命令执行失败,则会报错,若后面的命令无法处理前面命令的输出,也会报错。

例 ls | grep *.txt

运行三条命令并用管道连接,查看它们的信息。

上面只能证明具备匿名管道的条件,但并不能证明就是匿名管道。但事实上确实是匿名管道,可以用这个例子来帮助记忆。

共享内存

共享内存区是最快的进程间通信形式。一旦这样的内存映射到共享它的进程的地址空间,进程间数据的传递不再涉及到内核,或者说进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存和消息队列以传送数据为目的,而信号量是为了保证进程的同步与互斥而设计的,但也属于通信范畴。

共享内存本质就是修改页表的映射关系,在不同进程的虚拟地址空间中开辟空间,和同一块物理内存对应。完成开辟空间、建立映射、开辟虚拟空间、返回给用户信息等等一系列操作都有系统接口可用,是操作系统完成的。

基本原理

共享内存的基本原理如下:

共享内存建立过程:

  1. 申请共享内存(假设物理内存已经开辟好了)。

  2. 将共享内存挂接到进程地址空间(建立映射关系)。

  3. 去关联共享内存(修改页表,取消映射关系)。

  4. 释放共享内存,将内存归还给系统。

前面的管道文件是由某一个进程来创建,其他进程通过打开相同文件名使用即可。那么,共享内存呢?

多个进程使用共享内存时,都需要创建共享内存,只不过创建共享内存时,会通过唯一的键值key来标记使用的是同一段共享内存区域。接下来详细说明。

相关函数参考:

https://www.cnblogs.com/52php/p/5861372.html

特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

在Linux中提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。

shmget()函数

该函数用来创建共享内存,它的原型为:

int shmget(key_t key, size_t size, int shmflg);

第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

显然系统中可能会同时存在多个共享内存,为了管理这些共享内存,操作系统就需要维护与其相关的内核数据结构。其次,保证共享内存的唯一性,这就需要上面shmget函数中的第一个参数key传入一个系统中独一无二的值。

不同的进程就是通过该key来标识唯一的一段共享内存的。

那么这个独一无二的key如何获得呢?需要再调用函数ftok。

ftok可以把一个已存在的路径名和一个整数标识符转换成IPC键值,通过ftok返回的是根据文件(pathname)信息和计划编号(proj_id)合成的IPC key键值,从而避免用户使用key值的冲突,保证了唯一性。

更多参考:https://blog.csdn.net/u013485792/article/details/50764224

第二个参数,size以字节为单位指定需要共享的内存容量

第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

shmat()函数

第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一个参数,shm_id是由shmget()函数返回的共享内存标识。

第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

第三个参数,shm_flg是一组标志位,通常为0。

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

shmdt()函数

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void *shmaddr);

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

shmctl()函数

该函数用来控制共享内存,它的原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一个参数,shm_id是shmget()函数返回的共享内存标识符。

第二个参数,command是要采取的操作,它可以取下面的三个值 :

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

  • IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

shmid_ds结构 至少包括以下成员:

struct shmid_ds {

uid_t shm_perm.uid;

uid_t shm_perm.gid;

mode_t shm_perm.mode;

};

说明:共享内存的生命周期是跟随内核的,也就是说除非进程主动删除或在命令行中用命令删除,否则共享内存就一直存在直到关机。

再进一步,System V包括的共享内存、消息队列、信号量的生命周期都是跟随内核的。所以这部分内存一定是操作系统提供并维护的。

示例:

下面两部分代码将上面几个过程结合起来,并简单地在进程间用字符串通信。

server.c代码如下:

client.c的代码如下:

注意上面向共享内存中读写时,并没有像使用管道时通过系统调用接口(read、write)实现,而是直接使用(像malloc申请的堆空间一样)。因此拷贝次数少、不提供同步与互斥,所以这也是进程间通信最快的方式。

使用共享内存的优缺点

1、优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

2、缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

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

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

相关文章

matlab仿真 数字信号载波传输(下)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第七 章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all M8; msg[1 4 3 0 7 5 2 6]; ts0.01; T1; %t0:ts:T; t0:ts:T-ts; %x0:ts:length(msg); x0:ts:length(msg)-ts; f…

《python语言程序设计》第6章10题使用isPrime函数 求小于10000的素数的个数

修改了一个地方&#xff0c;真的太棒了&#xff01; def isPrime(number):divisor 2while divisor < number / 2:if number % divisor 0:return Falsedivisor 1return Truedef printPrimeNumbers(numberOfPrimes):# 这个代码之前就没有用&#xff0c;作者写的目的是什么呢…

NC 最长回文子串

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 对于长度为n的…

6. 开发板烧录

1. 概述 采用恒玄的底板+2小板的开发板 2. 开发板资料 详见:<<BES AUDIO DEV BOARD USER MANUUAL_9v5.pdf>> 3. 硬件接线 供电:可以采用电池供电,也可以采用Type-c供电 烧录:采用Type-C口,实际上就是串口。(下图带黑色标志的)

【启明智显分享】基于国产Model3芯片的7寸触摸屏助力智慧医疗,电子床头屏提升护理交互

未来医院必然是以信息化为基础&#xff0c;以物联网为特征&#xff0c;以医疗为核心的服务型医院。病房作为医院的重要服务场所&#xff0c;成为智慧医院建设的重要一环。 为提高医护人员与患者的互动交流&#xff0c;给医疗注入智慧元素&#xff0c;让患者享受智能服务&#…

AJAX-Promise 详解

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 前言 一、Promise基本概念 1.1 定义 1.2 状态 1.3 构造函数 二、Promise基本用法 2.1 then() 2.2 ca…

keras的路透社数据训练对测试数据的概率总和计算问题

在使用keras内置数据路透社新闻分类的时候&#xff0c;使用训练的模型预测测试数据。然后发现对预测数据分类的概率总和不是1. pridiction model.predict(x_test)for i in range(0,46):print(np.sum(pridiction[i]))然而python深度学习这本书里面的是1.0 问题目前没有解决。…

醒醒,别睡了...讲《数据分析pandas库》了—/—<3>

直接上知识点 一、 1、新建数据框时建立索引 所有的数据框默认都已经使用从 0 开始的自然数索引&#xff0c;因此这里的"建立”索引指的是自定 df pd.DataFrame( {varl : 1.0, var2 :[1,2,3,4], var3 :[test,python,test,hello] , var4 : cons} , index [0,1,2,3]) …

量化私募公司的多因子构建方案(附python代码)

原创文章第600篇&#xff0c;专注“AI量化投资、世界运行的规律、个人成长与财富自由"。 昨天代码已经发布了&#xff0c;大家可以前往下载和更新&#xff1a; 代码发布&#xff1a;quantlabv5.3&#xff0c;可转债所有数据及双低、动量因子策略&#xff0c;单因子分析框…

文件夹怎么设置密码?文件夹加密方法盘点

文件夹是电脑管理数据的重要工具&#xff0c;当我们将重要数据存储在文件夹中时&#xff0c;需要严格保护文件夹的数据安全&#xff0c;避免数据泄露。下面我们就来了解一下文件夹设置密码的方法。 文件夹加密 文件夹加密是指通过加密算法来加密保护文件夹&#xff0c;避免其他…

高级网页爬虫开发:Scrapy和BeautifulSoup的深度整合

引言 在互联网时代&#xff0c;数据的价值日益凸显。网页爬虫作为一种自动化获取网页内容的工具&#xff0c;广泛应用于数据挖掘、市场分析、内容聚合等领域。Scrapy是一个强大的网页爬虫框架&#xff0c;而BeautifulSoup则是一个灵活的HTML和XML文档解析库。本文将探讨如何将…

Kolla-Ansible的确是不支持CentOS-Stream系列产品了

看着OpenStack最新的 C 版本出来一段时间了&#xff0c;想尝个鲜、用Kolla-Ansible进行容器化部署&#xff0c;结果嘛。。。 根据实验结果&#xff0c;自OpenStack Bobcat版本开始&#xff0c;Kolla-Ansible就适合在CentOS系列产品上部署了&#xff0c;通过对 Bobcat和Caracal…

【docker】部署证书过期监控系统mouday/domain-admin

证书过期了再去部署证书容易被骂&#xff0c;就找了一个开源的证书过期系统来部署一下 过程 官方文档&#xff1a;https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html#docker 直接下载镜像是超时的&#xff0c;切换一下文档推荐的镜像源 新建docker配置…

模拟电子技术-实验四 二极管电路仿真

实验四 二极管电路仿真 一&#xff0e;实验类型 验证性实验 二&#xff0e;实验目的 1、验证二极管的单向导电性 2、验证二极管的稳压特性。 三&#xff0e;实验原理 二极管的单向导电性&#xff1a; 四、实验内容 1、二极管参数测试仿真实验 1&#xff09;仪表仿真…

IndexError: index 0 is out of bounds for axis 1 with size 0

IndexError: index 0 is out of bounds for axis 1 with size 0 目录 IndexError: index 0 is out of bounds for axis 1 with size 0 【常见模块错误】 【解决方案】 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#…

物联网主机 E6000:智慧应急领域的创新力量

在当今瞬息万变的世界中&#xff0c;突发事件和紧急情况时有发生。如何迅速、准确地应对这些挑战&#xff0c;保障人民生命财产安全&#xff0c;成为了社会发展的重要课题。而物联网主机 E6000 的出现&#xff0c;为智慧应急领域带来了全新的解决方案。 一、强大的性能与功能 物…

ueditor跨域问题解决

ueditor解决跨域问题 问题&#xff1a;1.在引用vue-ueditor-wrap后&#xff0c;上传图片和附件出现跨域问题&#xff0c;前端引用了webpack去解决跨域问题&#xff0c;但仍然存在跨域问题&#xff1f; ueditor是百度的富文本&#xff0c;功能较多但资料不够全&#xff0c;因为…

模拟string(四)详解

目录 判断string大小关系bool operator(const string&s1,const string s2)代码 bool operator<(const string& s1, const string& s2)代码 bool operator<(const string& s1, const string& s2)代码 bool operator>(const string& s1, const …

算法板子:使用数组模拟队列——在队尾插入元素、在队头弹出元素、判断队列是否为空、查询队头元素

使用数组模拟时长这个样子&#xff1a; 代码&#xff1a; #include <iostream> using namespace std;const int N 1e5 10;// 数组q相当于队列; // hh是队头指针&#xff0c;始终指向队头 // tt是队尾指针&#xff0c;始终指向队尾 int q[N], hh, tt -1;// 队尾插入元…

代码随想录算法训练营第 25 天 | LeetCode491.递增子序列 LeetCode46.全排列 LeetCode47.全排列ii

代码随想录算法训练营 Day25代码随想录算法训练营第 25 天 | LeetCode491.递增子序列 LeetCode46.全排列 LeetCode47.全排列ii 目录 代码随想录算法训练营前言LeetCode491.递增子序列LeetCode46.全排列LeetCode47.全排列ii 一、LeetCode491.递增子序列1.题目链接2.思路3.题解 …