Linux系统篇26——通信(三)补:共享内存访问真的不进内核?

上一篇《通信(三):System V 共享内存:从页表映射到 shmget/shmat 一文搞懂》把接口、生命周期、key/shmid 都过了一遍。但有个问题一直没正面答:共享内存映射建好之后,进程读写那段内存,到底要不要进内核、走不走系统调用? 这篇把它讲透,顺带补三个篇25 没细说的隐藏细节。


一、先给结论:访问共享内存没有系统调用

直接回答:映射建好之后,进程对共享内存的每一次读写,都不会触发系统调用。

反直觉点:很多人默认"IPC 嘛,肯定要经过内核",共享内存恰恰是反例。它快不是"快一点",是根本不进内核

为什么这个问题关键:弄懂"不进内核",就弄懂了它为什么是 IPC 速度天花板,也弄懂了"零拷贝"到底省掉了哪一步。


二、为什么没系统调用:地址在用户空间

进程虚拟地址空间布局大致是 :代码段、数据段、堆、栈......共享内存的映射区域通常落在堆和栈之间的用户空间区域。

管道不一样管道是文件,数据存在内核的文件缓冲区。用户要读写必须调 read / write,这俩本身就是系统调用,会切到内核态把数据从内核缓冲区搬到用户空间(或反过来)。

共享内存相反shmat 把那段物理内存映射进本进程的虚拟地址空间后,你拿到的 shmaddr 就是一个普通用户态虚拟地址 。之后 addr[i] = 'A' 这种操作,和写普通数组没区别------CPU 经 MMU 直接访问页表指向的物理页,全程在用户态,内核不参与。

澄清一个误区(回指篇25):不是"建立映射不进内核"。shmget / shmat / shmdt / shmctl 都是系统调用,页表改写由内核完成。但映射建好之后的那无数次读写,不再进内核。一次建、永远用,这就是它比管道省掉的两趟拷贝。


三、shmat 返回的是什么:虚拟地址,和 malloc 同理

shmat 返回 void*,本质是一个虚拟地址,不是物理地址,也不是"文件句柄"。

类比 malloc:malloc 返回的也是虚拟地址,物理页是内核按需分配的;shmat 同理,区别只在于它映射到的物理页是多进程共享的那一块 ,而 malloc 拿到的是本进程私有物理页。

推论:你在共享内存上做的所有操作------指针偏移、memcpy、下标访问------都和本地内存一样,零额外成本。这也是"零拷贝"的真正含义:没有内核搬运这一环,数据是"大家直接改同一块物理页"。


四、大小为什么必须按 4KB 对齐

物理内存按 管理,Linux 通常一页 4096 字节。shmget 申请共享内存时,内核按页向上取整分配。

你传 size = 4097 会怎样?内核实际给你两页 = 8192 字节 ,但 shmid_ds.shm_segsz 显示的还是你申请的 4097。多出来的部分被浪费,且你不能越界使用。

验证方法:

bash 复制代码
ipcs -m 

ipcs -m 看对应段的 size 列,或在代码里打印 shm_segsz。一般直接传 4096 的整数倍(4096、8192......)最干净。

划重点:这不是"建议",是内核分配粒度的硬约束。不对齐不会报错,但会多占物理页、造成浪费。


五、没有同步,现场能乱成什么样

篇25 说过"共享内存不带同步互斥",这里给个直观现场。

基于篇25 的 server / client demo:如果 server 先启动,sleep 2 后开始循环读;而 client 还在 sleep 1 + sleep 2,还没往里写。

server 第一次 printf 读到的就是空白或上一次的旧数据------不是"等 client 写完再读",而是直接读、读到啥算啥。

根因 :共享内存只负责"大家看得到同一块",不负责"谁先写谁后读"的顺序,也没有读写保护。多个进程并发写还会互相覆盖。

结论:实际项目里共享内存必须配信号量(下一篇《通信(四)》)或用管道做通知。本篇只点现象,方案留到下篇。


六、回指篇25:接口 / 生命周期 / key-shmid 不重讲

