【Linux 系统】进程间通信(共享内存、消息队列、信号量)(上)

简介: 【Linux 系统】进程间通信(共享内存、消息队列、信号量)(上)

一、System V —— 共享内存(详解)

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

       下面我们还需要了解进程间通信之 System V 标准下的共享内存,前面所讲的管道其实不属于 System V 标准,但是它依旧是操作系统下最原生的通信方式。

      System V 标准下有最典型的三种通信方式:共享内存、消息队列、信号量。下面会重点谈谈共享内存,然后简单提一下消息队列,而信号量将放在后面的多线程部分再进行了解。


1、共享内存的原理

       正如上面两种管道,进程间通信的第一步一定是先让不同的进程看到同一份资源,然后才是通信的过程。

       进程间通信中的大部分内容都是第一步,在这之前要让不同进程看到同一份资源:匿名管道是通过父子共享文件的特征;命名管道是通过文件路径具有唯一性。

       其中,这两种管道归根到底所看到的资源都是文件资源。

对于上图中的内容,我们之前已经接触过了。操作系统为了满足通信的需求,需要:

  1. 在物理内存上申请一块物理内存空间
  2. 再把这块空间通过页表映射到共享区(也就是堆栈之间)。
  3. 最后,将建立映射之后的虚拟地址返回给用户。

操作系统当然可以做到这些操作,因为操作系统是软硬件资源的管理者,这并不难理解,在 C/C++ 中使用 malloc / new 时,实际上就是在堆上申请空间,最后也还是在物理内存中申请,然后再返回地址。以前 malloc / new 是为了让你这个进程私有使用,而现在是为了能够让多个进程看到同一份资源。

       此时又有一个进程 B,它和进程 A 没有任何关系,虽然它们是类似的数据结构来表示进程,但是它们的代码和数据是被加载到内存中不同的位置,所以实际上它们是具有很强的独立性。举一个例子:一个全局变量,然后父进程直接打印出它的地址和值,子进程修改内容后再打印出地址和值,最后结果显示二者地址一样,值却不一样。

       同样对于进程 B,操作系统也可以向物理内存申请空间,然后再映射到共享区,接着返回给进程。不过,我们要做到的是让不同的进程能够看到同一份资源,所以原理就是操作系统向物理内存申请一块空间,这块空间就叫做共享内存,再把这块空间分别映射到两个进程中 mm_struct 的共享区(这个共享区在基础 IO 中已经说过动态库是被映射到这个区域的,现在就知道了物理内存中申请的共享内存也会被映射到这块区域),然后再返回给进程,那么这两个进程就可以使用各自的虚拟地址、页表访问同一块物理内存,这就是共享内存。上述步骤一定是有对应的系统调用接口帮助我们实现。共享内存的提供者是操作系统

       操作系统内部是提供通信机制的(IPC),也就是其中有一个 ipc 模块。前面讲到操作系统申请一块内存空间,但也得是有人来告诉操作系统自己需要申请,所以本质还是进程申请的。从宏观上来看,操作系统内一定存在大量的共享内存,所有的共享内存都是进程向操作系统申请的,其中操作系统当然要管理这么多的共享内存,那应该怎么管理呢?—— 先描述,再组织(共享内存是给进程使用的,而操作系统为了管理这些共享内存,它也需要申请大量对应的数据结构来维护),共享内存 = 共享内存块 + 对应的共享内存的内核数据结构,所以操作系统对共享内存的管理就变成了对共享内存所对应的数据结构的管理。

       综上所述,流程对应如下:

  1. 申请共享内存。
  2. 进程 A、B 分别挂接对应的共享内存到自己的地址空间(共享区)。
  3. 进程双方就能够看到同一份资源,也就可以互相通信了。
  4. 释放共享内存。

2、 shmget

(1)认识接口

shmget系统提供申请共享内存的一个系统接口

key 是这个共享内存段的名字

size 是我们想申请共享内存的大小,理论上来说是可以任意,但还是建议选择 4kb 的倍数(后面会解释原因)。

shmflgIPC_CREAT IPC_EXCL 两个选项。前者是创建共享内存,后者单独使用并没有意义。其次,这里还可以 | 上一个八进制方案,表示这个共享内存的权限。shmflg 由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的

  • 同时设置 IPC_CREAT 和 IPC_EXCL,如果目标共享内存不存在,则创建;否则,出错返回。这样做的意义是如果调用 shmget 成功,那么得到的一定是全新的共享内存,因为如果它失败就出错了。所以一般这两个选项会组合使用,就可以从 0 到 1 的创建一个共享内存。

  • 若只设置 IPC_CREAT(同 0)(没有意义),如果目标共享内存不存在,则创建;否则,则获取共享内存。

一定是一个进程设置 IPC_CREAT | IPC_EXCL,另一个进程设置 IPC_CREAT(什么叫做同时设置呢?之前我们就说过标志位用 int 太浪费了,所以这里用的是一个 bit 位来表示一种状态。如果有多个状态需要同时设置就使用 |,这里可以验证一下,结果可以看到这里 define 的是一种 8 进程数据,这里的 1 2 4 就说明用的是一串 01 序列,但只有一个 1,而且 1 的位置不一样,所以 | 就可以获取到多个标志位)。

