:共享内存与信号量`)
POSIX 共享内存 信号量 —— 跨进程通信的标配多个进程要共享 GB 级数据怎么办socket 拷贝到飞起pipe 流式不方便。POSIX 共享内存 信号量是工业标配shm_open拿一个名字 →mmap进各自地址空间 →sem_open同步访问。这篇把这套组合拳讲清楚。0. 引言跨进程读 1GB 配置进程 A 算好 1GB 数据进程 B 要消费。怎么传方案缺点socket / pipe数据拷贝两次1GB 几十 ms临时文件磁盘 IO 慢shm_open mmap零拷贝纳秒级访问后者是答案。但共享内存只解决共享不解决同步。两个进程同时改一个字段会冲突需要信号量配合。1. POSIX 共享内存shm_open mmap1.1 三步走#includesys/mman.h#includesys/stat.h#includefcntl.h/* ① 创建/打开命名共享内存 */intfdshm_open(/myshm,O_CREAT|O_RDWR,0666);/* ② 设置大小首次创建必须 */ftruncate(fd,4096);/* ③ 映射进地址空间 */char*pmmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);close(fd);/* fd 可关映射继续有效 *//* 用 p[] 跟普通内存一样 */strcpy(p,hello shm);munmap(p,4096);shm_unlink(/myshm);/* 删除命名共享内存 */1.2 编译时记得链 -lrtgcc shm.c-lrt1.3 名字规则规则说明必须以/开头/myshm✓myshm✗实际路径/dev/shm/myshm文件tmpfs 上纯内存不持久化系统重启后丢失1.4 用 ls 看共享内存$ls-l/dev/shm/ -rw-rw-r--1user user4096Jun1116:00 myshm1.5 跨进程使用/* 进程 A写 */intfdshm_open(/myshm,O_CREAT|O_RDWR,0666);ftruncate(fd,4096);char*pmmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);strcpy(p,data from A);/* 进程 B读A 必须先创建 */intfdshm_open(/myshm,O_RDWR,0);char*pmmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);printf(%s\n,p);/* data from A */只要两边都 mmap 同名共享内存指向的是同一物理页一边改另一边立即可见。2. POSIX 命名信号量sem_open#includesemaphore.hsem_t*sem_open(constchar*name,intoflag,mode_tmode,unsignedintvalue);intsem_wait(sem_t*sem);/* P 操作减 10 时阻塞 */intsem_trywait(sem_t*sem);/* 非阻塞版本 */intsem_post(sem_t*sem);/* V 操作加 1 */intsem_getvalue(sem_t*sem,int*sval);intsem_close(sem_t*sem);intsem_unlink(constchar*name);2.1 用法骨架sem_t*semsem_open(/mysem,O_CREAT,0666,1);/* 初值 1 */sem_wait(sem);/* 临界区 */sem_post(sem);sem_close(sem);sem_unlink(/mysem);跟线程内的 mutex / sem 行为一样但进程间共享通过名字。3. 完整例子生产者-消费者/* shm_layout.h */#includesemaphore.h#defineSHM_NAME/pc_shm#defineSLOTS16structringbuf{sem_tempty;/* 空槽数 */sem_tfull;/* 满槽数 */sem_tmutex;/* 互斥访问 */intin;intout;intdata[SLOTS];};/* producer.c */#includeshm_layout.h#includefcntl.h#includesys/mman.h#includeunistd.hintmain(void){intfdshm_open(SHM_NAME,O_CREAT|O_RDWR,0666);ftruncate(fd,sizeof(structringbuf));structringbuf*rmmap(NULL,sizeof*r,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);/* 首次创建初始化要做幂等保证简化版直接初始化 */sem_init(r-empty,1,SLOTS);/* 1 跨进程 */sem_init(r-full,1,0);sem_init(r-mutex,1,1);r-inr-out0;for(inti0;i100;i){sem_wait(r-empty);/* 等空槽 */sem_wait(r-mutex);r-data[r-in]i;r-in(r-in1)%SLOTS;sem_post(r-mutex);sem_post(r-full);/* 通知有数据 */printf(produce %d\n,i);}munmap(r,sizeof*r);close(fd);return0;}/* consumer.c */#includeshm_layout.h#includefcntl.h#includesys/mman.h#includeunistd.hintmain(void){intfdshm_open(SHM_NAME,O_RDWR,0);structringbuf*rmmap(NULL,sizeof*r,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);for(inti0;i100;i){sem_wait(r-full);sem_wait(r-mutex);intvr-data[r-out];r-out(r-out1)%SLOTS;sem_post(r-mutex);sem_post(r-empty);printf(consume %d\n,v);}munmap(r,sizeof*r);close(fd);shm_unlink(SHM_NAME);return0;}注意sem_init(r-empty, 1, SLOTS)第二个参数pshared1表示跨进程共享。这种方式叫未命名信号量放在共享内存里比 sem_open 的命名信号量更紧凑。4. 进程间 mutexPTHREAD_PROCESS_SHARED不止信号量pthread_mutex / pthread_cond 也能跨进程。前提放在共享内存里 设PTHREAD_PROCESS_SHARED属性structshared{pthread_mutex_tmutex;pthread_cond_tcond;intready;intdata;};/* 初始化在创建进程里做一次 */pthread_mutexattr_tmattr;pthread_mutexattr_init(mattr);pthread_mutexattr_setpshared(mattr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(s-mutex,mattr);pthread_condattr_tcattr;pthread_condattr_init(cattr);pthread_condattr_setpshared(cattr,PTHREAD_PROCESS_SHARED);pthread_cond_init(s-cond,cattr);/* 之后两个进程像在同一进程内一样用 */pthread_mutex_lock(s-mutex);while(!s-ready)pthread_cond_wait(s-cond,s-mutex);intvs-data;pthread_mutex_unlock(s-mutex);4.1 robust mutex —— 防进程崩溃锁泄漏普通 mutex 持有者崩溃锁就永远不会释放。robust mutex解决这个pthread_mutexattr_setrobust(mattr,PTHREAD_MUTEX_ROBUST);if(pthread_mutex_lock(m)EOWNERDEAD){/* 上一个持有者崩溃了需要恢复状态 */fix_inconsistent_state();pthread_mutex_consistent(m);}跨进程数据同步必备。5. POSIX vs System V IPCLinux 上有两套 IPC维度POSIX (推荐)System V共享内存shm_open mmapshmget shmat信号量sem_open / sem_initsemget消息队列mq_openmsgget标识字符串名字32 位 key (ftok)看到ls /dev/shm/ipcs销毁shm_unlinkipcrmAPI 风格现代 fd 风格70 年代风格跨平台各 POSIX 系统通用Unix-only新代码用 POSIXSystem V 只在维护老代码时碰。6. 共享内存的同步策略对比方案特点用途命名信号量(sem_open)跟 file 一样持久化简单互斥、计数未命名信号量(sem_init in shm)紧凑、嵌入数据结构生产消费队列PSHARED mutex/cond完整 pthread 语义复杂同步robust mutex容错进程崩溃高可靠场景原子变量(stdatomic.h)无锁、最快计数器、标志位memory barrier极致性能自定义无锁结构经验法则场景推荐方案简单的互斥访问数据POSIX 信号量 或 PSHARED mutex生产-消费队列多个信号量empty/full/mutex高可靠防进程崩溃robust mutex共享计数器atomic_int7. 几个真实坑7.1 ftruncate 没做 → SIGBUSshm_open 创建的对象初始大小是 0没 ftruncate 直接 mmap 然后访问 → SIGBUS。intfdshm_open(NAME,O_CREAT|O_RDWR,0666);ftruncate(fd,sizeof(structringbuf));/* ← 必须 */7.2 多进程都创建 → 谁初始化经典问题A 和 B 都启动 shm_open(O_CREAT)谁来 sem_init两次 init 会破坏状态。办法A 先启动跑初始化脚本B 后启动只 attach用 O_EXCL 抢占创建intfdshm_open(NAME,O_CREAT|O_EXCL|O_RDWR,0666);if(fd0){/* 我是创建者初始化 */ftruncate(fd,SIZE);/* sem_init ... */}elseif(errnoEEXIST){/* 已存在attach */fdshm_open(NAME,O_RDWR,0);/* 等待对方初始化完成用一个 ready 标志 */}7.3 进程崩溃 → 共享内存不会自动释放/dev/shm/下的对象一直在下次启动可能拿到脏数据。第一启动的进程显式 unlink 老数据shm_unlink(NAME);/* 不存在也无所谓 */intfdshm_open(NAME,O_CREAT|O_EXCL|O_RDWR,0666);7.4 32 位指针不能跨进程传structshared{char*next;/* ⛔ 这个指针在进程 A 是地址 0x7fff..., 进程 B 完全不一样 */};共享内存地址在两个进程不一定相同取决于 mmap 返回值。要么用偏移structshared{size_tnext_offset;/* 相对 base 的偏移 */};void*basemmap(...);structnode*n(void*)((char*)bases-next_offset);要么用 MAP_FIXED 强制相同地址很危险。7.5 size 太大失败/dev/shm/默认大小有限通常 RAM 的一半。映射超大 shm 失败。改 mount 选项sudomount-oremount,size8G /dev/shm8. 总结API 速查操作API创建/打开shm_open(name, O_CREAT设大小ftruncate(fd, size)映射mmap(NULL, size, PROT_RW, MAP_SHARED, fd, 0)解除映射munmap(p, size)删除对象shm_unlink(name)命名信号量sem_open(name, O_CREAT, 0666, value)嵌入信号量sem_init(sem, 1, value)pshared1pshared mutexpthread_mutexattr_setpshared(attr, PTHREAD_PROCESS_SHARED)共享内存的精髓一段物理页让多个进程的虚拟地址空间映射进去从而共享。shm_open mmap拿到这段sem/pshared mutex同步访问。这是高性能跨进程通信的标准答案。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。