云计算百科
云计算领域专业知识百科平台

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。多出来的部分被浪费,且你不能越界使用。

验证方法:

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 同理,只是物理页被多个进程共享。

三个隐藏细节:

  • 没系统调用,是它最快的根本原因;
  • 大小按 4KB 页对齐,不对齐白白浪费物理页;
  • 无同步会导致读到空白 / 旧数据,必须配信号量。
  • 回指篇25 看接口与生命周期;下一篇《通信(四)》用信号量把"同步"这个坑正式填上。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux系统篇26——通信(三)补:共享内存访问真的不进内核?
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!