【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

相关文章
|
7月前
|
缓存 监控 Linux
CentOS系统如何查看当前内存容量。
以上方法都不需要特殊软件或者复杂配置即可执行,在CentOS或其他Linux发行版中都适合运行,并且它们各自透露出不同角度对待问题解答方式:从简单快速到深入详尽;从用户态到核心态;从操作层数到硬件层数;满足不同用户需求与偏好。
565 8
|
8月前
|
存储 缓存 监控
手动清除Ubuntu系统中的内存缓存的步骤
此外,只有系统管理员或具有适当权限的用户才能执行这些命令,因为这涉及到系统级的操作。普通用户尝试执行这些操作会因权限不足而失败。
1573 22
|
监控 Linux Python
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
416 27
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
1039 38
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
967 20
|
机器学习/深度学习 人工智能 缓存
【AI系统】推理内存布局
本文介绍了CPU和GPU的基础内存知识,NCHWX内存排布格式,以及MNN推理引擎如何通过数据内存重新排布进行内核优化,特别是针对WinoGrad卷积计算的优化方法,通过NC4HW4数据格式重排,有效利用了SIMD指令集特性,减少了cache miss,提高了计算效率。
608 3
|
机器学习/深度学习 人工智能 算法
【AI系统】内存分配算法
本文探讨了AI编译器前端优化中的内存分配问题,涵盖模型与硬件内存的发展、内存划分及其优化算法。文章首先分析了神经网络模型对NPU内存需求的增长趋势,随后详细介绍了静态与动态内存的概念及其实现方式,最后重点讨论了几种节省内存的算法,如空间换内存、计算换内存、模型压缩和内存复用等,旨在提高内存使用效率,减少碎片化,提升模型训练和推理的性能。
746 1
|
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`:终止后台作业。 优先级调整:
1211 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能