这篇是篇25 的补遗,下面这些篇25 已经讲透,这里不重复:

  • 五个接口 ftok / shmget / shmat / shmdt / shmctl 的用法;
  • 生命周期随内核、ipcs -m / ipcrm -m 清理残留;
  • key 与 shmid 的区别;
  • 多文件 demo 与 IPC_CREAT | IPC_EXCL

没看过篇25 的,先去把《通信(三):System V 共享内存:从页表映射到 shmget/shmat 一文搞懂》看了,再回来啃这篇细节。


七、共享内存的优缺点

前面几节把机制拆开了,这里收一张总表,方便面试时一口报全。

优点:

  • 速度最快:映射建好后读写不进内核、零拷贝,是 IPC 里的性能天花板。
  • 用法简单:shmat 拿到指针就能当本地内存用,和 malloc 一个道理,没有 read/write 那套。
  • 扛大块数据:管道搬大块数据开销明显,共享内存无内核搬运,量越大优势越突出。

缺点:

  • 无同步互斥:多进程并发读写会乱,必须自己配信号量或用管道通知(本篇五已给现场)。
  • 无访问控制:任何挂接进程都能读能写,没有读写权限隔离。
  • 生命周期要手动管:随内核,不删就泄漏,得靠 ipcrm 或 shmctl(IPC_RMID) 清理。
  • 大小受页对齐约束:不对齐浪费物理页(本篇四已讲)。

一句话:共享内存是"把性能给你,把秩序留给你自己"的 IPC。


八、面试官追问(本篇新增)

问题 答案
访问共享内存期间会发生系统调用吗? 映射建好之后不会。shmat 返回的是用户态虚拟地址,后续读写只是访问自己的页表映射,全程用户态;只有建 / 挂 / 摘 / 删那几次系统调用才进内核。
共享内存大小为什么要按页对齐? 内核按 4KB 物理页分配,传 4097 实际拿 8192,多出的浪费;传整数倍最干净。
shmat 返回的和 malloc 返回的本质区别? 都是虚拟地址; 区别在映射到的物理页:malloc 是进程私有,shmat 是多进程共享同一块。
为什么共享内存比管道快? 管道数据走内核文件缓冲区,每次读写都系统调用搬数据; 共享内存映射后直接用户态访问物理页,无内核搬运。

九、小结

一句话:共享内存映射建好后,读写不进内核、无系统调用,因为它是用户空间的虚拟地址;shmat 和 malloc 同理,只是物理页被多个进程共享。

三个隐藏细节:

  1. 没系统调用,是它最快的根本原因;
  2. 大小按 4KB 页对齐,不对齐白白浪费物理页;
  3. 无同步会导致读到空白 / 旧数据,必须配信号量。

回指篇25 看接口与生命周期;下一篇《通信(四)》用信号量把"同步"这个坑正式填上。

相关推荐
不会就选b1 小时前
Linux之socket编程(七)----序列化与反序列化(1)
java·linux·服务器
极小狐1 小时前
极狐GitLab Duo 功能更新:扩展 MCP 工具集、支持 MR 事件触发
运维·gitlab·agent·mr·极狐gitlab·mcp·极狐gitlab duo
隔窗听雨眠1 小时前
无辅助损失函数的负载均衡:DeepSeek MoE架构的核心突破
运维·架构·负载均衡
zerwave1 小时前
从零实现简化 DDS 完整技术文档(含模块作用与详细注释)
linux·c++·机器人
guo_wen_qiang3 小时前
mac中docker desktop服务端开启远程访问
运维·服务器·macos·docker
小五传输4 小时前
消防总队信创改造实践:国产文件传输服务器解决文件传输难题
大数据·运维·安全
云飞云共享云桌面4 小时前
医疗器械设备研发:多人同时操作 SolidWorks,怎样依靠单台服务器替代多工作站
运维·服务器·网络·数据库·制造
T型码农要学习4 小时前
开源项目5|FileBrowser:免费在线文件管理器!随时随地管控服务器文件
运维·服务器·人工智能·开源
朱 欢 庆5 小时前
云服务器附件备份到本机内网服务器
运维·服务器·前端·经验分享