Linux 进程间通信(四)System V共享内存

目录

一、什么是共享内存?

二、共享内存的原理

共享内存的本质

共享内存的管理

三、系统调用接口介绍

shmget,创建共享内存

[ftok() 函数生成 key 值](#ftok() 函数生成 key 值)

shmid和key对比

shmat,挂接接口

shmdt,去挂接

shmctl,关闭共享内存

[共享内存状态结构体 struct shmid_ds](#共享内存状态结构体 struct shmid_ds)

四、代码实现

Makefile

Shm.hpp

Server.cc

Client.cc

运行:

进行通信:

五、共享内存的特性

六、总结


前面我们学习了匿名管道和命名管道,今天我们学习另一种通信方式 : System V共享内存通信方式

一、什么是共享内存?

共享内存就是由内核开辟一块公共物理内存,多个进程将这块公共的物理内存映射到各自的进程虚拟地址空间进而实现共享,从而直接读写同一块内存数据,无需内核拷贝数据,所以共享内也是速度最快的进程间通信方式。

二、共享内存的原理

进行进程间通信的本质就是要让不同进程看到同一份资源,那么共享内存又是如何做的呢?请看下图 :

上图包含物理内存、两个进程(进程A和B),以及每个进程对应的内核数据结构:task_struct(进程描述符)、虚拟地址空间、页表。

我们以进程 A 为例:如果要使用共享内存,顾名思义,首先就要有内存,即先使用某种手段在物理内存上申请内存,这种手段就是系统调用,首先通过系统调用向操作系统申请一块物理内存,这块申请的物理内存就是共享内存。申请成功后,操作系统会通过页表为进程 A 建立 "物理地址 ↔ 虚拟地址" 的映射关系,将这块共享的物理内存映射到进程 A 虚拟地址空间的共享区,并向用户层返回共享区的起始虚拟地址。

接下来进程 B 要与进程 A 通信:此时共享内存已存在,进程 B 只需通过系统调用获取该共享内存,操作系统同样会通过页表为进程 B 建立 "物理地址 ↔ 虚拟地址" 的映射关系,将同一块物理内存映射到进程 B 虚拟地址空间的共享区,并返回对应的起始虚拟地址。此时进程 A 和进程 B 虽为不同进程,却能通过各自的虚拟地址访问同一块物理内存,从而实现高效通信;共享内存默认支持双向读写,具体通信方向由不同场景决定。

释放共享内存的步骤与建立过程相反:进程调用系统调用(shmdt),操作系统清除页表中该进程对应的 "虚拟地址→物理地址" 映射关系,将共享内存从进程虚拟地址空间中分离;当所有进程都与共享内存分离后,还要调用系统调用(shmctl),操作系统才会真正释放这块物理内存。上述操作不能由进程直接完成,因为进程的虚拟地址空间是独立的,若由进程自行申请内存,该内存就应归属于当前进程,从而导致其他进程无法访问。因此,共享内存的创建、映射、释放等操作,必须由操作系统内核亲自完成。

操作系统通过一系列的系统调用接口(如shmget、shmat、shmdt、shmctl)向进程提供服务:进程若要创建共享内存、挂载到地址空间等操作,就要使用对应的系统调用,由内核执行具体的内存管理和映射工作。

共享内存的本质也就是让不同进程看到同一份资源,并且一个进程创建,另一个进程接受并使用,这和命名管道是一样的。

共享内存的本质

共享内存的本质就是内核在物理内存中开辟的一块公共物理内存区域,它是多个进程共享数据的载体,不属于任何单个进程,只由内核统一管理。

  • 进程视角的共享内存:进程无法直接访问物理内存,必须通过系统调用将这块物理内存映射到自己的虚拟地址空间,得到一个虚拟地址指针,之后读写这个指针就等价于读写这块共享物理内存。
  • 内核视角的共享内存:内核会用一个结构体来描述这块物理内存的属性(大小、权限、挂载进程数等),并通过 shmid 来标识它,这个结构体 + 对应的物理内存,共同构成了完整的 "共享内存对象"。

共享内存的管理

既然进程A和进程B会共享使用一块共享内存,那么就会有其他的进程使用另一块共享内存,所以共享内存就需要被操作系统统一管理。 管理的方式仍然是先描述,再组织

先描述 : 内核会用一个结构体 (struct shmid_kernel)来描述这块物理内存的属性(大小、权限、挂载进程数等),并通过shmid来标识它,这个结构体 + 对应的物理内存,共同构成了完整的 "共享内存对象"。

再组织:通过链表、数组等数据结构将这些结构体组织起来,后续对共享内存的增、删、查、改操作,就转化为对该数据结构的操作。

三、系统调用接口介绍

在介绍系统调用之前,我们先梳理一下整个共享的流程:

首先进程 A 会调用第一个系统调用接口 (shmget) 创建共享内存,内核开辟一块物理内存,即共享内存本体。需要注意的是是内核创建的共享内存并非进程 A。又因为进程看不到并且不会和这块共享物理内存打交道,所以进程只能通过第二个系统调用接口 (shmat) 将共享内存挂接到自己的虚拟地址空间,从而得到虚拟地址的起始地址。下来进程 B 为了获取同一块共享内存,进程 B 也使用这个系统调用接口挂接到自己的虚拟地址空间,从而双方共用一块物理内存。下来准备工作已经完毕,进程AB之间就可以进行通信(读写数据),通信完毕后双方就先要取消各自挂接的共享内存就涉及到第三个系统调用的接口 (shmdt),进程 A 先取消挂接,接着进程 B 取消挂接,两个进程都取消挂接之后。最后还要删除这块共享内存,删除共享内存就要用到第四个系统调用接口 (shmctl),随后内核真正释放物理内存,整个流程结束。

shmget,创建共享内存

如何创建获取共享内存,那么就是使用第一个系统调用shmget,其中sh的意思是share共享,m是memory内存,shmget的意思即为共享内存获取分配的意思。

shmget 的作用是在内核中分配或查找一块共享内存段,并返回其唯一标识符 shmid。

第一个参数 key 的类型是 key_t,本质是一个整数,通常是 32 位,作用就是让不同的进程通过同一个 key 找到共享内存。key 相当于是"共享内存的名字/身份证号",不同进程只要约定好同一个 key,就能定位到同一块共享内存。

key 有两种常见生成方式:

方式一是手动指定整数 : 直接写一个固定整数,比如 0x666 或 1234,写法简单,测试方便。但是容易和系统中其他 IPC 资源冲突,不适合工程使用。

ftok() 函数生成 key 值

方式二是用 ftok() 函数生成 :

ftok() 的作用是把文件路径和项目标识组合成一个唯一的 IPC 键,能极大降低冲突概率:。

  1. 第一个参数 const char *pathname 代表路径参数,本质是指向一个已存在的文件/目录路径(必须真实存在,不能是不存在的路径)。内核层面上 ftok() 会读取这个文件的 inode 号(文件系统中唯一标识文件的编号),作为生成 key 的基础信息。注意只要路径指向的是同一个文件(inode 不变),多次调用 ftok() 都会得到相同的 key。如果文件被删除重建,inode 会变化,生成的 key 也会改变。可以用普通文件或目录,但必须保证通信双方都能访问到这个路径。
  2. 第二个参数 int proj_id 用来项目标识参数,本质是一个 8 位的标识符(取值范围是 0-255,通常用字符如 'a'、'b' 表示)。作用就是在同一个文件(同一个 inode)上区分不同的 IPC 资源。比如同一个文件可以生成多个 key:ftok(path, 'a') 对应共享内存,ftok(path, 'b') 对应消息队列,互不冲突。注意只有低 8 位有效,高位会被忽略,所以用 'a'(ASCII 97)和用 0x61 效果完全一样。通信双方必须使用完全相同的 pathname 和 proj_id,才能得到同一个 key,进而打开同一段 IPC 资源。如果计算的这个key真的产生了冲突,即与原有的svstemV系列的共享内存或者信号量,消息队列中的原有的key值产生了冲突,如何做,很简单,那么作为用户,更换一个pathname或者proj_id即可。

第二个参数 size 的类型是 size_t,是无符号整数类型,表示字节数量。size 的作用用来指定共享内存的大小。创建时是申请内存的依据;获取已有共享内存时,一般填 0 或不大于原大小的值。

内核按 4KB(4096 字节)为一页向上取整分配物理内存:申请1 ~ 4096字节,实际会分配 1 页(4096 字节),申请4097 ~ 8192字节,实际会分配 2 页(8192 字节),以此类推。但进程只能安全使用自己指定的 size 字节,超出会导致段错误。共享内存大小在创建时固定,后续不能扩容或缩容。

第三个参数 shmflg 是用来设置共享内存的访问权限(类似文件权限)。控制 shmget 是用来创建新共享内存,还是获取已有共享内存。它的类型是 int,由权限位 + 创建标志按位或组成。 权限位(低9位) 的格式是 0664、0644、0777等,以 0 开头表示八进制。分别对应当前用户、同组用户、其他用户的读/写/执行 权限。常用权限是 0666 (所有进程均可读写)。

创建标志位(控制行为):

  • IPC_CREAT : 如果 key 对应的共享内存不存在,就创建;如果已存在,就直接获取并返回 shmid。
  • IPC_CREAT | IPC_EXCL : 必须是不存在才能创建成功;如果已存在,直接报错返回 -1。保证本次一定是新建,不会拿到旧的共享内存。

第三个参数的常见组合用法:

  1. IPC_CREAT | 0666 :存在则获取,不存在则创建。
  2. IPC_CREAT | IPC_EXCL | 0666 :必须全新创建,否则失败。
  3. 直接传 0666 :只获取已存在的,不创建。

返回值 : 创建成功返回一个非负整数 shmid,即共享内存标识符,后 shmat、shmdt、shmctl都靠它来标识这块共享内存。失败则返回 -1,并通过 errno 错误码说明失败原因。

shmid 的本质就是内核用来唯一标识一块共享内存的 ID 编号,作用和文件系统中的inode 号非常相似,inode 唯一标识一个文件,shmid 唯一标识一块共享内存。在内核内部,每一块共享内存都由一个结构体(struct shmid_kernel)来描述,里面记录着各种共享内存的信息:共享内存的大小、对应的物理内存起始地址、挂载进程数量、权限信息、创建时间、关联的 key 等。内核会用数组或类似结构管理所有共享内存对象,shmid 本质上就是这个结构体在数组中的索引/编号,内核通过它来定位、索引、操作对应的共享内存管理结构体,进而操作这块物理内存。

shmid和key对比
  1. key 偏向进程间约定,是进程A和进程B商量好的一个编号,目的是大家凭这个编号,找到同一块共享内存,属于进程之间的协议,不是内核内部管理用的。
  2. shmid 完全是内核层面,是内核自己分配、自己管理的编号,内核用它找对应的结构体、物理内存,进程拿到后只是"转手交给内核",自己不解释它。

key 是进程之间约定的"用户层标识"
shmid 是内核内部使用的"内核层标识"

shmat,挂接接口

shmat(share memory attach)即共享内存挂载,是将内核中已创建的共享物理内存,挂接映射到进程的虚拟地址空间,让进程可以像访问普通内存一样读写共享内存。

第一个参数是由 shmget 返回的共享内存 ID,用来指定要挂载哪块共享内存。

第二个参数指定把共享内存映射到进程虚拟地址空间的哪个位置。但是我们知道应该挂接到哪里吗?实际上是不知道的,所以这里我们设置为 nullptr 让内核自动选择合适的虚拟地址。

第三个参数是挂载标志,一般我们传默认值 0 表示以读写方式挂载共享内存即可。

对于 shmat 的返回值 shmaddr 其实是操作系统在进程虚拟地址空间中为共享内存分配的起始虚拟地址 。所以对于 shmat 的返回值我们是一定要接收的,因为后续执行共享内存去挂载操作时需要使用该地址作为参数。

返回值类型为 void*,本身不具备可操作性,无法直接解引用或下标访问。实际使用时需强制类型转换为具体指针类型:若按字节流 / 字符串处理 → 转换为 char*,若按自定义结构体处理 → 转换为对应结构体指针,这与 malloc 返回 void* 后需强转的逻辑完全一致。

此时我们可以将共享内存挂接到进程地址空间上了,那么可不可以将共享内存在地址空间上去挂接操作呢?可以的,通过 shmdt 即可。

shmdt,去挂接

shmdt 与 shmat 是一对互补操作,用于将共享内存从进程的虚拟地址空间中分离(去挂载)。

参数 shmaddr 就是由 shmat 返回的共享内存虚拟地址起始指针,也就是进程之前挂载时得到的那个地址。

返回值成功时返回 0,表示共享内存已从当前进程虚拟地址空间分离。失败返回 -1,并设置 errno 来指示错误原因(如传入的地址无效、未挂载过等)。

shmctl,关闭共享内存

shmctl 是共享内存的控制操作接口,ctl 就是 control 控制的意思,用于查询、修改或删除共享内存段,是整个共享内存生命周期的收尾操作。

第一个参数 shmid 就是由 shmget 返回的共享内存标识符,用于指定要操作的目标共享内存。

第二个参数 cmd 是一个整数类型的命令,它是一个宏定义,通过宏定义不同位置的比特位传达不同的命令,下面我们认识一下宏定义,控制命令,决定要执行的操作,核心命令如下:

  1. IPC_STAT :获取共享内存的状态信息,将内核中的 shmid_ds 结构体拷贝到 buf 中。
  2. IPC_SET :修改共享内存的权限等属性(需有权限),将 buf 中的数据同步到内核。
  3. IPC_RMID :标记删除共享内存(最常用),将共享内存标记为待删除,等待最后一个挂载进程分离后,内核才会真正释放物理内存。

第三个参数 buf 时指向 struct shmid_ds 结构体的指针,用于传递或接收共享内存的状态信息;若执行 IPC_RMID 关闭删除共享内存时,通常传 NULL 。

共享内存状态结构体 struct shmid_ds

下面我们来看一个结构体 struct shmid_ds,这个结构体就是共享内存状态结构体,这个结构体是内核用来描述一块共享内存完整状态的数据结构,也是 shmctl 中 IPC_STAT/IPC_SET 命令的核心数据载体。

字段 类型 含义
shm_perm struct ipc_perm 共享内存的权限与归属信息,包含创建者 UID、GID、访问权限位等(类似文件的权限信息)。
shm_segsz size_t 共享内存段的总大小(字节数) ,即 shmget 时指定的 size
shm_atime time_t 最后一次 挂载(shmat) 的时间戳。
shm_dtime time_t 最后一次 分离(shmdt) 的时间戳。
shm_ctime time_t 共享内存创建时间 ,或最后一次通过 shmctl 修改属性的时间戳。
shm_cpid pid_t 创建该共享内存进程的 PID。
shm_lpid pid_t 最后一次执行 shmatshmdt 操作进程的 PID。
shm_nattch shmatt_t 当前挂载该共享内存的进程数量(nattch = number of attaches)。

与 shmctl 的配合使用 :

  1. IPC_STAT:将内核中该共享内存的 shmid_ds 数据拷贝到用户提供的 buf 指针中,用于查询状态。
  2. IPC_SET:将用户 buf 中的部分字段(如 shm_perm 权限)同步到内核,修改共享内存属性。
  3. IPC_RMID:无需使用 buf(传 NULL),直接标记共享内存为待删除。

shm_nattch 是判断共享内存是否还在被使用的核心字段:当 nattch 为 0 且被标记为删除时,内核才会释放物理内存。

四、代码实现

我们要实现一个基于 System V 共享内存的双进程通信 Demo,核心目标是让两个独立进程通过一块共享内存完成高效数据传递 ------ 一个进程负责写入数据,另一个进程负责读取数据。为了让代码结构清晰、易于复用和维护,我们将整个项目拆分为四个核心文件,分别承担不同职责,形成 "底层封装 + 业务逻辑 + 工程构建" 的分层架构。

  1. Shm.hpp:作为共享内存的核心封装层,将 shmget、shmat、shmdt、shmctl 等底层系统调用封装为面向对象的 Shm 类,统一管理共享内存的创建、获取、挂载、分离和删除,对外提供简洁易用的接口,屏蔽底层细节。
  2. Server.cc作为共享内存的管理者与读端,负责创建共享内存、循环读取其中的数据,并在程序结束时完成共享内存的销毁,是整个通信流程的 "资源主控方"。
  3. Client.cc作为共享内存的使用者与写端,仅获取已创建的共享内存,向其中写入用户输入的数据,不参与共享内存的创建与销毁,体现 "按需使用" 的设计思想。
  4. Makefile:作为工程化构建脚本,定义编译规则,实现一键生成服务端与客户端可执行文件,同时提供清理功能,让项目构建更高效、规范。

Makefile

Shm.hpp

Create() 和 Get() 本质上都是通过 shmget 获取共享内存,底层逻辑完全一致,仅在创建标识 shmflg 上存在差异,因此我们将生成 key、调用 shmget、错误处理、保存 shmid 等公共代码抽取到 GetHelper 函数中实现复用;Create() 传入 IPC_CREAT | IPC_EXCL | 0666 用于创建全新共享内存,Get() 传入 IPC_CREAT 用于获取已存在的共享内存,两者通过传递不同参数,共用同一套底层逻辑,既简化了代码,又让结构更加清晰统一。

Server.cc

服务端 Create ():创建共享内存,是资源的所有者。一块共享内存,只创建一次,被多个进程 Get 使用。

Client.cc

客户端 Get ():获取共享内存,是资源的使用者。

运行:

运行的同时使用监控脚本进行检测,期望观察到挂接数字的变化:

这个ipcs监控脚本用于实时查看系统共享内存状态,输出信息包含七列核心字段:key是进程间约定的查找标识,shmid是内核分配的唯一操作 ID,owner是共享内存所属用户,perms是访问权限,bytes是共享内存大小,nattch是当前挂载进程数量(挂载 + 1、分离 - 1),status标记内存是否待删除。脚本通过每秒刷新输出,让我们直观观察共享内存从创建、挂载、使用到分离、删除的完整生命周期变化。

还要补充的是我们还可以在命令行中查看共享内存,删除共享内存:

  1. 命令行中查看共享内存使用的是ipcs -m
  2. 命令行中删除共享内存使用的是ipcrm -m 共享内存标识符

首先我们先运行服务端:

这是运行 ./Server 后打印的共享内存初始化信息,代表共享内存刚创建完成、还未被任何进程挂载的状态:key=0x66024001, _shmid = 1,key由 ftok("/tmp", 0x66) 生成的进程间约定键值,用于让客户端定位到同一块共享内存。_shmid是内核为这块共享内存分配的唯一标识符 shmid,后续挂载、分离、删除操作都依赖它。shm_nattch 是当前挂载这块共享内存的进程数。这里为 0,说明共享内存已创建,但还没有任何进程执行 Attach() 挂载,符合刚创建后的初始状态。

shm_segsz: 0x80 是共享内存的总大小(字节)。0x80 是十六进制,等于十进制的 128,和我们代码中定义的 const int gsize = 128 完全对应,验证了共享内存大小创建正确。

  1. 服务端启动:调用 Create() 创建共享内存 → nattch = 0。调用 Attach() 挂载 → nattch = 1。
  2. 客户端启动:调用 Get() 获取共享内存 → nattch 保持 1。调用 Attach() 挂载 → nattch = 2(两个进程同时挂载)。
  3. 客户端退出:调用 Detach() 分离 → nattch = 1(只剩服务端挂载)。
  4. 服务端退出:调用 Detach() 分离 → nattch = 0。调用 Delete() 标记删除 → 共享内存被内核回收,从列表中消失。

这里的 shmid 是内核分配给共享内存的唯一序列号,每次创建新的共享内存都会自动加 1,即使旧的共享内存被删除,编号也不会复用,只会持续递增,直到系统重启后才会重新从 0 开始计数,以此保证每个共享内存的 ID 在系统内全局唯一。

key 值是进程中用来定位共享内存的唯一标识,由 ftok 生成,在共享内存创建时就永久绑定,它的存在与是否有进程挂载(nattch 数量)无关;无论是否有进程使用,只要共享内存未被内核删除,key 值就一直存在,用于让客户端和服务端能找到同一块共享内存。

进行通信:

上面我们所做的一切都是通信前的准备,真正的关键是用共享内存来进行进程间通信,所以下面我们分别要对Server.cc和Client.cc文件进行调正,调整为一方发送信息,一方接收信息,从而实现通信功能:

Server.cc :

服务端作为共享内存的创建者和读取端,首先创建内核中的物理内存,并通过 Attach 将这块内存映射到自己进程的虚拟地址 _start_addr。服务端使用 cout << shm_start 读取数据,shm_start 是 char* 类型的指针,代表共享内存的起始地址。cout 在识别到 char* 时,会自动对地址进行解引用,从指针指向的内存地址开始读取数据,直到遇到结束符,这个过程本质就是通过虚拟地址访问物理内存,不需要显式使用 * 也能完成解引用操作,从而直接获取客户端写入的数据。

Client.cc :

客户端作为共享内存的使用者和写入端,通过 Get 获取服务端创建的物理内存,并使用 Attach 将同一块内存映射到自己的虚拟地址 _start_addr。客户端使用 memset(shm_start, 0, size) 清空内存,该函数会接收地址并对地址解引用,将数据写入物理内存;同时使用 strncpy(shm_start, data, size-1) 写入用户输入的数据,该函数同样会对传入的地址进行解引用,把数据直接存到指针指向的物理内存中。因为服务端和客户端映射同一块物理内存,所以客户端通过解引用写入的数据,服务端可以立刻通过解引用读取,实现真正高效的进程间通信。

memset(shm_start, 0, size在干嘛?shm_start 是一个指针,本质就是一个虚拟地址,指向共享内存的起始位置。memset 函数的作用是把一段内存区域全部设为某个值(这里是 0)。它接收地址 shm_start,然后对这个地址做解引用:

cpp 复制代码
// 伪代码,memset 内部大概是这样:
for (int i = 0; i < size; i++) {
    *(shm_start + i) = 0;  // 对地址解引用,把 0 写到物理内存里
}

strncpy(shm_start, data, size-1在干嘛?strncpy 是 "字符串拷贝" 函数,作用是把一段字符串数据拷贝到目标地址。它接收目标地址 shm_start,然后对这个地址做解引用,就是把用户输入的字符串,通过解引用,直接写到共享内存的物理地址里

cpp 复制代码
// 伪代码,strncpy 内部大概是这样:
for (int i = 0; i < size-1 && data[i] != '\0'; i++) 
{
    *(shm_start + i) = data[i];  // 对地址解引用,把字符写到物理内存里
}
*(shm_start + size-1) = '\0';  // 保证字符串结束

共享内存之所以能通信,核心就是两个进程映射到同一块物理内存,并通过_start_addr解引用直接读写这块内存。_start_addr是共享内存的起始虚拟地址,进程并不能直接用 "地址" 传递信息,而是对这个地址进行解引用,去访问地址指向的物理内存:客户端对自己的_start_addr解引用写入数据,数据就存进公共物理内存;服务端对自己的_start_addr解引用读取数据,就能直接拿到内容。两个进程的虚拟地址虽然不同,但解引用后指向同一块物理内存,一方写入、一方读取,不需要任何数据拷贝,这就是共享内存高效通信的核心原理。

通信结果:

客户端输入的nihao、1、2、3等数据,能够实时、准确地传递到服务端并完整打印输出,无乱码、无脏数据、无信息丢失。右侧ipcs显示挂载数为2,证明服务端与客户端成功挂载同一块物理内存,通过虚拟地址_start_addr的解引用操作,实现了高效、实时、可靠的进程间通信,整体效果符合预期。

五、共享内存的特性

1. 共享内存是进程间通信(IPC)中速度最快的方式,其核心原因在于零数据拷贝 。与管道等通信机制不同,管道的数据需要经过 "用户层 → 内核缓冲区 → 用户层" 的两次拷贝,而共享内存直接让进程映射到同一块物理内存。进程 A 和进程 B 通过各自的页表,将同一块物理内存挂载到自己的虚拟地址空间中。进程 B 直接通过虚拟地址解引用 向物理内存写入数据,进程 A 则通过自己的虚拟地址解引用直接读取同一块内存中的数据,数据没有在用户态和内核态之间反复搬运,实现了最高效的直接数据传递。

  1. 除了零拷贝的高性能,共享内存还具备两个关键特性:一是缺乏原生的进程间协同与同步互斥机制 ,这意味着操作系统不会自动保护数据一致性,读写操作需要用户自行通过信号量等机制实现同步,否则容易出现竞争问题;二是数据格式与访问完全由用户自定义,系统仅提供物理内存映射,用户可以根据需求将共享内存当作字符数组、结构体或其他数据结构来使用,自由控制数据的写入与读取格式。正是凭借 "零拷贝、高速、用户自定义" 的特点,共享内存成为大数据传输、高频通信场景的首选 IPC 方式。

共享内存为什么缺乏同步互斥?

  • 共享内存的设计追求极致速度,它仅提供物理内存映射,让进程直接读写内存,不涉及任何阻塞、等待或保护机制 。多个进程可以同时访问同一块内存,内核不会干预访问顺序,因此共享内存没有原生同步与互斥能力,必须依靠信号量、互斥锁等外部工具保证数据安全。

与管道的本质区别

  • 管道是基于内核缓冲区的通信方式,由内核管理数据的读写顺序,自带同步、互斥与阻塞机制:读空会等待、写满会阻塞,数据不会被破坏。但管道需要内核拷贝数据,速度较慢;而共享内存无需拷贝、速度最快,但完全没有同步保护,需要用户自己管理进程访问秩序。

六、总结

本文介绍了SystemV共享内存通信机制的原理与实现。共享内存通过内核开辟公共物理内存区域,由多个进程映射到各自虚拟地址空间实现高效通信,是速度最快的IPC方式。文章详细讲解了共享内存的创建、挂接、读写和释放流程,以及相关系统调用接口(shmget/shmat/shmdt/shmctl)的使用方法。通过代码示例展示了双进程通信的具体实现,并分析了共享内存零拷贝、高速度但缺乏同步机制的特性。最后比较了共享内存与管道的本质区别,指出共享内存适合大数据传输但需要额外同步机制保障数据安全。

感谢大家的观看!

相关推荐
Lana学习中42 分钟前
【运维杂记】连接不上远程服务器的问题处理
运维·服务器
189228048611 小时前
NV023固态MT29F16T08GWLCEJ9-QBES:C
大数据·服务器·人工智能·科技·缓存
AOwhisky2 小时前
MySQL 学习笔记(第一期):数据库基础与 MySQL 初探
运维·数据库·笔记·学习·mysql·云计算
Peace2 小时前
【Prometheus】
linux·运维·prometheus
LZZ and MYY3 小时前
RTS 在windows和Linux之间ShareMem
linux·运维·服务器
aningx3 小时前
openSUSE Leap 16.0 运行 sunshine 报错的解决方法
linux
爱学习的徐徐3 小时前
Linux 基础IO
linux·服务器
蛋蛋的学习记录3 小时前
C#窗体应用中使用EasyModbusCore通讯
服务器·c#·tcp
zt1985q3 小时前
本地部署源代码管理解决方案 Bitbucket Data Center 并实现外部访问
运维·服务器·数据库·网络协议·postgresql·源代码管理
xiaobobo33303 小时前
面向对象:linux内核中函数转数据的用法
linux·面向对象·隔离·函数指针绑定