土法炼钢兴趣小组的算法知识备份

【操作系统百科】共享内存:SysV vs POSIX vs memfd

文章导航

分类入口
os
标签入口
#shm#shared-memory#memfd#hugetlbfs#tmpfs#file-seals#ipc

目录

共享内存是性能最极限的 IPC:生产者写、消费者读,同一页物理内存,没有 copy、没有 syscall(除同步原语)。但 Linux 上共享内存 API 有三代,写新代码该用哪个?答案是 memfd_create + mmap,其他都是历史兼容。

一、先看图

flowchart TB
    subgraph SysV[SysV 共享内存 · 历史]
      A1[shmget key]-->A2[shmid]
      A2-->A3[shmat → VA]
      A3-->A4[shmdt]
    end
    subgraph POSIX[POSIX 共享内存]
      B1[shm_open /name]-->B2[ftruncate]
      B2-->B3[mmap → VA]
      B3-->B4[munmap + shm_unlink]
    end
    subgraph Modern[现代范式]
      C1[memfd_create]-->C2[fcntl F_ADD_SEALS]
      C2-->C3[SCM_RIGHTS 传 fd]
      C3-->C4[mmap 对端]
    end
    classDef old fill:#63636e22,stroke:#636e7b,color:#adbac7;
    classDef mid fill:#388bfd22,stroke:#388bfd,color:#adbac7;
    classDef new fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    class A1,A2,A3,A4 old
    class B1,B2,B3,B4 mid
    class C1,C2,C3,C4 new

三者都落在同一物理内存(实际上 POSIX shm 和 memfd 都是 tmpfs 文件,SysV 是 shm 特殊 inode),区别在命名、生命周期、权限

二、SysV shmget:遗产 API

int id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
void *p = shmat(id, NULL, 0);
// ...
shmdt(p);
shmctl(id, IPC_RMID, NULL);

问题

SysV shm 唯一仍然常见的场景:PostgreSQL 老版本、一些商业数据库兼容层。新代码别写。

三、POSIX shm_open

int fd = shm_open("/foo", O_CREAT | O_RDWR, 0600);
ftruncate(fd, size);
void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// ...
munmap(p, size);
close(fd);
shm_unlink("/foo");

底层就是 /dev/shm(tmpfs)上的文件。POSIX 规定 /name 风格,Linux 把 /name 映射到 /dev/shm/name

优点:

缺点:名字全局命名空间,泄漏后需手工清理,和 SysV 差不多。

四、memfd_create:现代范式

int fd = memfd_create("buffer", MFD_CLOEXEC | MFD_ALLOW_SEALING);
ftruncate(fd, size);
void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

没有名字——只有 fd。好处:

seal 用在哪?看下节。

4.1 F_SEAL 的应用

Wayland / Chromium / Flutter 把共享 buffer 传给 compositor / GPU 进程时,生产者希望保证”传过去的这段内存以后不能变大小、不能再写入”,避免恶意子进程在 compositor 读的瞬间把页换了。

fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);

seal 是一次单向门。加了不能取消。接收方可以查 F_GET_SEALS 验证对方确实封了。

4.2 memfd 和 tmpfs 的关系

实现上:memfd 就是创建一个 tmpfs 匿名 inode(不链到任何目录),fd 指向它。其他进程只能通过 fd 引用,没法用路径开。

五、hugetlbfs:大页共享

共享 2MB / 1GB 大页场景:DPDK、QEMU vhost、Redis huge page、PostgreSQL。

方式 A:mount hugetlbfs

mount -t hugetlbfs none /mnt/huge -o pagesize=2M

然后在上面 open + mmap(MAP_SHARED)

方式 B:memfd 加 MFD_HUGETLB

int fd = memfd_create("hp", MFD_CLOEXEC | MFD_HUGETLB | MFD_HUGE_2MB);

方式 C:mmap(..., MAP_HUGETLB | MAP_HUGE_2MB)

注意 HugeTLB 页必须预留/proc/sys/vm/nr_hugepages),不能像 THP 那样延迟组装。预留失败 mmap 直接返回 ENOMEM。

QEMU 用 memfd + MFD_HUGETLB + SCM_RIGHTS 把 guest RAM 传给 vhost-user 进程,是现代虚拟化主流路径。

六、共享内存里的同步

共享内存本身没有同步。并发读写必须自己加同步:

关键难点:进程间持锁的 crash。一个进程持 pthread mutex 的时候 crash,其他进程卡死。解决:PTHREAD_MUTEX_ROBUST + pthread_mutex_consistent,或者用 futex 手动写状态机。

七、/dev/shm 与容量管理

/dev/shm 是一个 tmpfs,默认容量 = RAM/2。docker 里默认 64MB(用 --shm-size 调)。

常见坑:

监控:df -h /dev/shm;进程级 lsof +D /dev/shm

八、跨机器共享内存?

严格说不存在(无 cache coherence)。但有近似方案

单机才讲”共享内存 = 零拷贝”,跨机一切都是消息。

九、共享内存的安全边界

从一个进程把 fd 传给另一个 UID 的进程 = 绕过路径权限的后门。SCM_RIGHTS 把 fd 送到 bytestream 过去,对端 mmap 就有 RW 能力。

防御:

十、常见 bug

bug A:服务重启 /dev/shm 里残留旧段 原因:shm_unlink 未调;服务 crash 解决:atexit 或手动清理;或迁移到 memfd

bug B:SysV shm 容量不够 原因kernel.shmmax 默认小 解决:sysctl 调;或迁移到 memfd

bug C:进程间 mutex 死锁 原因:持锁 crash 没 robust 解决PTHREAD_MUTEX_ROBUST

bug D:hugetlbfs MAP_FAILED 原因nr_hugepages 不够或预留失败 诊断cat /proc/meminfo | grep Hugegrep -r . /sys/kernel/mm/hugepages/

bug E:IPC_PRIVATE 段在 fork-exec 后对端不认识 原因:id 是 PID 相关的全局号,但接受方要知道 id 才能 shmat 解决:父把 id 通过 env/cmdline/socket 传给子

十一、小结

下一篇 B-16 讲消息队列——SysV 与 POSIX mq 的历史、kdbus 的夭折、以及现代为什么几乎都转向 socket/broker。


参考文献

工具


上一篇管道、FIFO、socketpair 下一篇消息队列:SysV、POSIX mq、kdbus

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-18 · os

【操作系统百科】消息队列:SysV、POSIX mq、kdbus 夭折

消息队列承诺「有边界的带优先级的 IPC」,但 Linux 上两代 API(SysV msg、POSIX mq)都很冷清。内核内的 kdbus 曾想成为下一代系统总线,最终被拒。本文讲三者的设计、使用限制、以及为什么现代系统几乎都改走 Unix socket + 序列化库 + userspace broker。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。


By .