Linux C下共享内存双向多轮消息传输的正确处理方法
Linux C 共享内存多轮消息传输问题解答
核心结论
接收进程完全不需要detach后重新attach共享内存就能读到更新后的内容,你当前读不到新消息纯粹是代码写错了。
问题根因
你代码里的shmaddr = msg;操作根本没有修改共享内存的内容:
shmat返回的shmaddr是进程A地址空间里的一个指针变量,初始值指向共享内存段映射到进程A的起始地址- 你给
shmaddr重新赋值为msg,只是修改了这个指针变量本身的值,让它指向了进程A栈上的本地数组msg,和共享内存段没有任何关系了 - 这种情况下共享内存段里存储的还是你第一次
strcpy(shmaddr, argv[1])写入的旧内容,进程B当然读不到新字符串。
正确写入新消息的方式
和你第一次写共享内存的逻辑完全一致:只要保持shmaddr指向shmat返回的共享内存映射地址,直接往这个地址拷贝新内容即可,示例:
char * const shmaddr = shmat(shmid, NULL, 0); // 用const修饰指针本身,避免误改指针指向 if (shmaddr == (void*)-1) { perror("shmat failed"); exit(1); } // 第一次写入 strcpy(shmaddr, argv[1]); // ...... 等待进程B读取完第一条消息 ...... // 写入第二条消息:直接往共享内存地址拷贝即可,不需要重新映射 memset(shmaddr, 0, 1024); // 清空旧内容,避免字符串残留 strcpy(shmaddr, "第二条要发送的字符串");
只要是往shmat返回的映射地址范围内写入数据,所有attach了该共享内存段的进程都可以直接读到更新后的内容,映射关系不会因为你写入新内容就失效。
detach/重新attach的性能问题
这种方案完全没有必要,且存在不必要的开销:
shmat和shmdt都是系统调用,执行时需要陷入内核、修改进程页表、刷新TLB缓存,频繁调用会产生额外的性能损耗- 共享内存的设计初衷就是一次映射后长期读写,反复detach/attach属于违背设计初衷的冗余操作。
更优实现注意事项
你当前的实现缺少最关键的进程同步机制,直接裸写共享内存会出现竞态问题:比如进程A写数据写到一半,进程B就开始读取,会拿到半截不完整的脏数据。生产环境使用时需要搭配同步机制,常用方案包括:
- 搭配信号量(System V信号量/POSIX信号量均可):写进程写完数据后通过信号量通知读进程,读进程等待信号量触发后再读取数据,是最通用的可靠方案
- 轻量场景可在共享内存头部自定义状态位:定义共享内存结构体,用一个整型标记位标识当前内存是否有可读数据、是否可写,读写双方通过检查标记位决定操作时机,多核场景下标记位建议用原子变量避免缓存一致性问题
- 可以用
char * const修饰shmat返回的指针,从编译层面避免误修改指针指向的低级错误。
内容的提问来源于stack exchange,提问作者RogerRoger7
相关产品推荐
相关产品推荐

