【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

相关文章
|
4月前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
543 14
|
3月前
|
消息中间件 Linux
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
167 48
|
4月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
319 20
|
5月前
|
Linux
【Linux】System V信号量详解以及semget()、semctl()和semop()函数讲解
System V信号量的概念及其在Linux中的使用,包括 `semget()`、`semctl()`和 `semop()`函数的具体使用方法。通过实际代码示例,演示了如何创建、初始化和使用信号量进行进程间同步。掌握这些知识,可以有效解决多进程编程中的同步问题,提高程序的可靠性和稳定性。
227 19
|
Linux
Linux 更改共享内存tmpfs的大小
通过挂载的方式在原有目录或新建目录改造 如: [root@luozhonghua /]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/vg_luozhonghua-lv_root 21G 4.
1360 0
|
2月前
|
Linux
linux命令详细说明以及案例
本文介绍了常用的 Linux 命令及其详细说明和示例,包括:`ls`(列出目录内容)、`cd`(更改目录)、`rm` 和 `mv`(删除与移动文件)、`grep`(搜索文本)、`cat`(显示文件内容)以及 `chmod`(更改文件权限)。每个命令均配有功能描述、选项说明及实际案例,帮助用户更好地掌握 Linux 命令的使用方法。
175 56
|
1月前
|
Linux 定位技术
Linux系统中的cd命令:目录切换技巧
踏过千山,越过万水,人生就是一场不断前行的旅程,总充满了未知与挑战。然而,“cd”命令如同你的旅伴,会带你穿梭在如棋盘一般的文件系统中,探索每一处未知。希望你能从“cd”命令中找到乐趣,像是掌控了一种络新妙的魔法,去向未知进发,开始你的探索之旅。
120 24
|
1月前
|
安全 Shell Linux
Linux系统之su命令的基本使用
Linux系统之su命令的基本使用
106 3
Linux系统之su命令的基本使用
|
28天前
|
Linux
Linux命令的基本格式解析
总的来说,Linux命令的基本格式就像一个食谱,它可以指导你如何使用你的计算机。通过学习和实践,你可以成为一个真正的“计算机厨师”,创造出各种“美味”的命令。
65 15
|
1月前
|
监控 数据可视化 Java
调试技巧 - 用Linux命令排查Java问题
总的来说,使用Linux命令来排查Java问题,需要一定的实践经验和理论知识。然而,只要我们愿意花时间深入了解这些工具,我们就能够熟练地使用它们来分析和解决问题。此外,这些工具只是帮助我们定位问题,真正解决问题需要我们对Java和JVM有深入的理解,并能够读懂和分析代码。
72 13