跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

Linux 进程通信:System V 共享内存原理与 C++ 封装实战

综述由AI生成System V 共享内存是 Linux 下速度最快的进程间通信方式,数据直接驻留物理内存无需拷贝。介绍其核心概念、映射原理及 ftok/shmget/shmat/shmdt/shmctl 五大接口。通过 C++ 类封装实现 Server-Client 通信,并结合 FIFO 解决同步问题。同时分析了权限拒绝、大小对齐、sleep 时序及删除后访问等常见 Bug 的解决方案。

JavaCoder发布于 2026/2/6更新于 2026/5/3025 浏览
Linux 进程通信:System V 共享内存原理与 C++ 封装实战

一、System V 共享内存:最快的进程间通信

System V 是 Linux 内核支持的一套 IPC(进程间通信)标准,其中共享内存是速度最快的 IPC 方式——因为数据直接存在'共享物理内存'中,进程读写无需拷贝(其他 IPC 如 FIFO 需要内核中转)。

1. System V 共享内存核心概念

  • IPC 本质:让不同进程'看到同一份资源'。共享内存的'资源'是一块物理内存,OS 通过'页表'将这块物理内存映射到多个进程的虚拟地址空间中。
  • 优势:读写速度快(进程操作自己的虚拟地址,等同于操作共享物理内存,无内核拷贝)。
  • 劣势:无同步机制(比如两个进程同时写,会导致数据混乱,需配合其他 IPC(如 FIFO)实现同步)。

2. System V 共享内存原理

(1)进程虚拟地址空间结构

每个进程都有独立的虚拟地址空间,分为内核区、栈、堆、共享库区等,默认情况下进程间的内存互不干扰。

系统架构示意图

(2)共享内存映射过程

OS 通过 3 步让进程共享内存:

  1. OS 在物理内存中开辟一块'共享物理内存'。
  2. 进程 A 的虚拟地址空间中,分配一块虚拟地址,通过'页表'映射到共享物理内存。
  3. 进程 B 的虚拟地址空间中,也分配一块虚拟地址,同样通过页表映射到同一块共享物理内存。
    最终效果:进程 A 写自己的虚拟地址,进程 B 读自己的虚拟地址,就能拿到相同的数据。

(3)共享内存的管理:先描述,再组织

OS 用一个'内核结构体'(struct shmid_ds)描述共享内存的信息(如大小、权限、引用计数),再用链表/哈希表组织所有共享内存,方便管理。

引用计数:记录有多少进程正在使用这块共享内存。只有引用计数为 0 时,OS 才会真正释放物理内存(避免进程还在使用时内存被删)。

引用计数示意图

3. System V 共享内存核心接口

操作共享内存需 4 个核心接口:ftok(生成唯一 key)、shmget(创建/获取共享内存)、shmat(将共享内存映射到进程虚拟地址)、shmdt(解除映射)、shmctl(管理共享内存,如删除)。

(1)生成唯一 Key:ftok

  • 功能:将'文件路径'和'项目 ID(proj_id)'组合成一个唯一的 key_t 类型值(key),用于标识共享内存(确保不同进程能找到同一块共享内存)。
    • pathname:必须是已存在的文件路径(比如 "./")。
    • proj_id:1~255 的整数(自定义,只要进程间一致即可)。
    • 返回值:成功返回 key,失败返回 -1。

示例:两个进程都用 ftok("./", 100),会生成相同的 key,从而找到同一块共享内存。

ftok 示例图

ftok 参数图

接口参数与返回值:

ftok 详情图

(2)创建/获取共享内存:shmget

  • 功能:根据 key,创建新的共享内存,或获取已存在的共享内存(返回一个'共享内存 ID(shmid)',后续操作都用 shmid)。
    • key:由 ftok 生成的唯一标识。
    • size:共享内存的大小(必须是 4KB 的整数倍,OS 按页分配内存,不足 4KB 会自动补齐)。
    • shmflg:标志位,常用组合:
      • IPC_CREAT:如果共享内存不存在,则创建;如果已存在,则获取。
      • IPC_CREAT | IPC_EXCL | 0664:如果共享内存已存在,则报错(确保创建的是全新的共享内存);0664 是共享内存的权限(和文件权限一致)。
    • 返回值:成功返回 shmid(非负整数),失败返回 -1。

返回值说明:

shmget 返回值图

接口参数与返回值:

shmget 详情图

(3)映射共享内存到进程:shmat

  • 功能:将 shmid 对应的共享内存,映射到当前进程的虚拟地址空间中,返回映射后的虚拟地址(进程通过这个地址读写共享内存)。
    • shmid:shmget 返回的共享内存 ID。
    • shmaddr:指定映射到进程虚拟地址的哪个位置(一般设为 NULL,让 OS 自动分配,避免冲突)。
    • shmflg:映射标志(一般设为 0,默认权限)。
    • 返回值:成功返回映射后的虚拟地址(void* 类型),失败返回 (void*)-1。

示例:

shmat 示例图

shmat 运行图

