不能直接用 shmget + shmat 封装 composer 包,因 system v 共享内存生命周期由内核管理,ipc_rmid 调用后所有后续 shmat 失败,而 php 进程无统一生命周期管理,worker 崩溃时 register_shutdown_function 不触发,导致其他 worker 在 shmat 时返回 (void*)-1 并段错误。

为什么不能直接用 shmget + shmat 封装 Composer 包
因为 System V 共享内存的生命周期绑定内核,shmctl(..., IPC_RMID, ...) 一旦调用,所有进程后续 shmat 都会失败——而 Composer 包无法可靠控制谁“最后删段”。PHP 进程无父子继承关系,也没有统一的初始化/退出钩子,register_shutdown_function 在 worker crash 时根本不会触发。你封装得再漂亮,只要一个进程提前 shmctl,其他所有 worker 立刻卡死在 shmat 返回 (void*)-1,日志里只有一行段错误。
hyperf/process 是目前最可行的落地基座
它天然满足共享内存 IPC 的三个硬性前提:固定进程角色(主进程创建、worker 进程只读写)、统一生命周期(主进程退出时可集中清理)、协程安全(避免 sem_wait 阻塞整个进程)。实际封装时应:
- 主进程启动时用
shmget创建段,shmat映射后存入ApplicationContext,并用sem_init初始化配套信号量 - worker 进程通过
ApplicationContext获取已映射地址和信号量指针,**禁止自行调用shmget或shmat** - 所有读写操作必须包裹
sem_wait/sem_post,且信号量需设为进程间共享(SEM_PROCESS_SHARED) - 主进程
onShutdown回调中顺序调用shmdt→sem_destroy→shmctl(..., IPC_RMID, ...)
PHP 里别碰 mmap + shm_open 组合
POSIX 共享内存对象(/dev/shm/mykey)在 PHP 中无法跨进程持久持有文件描述符:shm_open 返回的 fd 在 fork 后虽可继承,但 worker 进程若执行 exec(如调用外部命令)或被 Swoole reload 机制重建,fd 必然丢失;而 mmap 地址空间映射不随 fd 生存,fd 关闭后映射仍有效——但这恰恰是陷阱:你无法判断其他进程是否已 munmap 或崩溃,更无法感知对象是否被 shm_unlink。实测中,PHP-FPM 下 shm_unlink 后,未 munmap 的进程仍能读写,但下次重启就 shm_open 失败,且 ipcs -m 完全看不到残留项,调试成本极高。
真正需要封装的是「协议层」而非「系统调用层」
用户要的不是 shmget 的 PHP wrapper,而是「怎么安全塞结构化数据进去、怎么通知对方来取、怎么防止覆盖、怎么检测脏读」。所以 Composer 库该聚焦:
- 内存布局定义:用
pack/unpack固定字段偏移,预留版本号字段,避免 C++ 服务端升级后 PHP 客户端解析错位 - 环形缓冲区封装:生产者写入前检查
write_pos + len ,消费者读取后原子更新 <code>read_pos,两者都用memory_order_acquire/release级别的屏障(PHP 扩展需用__atomic或pthread_mutex模拟) - 心跳与存活检测:在共享内存头部放
last_write_ts字段,worker 每秒更新,主进程定期扫描超时项并标记失效
这些逻辑一旦写错,比系统调用失败更难排查——比如没对齐结构体导致字段跨 cache line,多核下 write_pos 更新不被其他 CPU 立即看到,就会出现数据丢失。这才是封装时最该抠的细节。