如果共享内存已经存在了,此时就不应该再进行创建了,而是选择获取。因为如果一个创建好共享内存的进程要与另一个进程通信的话,另一个进程就只能是获得要通信进程对应的共享内存的返回值:如果成功,会返回一个合法的共享内存的标识符,类似之前学的 fd;否则,返回 -1。

它可以通过这个返回值来唯一标识这个共享内存。这个概念有点类似文件描述符,共享内存 ipc 机制也确实与文件系统有关,但 ipc 机制是操作系统另外一个独立的模块,这样的小模块还有很多,之前了解的都是一些宏观上的模块,就比如:进程管理、文件管理、内存管理、驱动管理。


如何保证两个进程看到的是同一块共享内存呢?

       每个共享内存都有自己对应的数据结构 struct shm_ipc,此时通过 key 就可以进行唯一区分(像是我们的身份证号码,更多的是强调唯一性)。其中 A 进程创建了共享内存,key 的值是 123,如果 B 进程想要和 A 进程通信,就需要遍历共享内存数据结构中的 key 值。

       那么现在的问题就变成了如何保证两进程获得的是同一个 key 值呢?—— ftok

       它和 fork 很像,但是没有任何关系。它的内部不进行任何的系统调用,而是一套算法,ftok 没有任何的系统调用。它只是把第一参数的字符串和第二个参数的整数合起来形成一个唯一的 key 值。它可以按照自己的情况任意填写,但必须要保证通信的两个进程填的 key 值是一样的,这样就能够保证两个进程使用的是同一个规则来形成的 key 值。


(2)代码

A. makefile

makefile 中是可以定义变量的, makefile 中取变量要用 $()。


B.  common.h


c. 必须要先保证 server.c 和 client.c 中的 ftok 获取的 key 值是一样的

ftok 本身没有任何的系统调用,key 值就是 ftok 将 PATH_NAME 和 PROJ_ID 组合形成唯一的 key 值。


D. 申请共享内存
a. ipcs

ipcs 命令默认它会查看 Message Queues(消息队列)、Shared Memory Segments(共享内存段)、Semaphore Arrays(信号量数组)相关信息。如果只想查看共享内存,则 ipcs -m,这里我们可以看到好像并没有什么共享内存,sudo 之后也没有,这时我们再打开一个共享内存。

此时 ./server,输出结果之后 server 进程当然退出了,所以它的退出码是 0。

(说明当进程运行结束,共享内存依旧存在

此时再 ipcs -m 就可以看到 server 进程所申请的共享内存信息了。

正如上面所看到的,server 进程已经结束了,但是它所申请的 ipc 共享内存资源仍然存在。这里表达的是,所有的 Systrem V IPC 资源的生命周期都是随内核,而不随进程。这里有两种方法可以释放共享内存:

  1. 手动删除:操作系统进行重启或者命令行指令(ipcrm -m shmid 释放共享内存,规范应该是由所对应的进程来调用系统接口来释放的)。
  2. 代码删除:当进程退出时,用调用释放(有申请共享内存,自然也有释放)。

2、shmctl

(1)认识接口

既然有 shmget 来申请共享内存,那么也必须要有 shmctl 来释放共享内存shmctl 用于控制共享内存。

  1. shmid 是 shmget 创建共享内存成功后返回的共享内存标识码 id。
  2. cmd 将要采取的动作(有三个可取值),如果想释放共享内存,那么就用 IPC_RMID 选项。
  3. buf 类似于上面说的共享内存的属性 struct shm_ipc,这很少使用,它指向一个保存着共享内存的模式状态和访问权限的数据结构,设置为 NULL。


(2)代码

这里的现实的 perms 就是共享内存的权限。


3、shmat

至此,我们完成了让进程在物理内存中创建好共享内存,然后释放共享内存,那么接下来还要将进程与共享内存关联。所以刚刚在查看共享内存时,nattch 就是与当前共享内存关联的进程的个数,我们可以看到,这里只是创建了共享内存,还没有任何一个进程与之关联。


(1)认识接口

shmat 是系统提供于共享内存和进程关联的一个系统接口,也就是将共享内存段连接到进程地址空间

  • shmid 是让这个进程和哪个共享内存关联,是一个共享内存标志
  • shmaddr 是一个指定连接地址,要把共享内存挂接到进程的哪个虚拟上,这里要挂接到共享区,我们直接设置为 NULL,操作系统会帮我们选择。
  • shmflg 的选项是挂接的方式,我们默认填 0,它的两个可能取值是 SHM_RND SHM_RDONLY
  • shmat 的返回值是 void*,我们之前学的 malloc 的返回值也是 void*,虽然它们的区域不一样,但是原理类似,malloc 成功后返回值就是堆上的一块空间的起始地址,而 shmat 成功就返回关联共享内存段的起始地址。
  • shmaddr NULL,核心自动选择一个地址。
  • shmaddr 不为 NULL shmflg SHM_RND 标记,则以 shmaddr 为连接地址。
  • shmaddr 不为 NULL shmflg 设置了 SHM_RND 标记,则连接的地址会自动向下调整为 SHMLBA 的整数倍。公式:shmaddr - (shmaddr % SHMLBA)。
  • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

【Linux 系统】进程间通信(共享内存、消息队列、信号量)(下)https://developer.aliyun.com/article/1515667?spm=a2c6h.13148508.setting.19.11104f0e63xoTy

相关文章
|
消息中间件 Linux
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
550 48
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
1016 20
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
580 13
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
1397 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
717 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
396 1
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作
403 2