接口参数与返回值:

shmat 详情图

(4)解除映射:shmdt

  • 功能:将共享内存从当前进程的虚拟地址空间中'解绑'(不会删除共享内存,只是进程看不到它了)。
    • shmaddr:shmat 返回的虚拟地址(必须和映射时的地址一致)。
    • 返回值:成功返回 0,失败返回 -1。
  • 注意:解除映射后,共享内存的'引用计数'会减 1;若引用计数变为 0,OS 也不会立刻删除共享内存(需调用 shmctl 删除)。

接口参数与返回值:

shmdt 详情图

(5)管理共享内存:shmctl

  • 功能:对共享内存进行管理,如查看信息、修改权限、删除共享内存(最常用的是删除)。
    • shmid:shmget 返回的共享内存 ID。
    • cmd:命令,常用 IPC_RMID(删除共享内存)。
    • buf:指向 struct shmid_ds 的指针(用于传递/接收共享内存的信息,删除时可设为 NULL)。
    • 返回值:成功返回 0,失败返回 -1。
    • 注意:调用 shmctl(shmid, IPC_RMID, NULL) 后,共享内存会被'标记为删除',但不会立刻消失——直到所有进程都 shmdt 解除映射(引用计数为 0),OS 才会真正释放物理内存。

删除共享内存示例:

shmctl 删除图

接口参数与返回值:

shmctl 详情图

4. 共享内存实战:查看与删除(命令行)

(1)查看系统中的共享内存:ipcs -m

  • 功能:列出当前系统中所有 System V 共享内存的信息,包括 shmid、key、大小、引用计数等。
    • shmid:共享内存 ID(删除时用)。
    • key:ftok 生成的标识。
    • owner:创建共享内存的用户。
    • segsz:共享内存大小(4KB 的整数倍)。
    • nattch:引用计数(当前有多少进程映射了这块内存)。

示例:

ipcs 示例图

(2)删除共享内存:ipcrm -m shmid

  • 功能:根据 shmid 删除共享内存(等同于代码中的 shmctl(shmid, IPC_RMID, NULL))。
    • 注意:共享内存的生命周期'随内核'——即使创建它的进程退出,共享内存也会留在系统中(需手动 ipcrm 删除,或代码中 shmctl 删除),避免内存泄漏。

示例:

ipcrm 示例图

5. 共享内存实战:Server 与 Client 通信

(1)封装共享内存类(Shm.hpp)

为简化代码,用 C++ 类封装共享内存的操作(创建/获取、映射、读写、删除)。

删除共享内存:提供 Destroy(调用 shmctl)成员函数,在析构函数中调用 Detach。

Shm.hpp 销毁图

映射与解除映射:提供 Attach(调用 shmat)和 Detach(调用 shmdt)成员函数。

Shm.hpp 映射图

若 shmget 失败(比如共享内存已存在),报错信息如下:

shmget 错误图

创建/获取共享内存:在构造函数中调用 ftok 和 shmget。

Shm.hpp 构造图

(2)服务器端(server.cc):读共享内存

核心逻辑:创建共享内存 → 映射到虚拟地址 → 循环读数据 → 解除映射 → 删除共享内存。
代码示例:

server.cc 代码图

server.cc 运行图

(3)客户端(client.cc):写共享内存

核心逻辑:获取已存在的共享内存 → 映射到虚拟地址 → 写数据 → 解除映射(无需删除共享内存)。
代码示例:

client.cc 代码图

(4)通信效果

客户端写入数据后,服务器能立刻读到共享内存中的内容(无需内核中转,速度极快)。

通信效果图

6. 共享内存的同步问题与解决

共享内存本身没有同步机制——如果服务器还没读,客户端就写了新数据,会覆盖旧数据;如果两个进程同时写,会导致数据混乱。

解决方法:结合 FIFO 实现同步(用 FIFO 作为'信号通道',控制读写顺序)。
核心思路:

  1. 创建一个 FIFO(同步通道)。
  2. 客户端:写共享内存 → 向 FIFO 写一个'信号'(比如 1 字节)。
  3. 服务器:从 FIFO 读'信号'(阻塞等待) → 读共享内存。
    这样就能确保'客户端写完,服务器才读',避免数据覆盖。

(1)同步通道(FIFO)创建

FIFO 创建图

(2)服务器端(同步读)

服务器先从 FIFO 读'信号'(阻塞等待客户端写完),再读共享内存。

server 同步图

server 同步运行图

(3)客户端(同步写)

客户端先写共享内存,再向 FIFO 写'信号'(唤醒服务器读)。

client 同步图

client 同步运行图

7. 常见问题与 Bug 解决

(1)权限拒绝(Permission denied)

调用 shmat 时若报错'权限拒绝',是因为共享内存的权限不足(创建时 shmflg 没加权限)。
Bug 效果:

权限拒绝图

解决方法:创建共享内存时,在 shmflg 中加上权限(如 0664)。

修复权限图

修改后效果(映射成功):

修复成功图

(2)共享内存大小不是 4KB 整数倍

