十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux --- 共享内存

Linux --- 共享内存 在 Linux 进程间通信IPC的所有机制中共享内存是理论带宽最高、延迟最低的方案却也是最容易因为同步设计、生命周期管理、崩溃一致性考虑不周而埋下隐患的机制。一、本质共享内存共享的是同一组物理内存页共享内存是把同一个内存对象映射到 A、B 两个进程各自的虚拟地址空间。两个进程看到的虚拟地址通常不同但最终指向同一组物理页进程 A 虚拟地址空间 进程 B 虚拟地址空间 0x7f10_0000 0x7f80_0000 │ │ │ A 的页表 │ B 的页表 └──────────────┐ ┌────────────────┘ ▼ ▼ 同一物理页进程天然拥有相互隔离的虚拟地址空间。普通情况下即使两个进程的虚拟地址数值完全相同也对应不同的页表、不同的物理内存一方无法直接访问另一方的指针。传统管道、Socket 的数据路径至少包含两次拷贝用户态 A → 内核 IPC 缓冲区 → 用户态 B。而共享内存跳过了内核中转建立映射后业务数据直接在同一块物理内存上读写这是其性能优势的根本来源。但必须明确共享内存只提供共同可访问的数据区域不自动提供消息边界、写入完成通知、读写互斥、生产者消费者速度协调、进程崩溃恢复、数据版本兼容、访问权限校验等能力。二、内核视角共享内存工作原理2.1 虚拟地址、页表与物理页进程访问共享内存时使用的仍然是普通虚拟地址。CPU 执行读写操作的完整链路为虚拟地址 → TLB 查询 → 页表查询 → 物理页地址 → CPU Cache / 主存。A 和 B 两个进程的页表可以分别建立不同虚拟页到同一物理页的映射A 页表项虚拟页VA_A→ 物理页 PB 页表项虚拟页VA_B→ 物理页 P两个进程的虚拟地址可以不同页表项分属不同进程但最终映射目标完全一致。这也是共享内存结构中不能保存普通指针的根本原因——指针是虚拟地址在另一个进程的地址空间中没有意义。2.2mmap()只建立映射关系不分配物理页调用mmap()时内核主要完成四件事在当前进程中选择一段空闲虚拟地址区域创建对应的 VMA虚拟内存区域结构体将该 VMA 与共享内存对象关联设置读写与共享属性返回虚拟地址起始指针。mmap()不会立刻为整个区域分配并填充所有物理页。绝大多数页面会在首次实际访问时通过缺页异常处理建立页表映射。这意味着第一次访问会有额外延迟同时 NUMA 节点上页面的实际归属由触发缺页的 CPU 节点决定。2.3 缺页异常真正建立页表的时刻假设进程 A 第一次向共享内存写入数据实际会发生CPU 访问虚拟地址发现对应页表项不存在触发缺页异常page fault陷入内核内核确认地址属于合法 VMA找到或分配共享对象对应的物理页为 A 进程建立页表项返回用户态重新执行写入指令。当进程 B 首次访问同一位置时也会触发一次缺页内核不会重新分配物理页而是直接让 B 的页表项指向已存在的物理页。这是建立映射不是把 A 的页面复制给 B。2.4 缓存一致性 ≠ 程序同步正确A 和 B 运行在不同 CPU 核心时数据会经过 CPU Cache。现代 CPU 的缓存一致性协议如 MESI会保证同一缓存行在多个核心之间的最终一致性但这完全不等于程序层面的同步正确。缓存一致性不能自动保证B 一定在 A 写完之后才读取结构体多个字段被 B 看成一个完整事务编译器不重排普通变量的访问顺序CPU 不重排内存操作指令B 不会读到一半新数据、一半旧数据。共享内存的并发访问仍然必须使用锁、原子操作与正确的内存序来保证正确性。三、Linux 上的 5 种共享内存方案3.1 POSIX 共享内存shm_open这是新项目中最常用、接口相对清晰的命名共享内存机制核心接口为shm_open → ftruncate → mmap → munmap → shm_unlinkPOSIX 共享内存对象在 Linux 上由挂载在/dev/shm的 tmpfs 支持对象有全局名称无关进程可以通过名称打开。新建对象初始长度为 0必须通过ftruncate()设置大小后才能正常访问。ftruncate()是 POSIX 系统调用Linux/Unix截断 / 扩展文件到指定长度。3.2 System V 共享内存shmget属于经典的 System V IPC 体系核心接口为shmget → shmat → shmdt → shmctl它使用整数key和内核返回的shmid标识共享段常见于数据库、中间件、遗留工业软件与历史项目。一个容易误解的点shmget(IPC_PRIVATE, ...)中的IPC_PRIVATE不代表“只有当前进程能访问”而是要求创建一个全新的、唯一的共享段其他进程拿到shmid后仍然可以附加到该段上。3.3 普通文件 mmap(MAP_SHARED)也可以直接映射磁盘上的普通文件intfdopen(/data/shared.bin,O_RDWR|O_CREAT,0600);ftruncate(fd,size);void*pmmap(nullptr,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);这种方案适合数据需要落盘、重启后需要恢复、多进程共同修改磁盘文件的场景例如数据库索引、持久化状态机。MAP_SHARED的修改对所有映射同一区域的进程可见对于普通文件修改最终也会回写到磁盘。msync()用于控制修改何时同步到底层文件不能替代进程间的同步原语。3.4 匿名共享映射具有父子关系的进程可以使用匿名共享映射无需名称和文件void*pmmap(nullptr,size,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);pid_t pidfork();fork()后父子进程继承该映射共同访问同一块内存。它适合父进程先分配、再派生子进程的场景无关进程难以发现和打开该区域。注意必须使用MAP_SHARED | MAP_ANONYMOUS如果写成MAP_PRIVATE | MAP_ANONYMOUS写入时会触发写时复制父子进程各自看到私有副本失去共享效果。3.5memfd_create更安全的匿名内存文件memfd_create()创建一个以内存为 backing 的匿名文件对象没有全局路径名通常通过 Unix 域 Socket 将文件描述符传递给其他进程对方再执行mmap()。intfdmemfd_create(frame_pool,MFD_CLOEXEC|MFD_ALLOW_SEALING);ftruncate(fd,size);void*pmmap(nullptr,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);它尤其适合不希望使用全局名称、需要精确控制访问权限的场景。其 sealing 机制可以限制后续写入、扩容、缩容等操作减少不可信对端造成 TOCTOU 或SIGBUS的风险广泛用于多媒体、浏览器、Wayland 等缓冲区共享场景。方案对比总览方案命名方式无关进程访问难度持久化能力典型适用场景POSIXshm_open全局名称容易内核持久直到显式删除通用本机 IPCSystem Vshmgetkey/shmid容易内核持久需显式删除遗留系统、数据库中间件普通文件mmap文件路径容易磁盘持久化数据库索引、状态持久化匿名MAP_SHARED无仅靠 fork 继承无父子进程间共享memfd_create无全局路径通过 FD 授权无安全共享、FD 传递场景dma-bufFD通过 FD 授权无CPU/GPU/相机设备间缓冲共享四、POSIX 共享内存生命周期4.1 创建与打开原子判定创建者intfdshm_open(/robot_state_v1,O_CREAT|O_EXCL|O_RDWR,0600);POSIX 共享内存名称的可移植格式为/名称名称中不要再包含额外的/。O_CREAT | O_EXCL可以原子地完成“对象不存在则创建存在则失败”的逻辑非常适合判定谁是初始化者创建成功的一方负责初始化互斥锁、条件变量与数据头部打开失败errno EEXIST的一方直接使用已有对象。4.2 权限控制别再默认 0666shm_open的 mode 参数会受进程umask影响最终权限为请求权限 ~umask。生产环境不建议使用0666更合理的选择是0600仅当前用户可读写0660当前用户和指定组可读写还可以通过fchmod()、fchown()精细管理权限与所有者。4.3 设置大小ftruncate与SIGBUS新创建的 POSIX 共享内存对象长度为 0必须调用ftruncate()设置大小ftruncate(fd,sizeof(SharedBlock));如果忘记设置大小就映射并访问会在访问时收到SIGBUS信号。它与空指针的SIGSEGV不同SIGSEGV虚拟地址不合法或权限错误SIGBUS地址在映射区内但底层对象没有对应有效存储4.4 建立映射MAP_SHARED与MAP_PRIVATE的天壤之别void*rawmmap(nullptr,sizeof(SharedBlock),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);两个关键 flag 必须区分清楚MAP_SHARED修改对所有映射同一区域的进程可见是共享 IPC 的必选项MAP_PRIVATE写入时触发私有写时复制修改不会发布给其他进程如果错用了MAP_PRIVATE会出现“我明明写了对方看不到”的经典问题。4.5 映射后可以关闭文件描述符成功mmap()之后调用close(fd)不会让映射失效。文件描述符和内存映射是内核中两层独立的引用关系映射建立后即可关闭 FD避免文件描述符泄漏。4.6 解除映射与删除名称munmap()只移除当前进程中的映射不会删除对象名称也不影响其他进程的已有映射。shm_unlink()语义类似文件系统的unlink删除全局名称后续新的shm_open无法打开旧对象已经打开、已经映射的进程可以继续使用当最后一个引用和映射消失后内核才回收内存。POSIX 共享内存具有内核持久性如果不调用shm_unlink()对象会一直存在到系统关机。进程异常退出后经常会在/dev/shm下看到遗留对象就是这个原因。五、共享内存到底是不是“零拷贝”“零拷贝”必须分层讨论不能一概而论。5.1 理想情况零次 IPC 拷贝如果生产者直接在共享内存上生成数据消费者直接在共享内存上处理那么进程间的有效载荷拷贝次数为 0。共享内存消除了传统 IPC 中“用户态 A → 内核缓冲区 → 用户态 B”的两次拷贝。5.2 实际工程业务侧的memcpy无法避免如果程序本身执行了std::memcpy(shared-data,local_buffer,size);那么仍然存在一次显式拷贝。如果消费者再复制到自己的私有缓冲区就是两次。共享内存消除的是 IPC 路径上的内核中转拷贝无法消除程序主动执行的业务层拷贝。能不能做到真正零拷贝取决于数据生产者能否直接输出到共享内存。5.3 硬件层面的缓存行迁移不是软件拷贝不同核心之间通过缓存一致性协议迁移缓存行会消耗带宽和延迟但这不属于 IPC 语境下的缓冲区拷贝。不过在高频访问场景下缓存行乒乓反而可能成为共享内存的主要性能瓶颈。5.4msync不是“让对方看到数据”的开关对于MAP_SHARED映射一方的修改对另一方天然可见不需要调用msync()。msync()的真正作用是控制文件-backed 映射何时把修改同步到底层存储它不负责加锁、发布完成标志、建立消息边界也不能替代内存屏障与同步原语。六、同步机制共享内存最核心的部分没有同步的共享内存就是数据竞争的温床。6.1 为什么裸变量绝对不行structShared{boolready;chardata[4096];};// A: 先写数据再置 ready// B: 等 ready 为 true 再读数据这是典型的错误设计存在并发数据竞争编译器和 CPU 都可能重排访问顺序B 完全可能看到ready true但data只写了一半。所以必须使用明确的同步机制。C/C 普通内存访问没有 “顺序契约”。编译器、CPU 只保证单线程内可观测结果不变不保证内存操作按源码顺序提交到共享内存。如果A 后续代码没有再读 data、ready那么在A进程看来先写数据还是先置ready为true是一样的。编译器不知道另一个线程 B 会观察这两块内存。C 语言普通变量没有告诉编译器 “这内存会被别的线程偷看”优化器只管当前线程语义。6.2 进程共享互斥锁互斥锁本身必须放在共享内存中并设置进程共享属性pthread_mutexattr_t attr;pthread_mutexattr_init(attr);pthread_mutexattr_setpshared(attr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(shared-mutex,attr);PTHREAD_PROCESS_SHARED表示任何能访问该内存的进程都可以操作这把锁。互斥锁同时解决了互斥、临界区完整性、内存可见性与顺序三个问题。6.3 条件变量等待状态变化互斥锁负责保护状态条件变量负责等待状态变化二者搭配是最经典的生产者消费者模型pthread_condattr_t attr;pthread_condattr_init(attr);pthread_condattr_setpshared(attr,PTHREAD_PROCESS_SHARED);pthread_cond_init(shared-cond,attr);使用时必须遵守while (!condition)范式因为条件变量存在虚假唤醒多消费者也会竞争同一状态。6.4 POSIX 无名信号量信号量可以直接放入共享内存适合计数型资源sem_init(shared-items,1,0);// pshared1 表示跨进程共享sem_init(shared-spaces,1,1);它非常适合表示队列中可读元素数、空闲槽位数、可用缓冲区数等计数场景。6.5futex高性能同步的底层基石futex 是 Linux 上互斥锁、信号量等同步原语的底层实现核心思想是无竞争时用户态原子操作完成加解锁不进入内核有竞争时futex(FUTEX_WAIT)进入内核休眠futex(FUTEX_WAKE)唤醒等待者futex word 必须位于共享内存中内核保证“比较并休眠”的原子性避免丢失唤醒。普通业务不建议直接手写 futex 锁需要处理的边界条件极多原子指令、内存序、超时、信号中断、进程死亡、优先级继承、ABA 问题等优先使用成熟的pthread原语。6.6 robust mutex处理持锁进程死亡普通互斥锁有一个致命问题如果持锁进程在临界区内崩溃锁会永远处于锁定状态其他进程永久阻塞。解决方案是设置健壮互斥锁pthread_mutexattr_setrobust(attr,PTHREAD_MUTEX_ROBUST);此时另一方加锁会得到EOWNERDEADOwner died锁的持有者死亡 返回值表示本方已经获得锁上一个所有者异常死亡共享数据可能不一致需要修复修复完成后调用pthread_mutex_consistent()。注意robust mutex 只负责发现“锁持有者死了”不会自动修复业务数据。数据一致性仍然需要应用层定义回滚与校验策略。6.7eventfd事件通知的最佳搭档共享内存存数据eventfd传通知是工业界非常常见的组合生产者写完数据后向 eventfd 写入一个计数表示有新数据消费者把 eventfd 加入poll/epoll事件循环等待通知。这种模式避免了忙轮询又能接入统一事件循环非常适合大帧数据、低频率通知的场景。6.8 原子变量与内存序无锁设计常使用std::atomic配合 release/acquire 内存序生产者release存储发布标志保证之前的数据写入都已完成消费者acquire加载标志保证之后读取的数据都是最新的。但需要注意工程边界ISO C 标准对跨进程共享原子的可移植语义定义不如 POSIX 进程共享原语明确。Linux 主流平台上真正 lock-free 的整数原子可用于共享映射但需要确认is_always_lock_free、对齐、ABI 一致性。高可移植性要求下仍然优先使用 POSIX 同步原语。七、共享内存数据结构设计7.1 绝对不能存普通指针A 进程里的指针值放到 B 进程的地址空间中毫无意义轻则读到垃圾重则直接崩溃。正确做法是保存相对于共享内存基址的偏移量structShared{uint64_tdata_offset;};// 使用时base offset这样不要求两个进程的映射基地址相同。7.2 不能直接放 STL 容器std::string、std::vector、std::map、std::shared_ptr等标准容器都不能直接放进共享内存交给另一进程使用。它们内部包含指向进程私有堆的指针、分配器状态、运行库内部对象跨进程后全部失效。可行方案固定长度数组偏移指针 自定义分配器Boost.Interprocess 等专门的共享内存容器FlatBuffers、Cap’n Proto 等基于偏移的序列化格式。7.3 用固定宽度类型拒绝long/size_t跨 32/64 位进程、不同编译选项下long、size_t、enum的大小和对齐可能不同。推荐统一使用uint32_t、uint64_t、int32_t等固定宽度类型。7.4 魔数 版本号防御旧对象与不兼容共享头部必须包含魔数和版本号constexpruint32_tkMagic0x53484D31;// SHM1structSharedHeader{uint32_tmagic;uint16_tmajor_version;uint16_tminor_version;uint32_ttotal_size;};消费者映射后首先校验魔数和版本避免旧进程误读新布局上一次崩溃留下的旧对象映射错了共享对象对象大小不匹配。7.5 显式记录总大小与校验头部中保存映射总大小并与fstat()返回的实际大小做对比。不要只相信对象名称正确。7.6 对齐与缓存行隔离高频读写的原子变量、生产者/消费者索引应当按缓存行对齐并隔离避免伪共享。x86 平台通常以 64 字节为缓存行基准。八、工程中常用的 4 种通信模型8.1 单缓冲最简单的模型一块共享区加一把锁生产者写、消费者读互斥访问。适合低频命令、状态数据优点是简单易验证缺点是读写相互阻塞吞吐量有限。8.2 双缓冲两块缓冲区交替使用一块消费者读取一块生产者写入完成后原子交换索引。适合图像、点云、状态快照等“最新值优先”的实时数据读写可以在不同缓冲区上并行。8.3 环形缓冲区固定数量槽位组成环形生产者不断写入、消费者依次读取是高频流式数据的首选模型例如相机帧、雷达点云、关节状态、音视频、高频日志。工业级环形队列通常每个槽位带 sequence 号通过原子发布协议实现无锁并发。8.4 描述符 数据池对于大图像、大帧数据通常将控制区与数据区分开控制区小频繁同步存放槽位索引、时间戳、大小、状态数据区大减少元数据修改存放实际帧数据。这种模式减少了锁持有大数据区的时间也更容易实现缓冲池所有权转移。九、启动初始化90% 的竞态都发生在这里9.1 谁来初始化互斥锁、条件变量、信号量、数据头部都只能初始化一次不能让两个进程同时执行初始化。标准做法是用O_CREAT | O_EXCL原子判定创建者创建成功的一方负责完整初始化另一方直接打开使用。9.2 创建成功 ≠ 初始化完成可能出现 A 创建成功但还没初始化完 mutexB 已经打开并开始加锁的情况。因此必须设计初始化状态机Empty → Initializing → Ready或者由 supervisor 严格控制启动顺序初始化完成后再通知消费者接入。9.3 遗留旧对象崩溃重启的隐形陷阱程序异常退出会留下共享内存对象。新启动时如果直接O_CREAT打开可能拿到包含旧数据、损坏锁、错误版本的旧对象。启动时必须校验魔数、版本、大小、初始化状态必要时由管理进程删除并重建。十、崩溃一致性写到一半进程死了的处理方式共享内存比 Socket 更容易受到“写到一半进程死亡”的影响因为 Socket 至少有连接断开的信号而共享内存只会留下半截数据。常见的防护手段先写备用槽原子切换指针发布前数据始终写在非活跃槽写完校验后再原子更新活跃索引旧槽位在发布前始终有效。版本号 校验和每个槽位附带 generation、size、checksum消费者只接受校验通过、状态完整的数据。状态机与所有权标记每个槽位有明确的状态FREE / WRITING / READY / READING异常恢复时可以检测到死在中间状态的槽位并回收。注意只记录所有者 PID 并不足够可靠因为 PID 会复用。严格方案需要结合进程启动时间、pidfd、心跳、supervisor 记录共同判定。十一、性能优化11.1 减少不必要的私有缓冲区尽量让生产者直接在共享内存上生成数据避免“SDK 缓冲区 → 私有工作缓冲区 → 共享内存”的多次拷贝。能原地处理就原地处理能零拷贝就零拷贝。11.2 消灭伪共享False Sharing如果写索引和读索引在同一个缓存行即使读写的是不同变量也会导致缓存行在核心之间来回乒乓严重影响性能。解决方法是将高频写入的变量按缓存行对齐并填充隔离确保不同核心的热点变量不在同一缓存行。11.3 避免全局原子的缓存行乒乓真共享的热点原子变量同样会造成缓存行迁移。优化方向遵循单写者原则每个生产者维护独立计数器定期合并减少全局原子变量数量读多写少数据与写热点分开存放。11.4 NUMA 亲和性多路服务器中访问本地 NUMA 节点比远端节点延迟更低、带宽更高。共享内存页面通常在首次缺页时分配在当前 CPU 所在节点。如果生产者在 Node 0 触页、消费者长期跑在 Node 1会产生大量远端访问。可以通过numactl、set_mempolicy()、mbind()调整内存策略将生产者和消费者绑定到同一 NUMA 节点或对大缓冲池采用交错分配。11.5 大页普通 4KB 页面在大内存区域下会产生大量页表项与 TLB miss。使用 2MB 甚至 1GB 大页可以显著降低地址翻译开销。Linux 支持透明大页THP与显式 HugeTLB 两种路径。tmpfs/shmem 可以使用透明大页显式大页则需要预留与专门映射。11.6 预缺页与mlock实时系统不希望运行中突发大量缺页。可以在初始化阶段主动触碰每一页或使用MAP_POPULATE、madvise(MADV_WILLNEED)预分配。对延迟要求极高的场景可以使用mlock()将共享区域锁定在物理内存防止被回收或换出。需要注意RLIMIT_MEMLOCK限制与 OOM 风险。11.7 澄清tmpfs 不等于永不换出/dev/shm基于 tmpfs但 tmpfs 属于虚拟内存体系在内存压力下仍然可以被换出到 swap。它不是永久锁定在物理 RAM 中。要求低延迟和可预测性时必须结合mlock、swap 配置、cgroup 限制综合设计。十二、安全性12.1 权限收紧拒绝0666共享内存使用文件式权限模型敏感数据绝不能设置全局可读写。应遵循最小权限原则仅授权给必要的用户和组。12.2 名称抢占攻击攻击者可以提前创建同名共享内存对象服务启动时如果只用O_CREAT而不用O_EXCL就可能打开攻击者控制的对象造成数据篡改与安全风险。必须使用O_CREAT | O_EXCL并校验所有者、权限、魔数、版本。更高安全要求的场景推荐使用memfd_create FD 传递彻底避免全局名称暴露。12.3 不可信对端永远校验所有字段对端可能在你校验后修改数据、篡改长度、截短底层对象、构造越界索引。即使使用了 sealing也必须校验长度不超过容量偏移 大小不溢出槽位索引不越界版本与魔数合法校验和匹配。12.4 容器与命名空间的隔离边界System V IPC 受 IPC namespace 隔离POSIX 共享内存通过/dev/shm实现可见性由 mount namespace 决定。容器部署时要注意/dev/shm的默认容量可能很小大数据场景必须显式调大。十三、高频踩坑清单错用MAP_PRIVATE写入对方看不到写时复制各自私有副本。忘记ftruncate对象长度为 0访问触发SIGBUS。共享结构里放指针另一进程地址空间无效必崩。靠volatile做同步volatile不提供原子性、互斥和内存序完全不够。用sleep猜写入时机负载一变就失效不是同步协议。死循环忙轮询占满 CPU、加剧缓存竞争正确做法是条件变量或 eventfd。运行时ftruncate缩容其他进程仍映射旧区域访问后半段触发SIGBUS。只munmap不shm_unlink对象遗留在系统中占用内存。持锁执行重计算锁内做长耗时运算让对方长时间阻塞。正确做法是锁内只交换所有权锁外处理数据。十四、调试与观测工具14.1 对象与进程映射观测# 查看 POSIX 共享内存对象ls-lah/dev/shmdf-h/dev/shm# 查看 System V 共享内存ipcs-m# 查看进程映射详情cat/proc/pid/mapscat/proc/pid/smaps14.2 系统调用跟踪strace-f-etraceopenat,mmap,munmap,ftruncate,futex,unlink ./program14.3 性能分析perf stat / perf record分析缺页、缓存未命中、上下文切换perf c2c定位伪共享与缓存行竞争numastat观察 NUMA 远端访问情况Intel VTune深度伪共享与内存带宽分析十五、工程选型速查表场景推荐方案两个无关进程共享少量状态POSIX shm process-shared mutex图像帧最新值快照双缓冲 原子索引 eventfd连续高频传感器帧SPSC 环形缓冲区多消费者图像处理共享缓冲池 描述符 引用状态父子进程间共享MAP_SHARED不希望使用全局名称memfd_create Unix Socket 传 FD需要落盘与重启恢复普通文件mmap(MAP_SHARED)GPU/相机/显示设备共享dma-buf 或设备 SDK 缓冲区机制遗留数据库/中间件兼容System V shm强实时低抖动场景预分配 预缺页 mlock 固定容量总结共享内存的本质是让多个进程的虚拟地址映射到同一块物理内存。它只解决了“数据不需要在进程间拷贝”这一个问题而同步、通知、生命周期、崩溃恢复、版本兼容、性能优化、安全防护这些真正决定系统可靠性与上限的部分都需要开发者自己设计与实现。写好一套可靠的共享内存系统难点从来不是调用mmap()而是设计一套健壮的并发协议与故障恢复机制。理解内核底层行为再结合业务场景选择合适的方案与同步模型才能既发挥共享内存的性能优势又保证系统长期稳定运行。可以把共享内存拆成五层知识框架存储对象层POSIX shm / System V shm / 文件 / memfd / dma-buf虚拟内存层mmap / VMA / 页表 / 缺页异常硬件访问层Cache / TLB / 缓存一致性 / NUMA并发协议层mutex / cond / semaphore / futex / atomic / eventfd业务协议层消息格式 / 环形队列 / 所有权 / 版本 / 崩溃恢复
返回列表