OS 按'页'(4KB)分配共享内存,若 size 不是 4KB 的整数倍,OS 会自动向上补齐(比如 size=4097,实际分配 8192 字节)。
但 ipcs -m 查看时,segsz 会显示用户设置的 size(而非补齐后的大小),容易误导。

大小对齐图

建议:创建共享内存时,手动将 size 设为 4KB 的整数倍(比如 size=4096、size=8192),避免浪费。

(3)sleep 导致的同步问题

若用 sleep 控制读写顺序(比如客户端 sleep(1) 后写,服务器 sleep(2) 后读),可能因时间差导致同步失败(比如 sleep 时间不够,服务器还没准备好,客户端就写了)。
Bug 效果:

sleep 问题图

解决方法:不用 sleep,改用 FIFO 等同步机制(如前所述),确保'写后再读'。

sleep 修复图

(4)共享内存删除后仍能访问

调用 shmctl(shmid, IPC_RMID, NULL) 后,共享内存被'标记为删除',但只要有进程还在映射(引用计数>0),进程仍能访问它;只有所有进程都 shmdt 后,共享内存才会真正消失。
示例:服务器删除共享内存后,客户端仍能读数据(直到客户端 shmdt)。

删除后访问图

删除后访问图 2

删除后访问图 3

8. 共享内存的内核数据结构(补充)

OS 用 struct shmid_ds 描述共享内存的详细信息,这个结构体包含以下核心字段:

shmid_ds 结构图

  • shm_perm:权限相关信息(如所有者、组、权限)。
  • shm_segsz:共享内存大小(用户设置的 size)。
  • shm_nattch:引用计数(当前映射的进程数)。
  • shm_atime:最后一次 shmat 的时间。
  • shm_dtime:最后一次 shmdt 的时间。
  • shm_ctime:最后一次修改(如权限、大小)的时间。

当调用 shmget 创建共享内存时,OS 会初始化这个结构体,并将 key 存入 shm_perm.__key 字段,用于标识共享内存。

shmid_ds 初始化图

shmid_ds 细节图

目录

  1. 一、System V 共享内存:最快的进程间通信
  2. 1. System V 共享内存核心概念
  3. 2. System V 共享内存原理
  4. (1)进程虚拟地址空间结构
  5. (2)共享内存映射过程
  6. (3)共享内存的管理:先描述,再组织
  7. 3. System V 共享内存核心接口
  8. (1)生成唯一 Key:ftok
  9. (2)创建/获取共享内存:shmget
  10. (3)映射共享内存到进程:shmat
  11. (4)解除映射:shmdt
  12. (5)管理共享内存:shmctl
  13. 4. 共享内存实战:查看与删除(命令行)
  14. (1)查看系统中的共享内存:ipcs -m
  15. (2)删除共享内存:ipcrm -m shmid
  16. 5. 共享内存实战:Server 与 Client 通信
  17. (1)封装共享内存类(Shm.hpp)
  18. (2)服务器端(server.cc):读共享内存
  19. (3)客户端(client.cc):写共享内存
  20. (4)通信效果
  21. 6. 共享内存的同步问题与解决
  22. (1)同步通道(FIFO)创建
  23. (2)服务器端(同步读)
  24. (3)客户端(同步写)
  25. 7. 常见问题与 Bug 解决
  26. (1)权限拒绝(Permission denied)
  27. (2)共享内存大小不是 4KB 整数倍
  28. (3)sleep 导致的同步问题
  29. (4)共享内存删除后仍能访问
  30. 8. 共享内存的内核数据结构(补充)
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Win10 彻底关闭 Microsoft 365 Copilot 弹窗的 6 种方法
  • Linux 下 libwebkit2gtk-4.1-0 安装与实战指南
  • 移动端部署 Stable Diffusion 开源方案与使用指南
  • 双指针算法专题:快乐数与盛水最多的容器
  • Tauri 项目结构解析:前端壳与 Rust 内核的协作及构建流程
  • 前端加密靶场 encrypt-labs 环境搭建与全关卡解析
  • Java 25 LTS 核心特性详解:语言改进与性能优化
  • Docker 部署 One API 大模型 Key 分发及接口管理系统
  • GitHub 上找神经网络 Draw.io 学术绘图模板指南
  • Java 全栈工程师面试实录:从基础到复杂场景
  • BERT 文本分类实战:代码逐行注释与原理详解
  • Python 读取 CSV 数据并筛选股票记录
  • 璞致 PZ-VU9P/VU13P FPGA 开发板与 Xilinx UltraScale Plus 架构解析
  • Trae 集成 Vizro:低代码构建专业数据可视化仪表板
  • C++ 继承入门 (下):友元、静态成员与菱形继承的底层逻辑
  • MacOS 多合一启动盘制作与系统降版本指南
  • Python IDLE 汉化方法
  • Arduino BLDC 自适应阻抗控制外骨骼机器人
  • Tomcat 服务器安全加固实战指南
  • Claude Code 本地部署与项目实战详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online