fork()创建父子进程后消息队列msgrcv永久阻塞问题排查
代码问题分析与修复
核心阻塞原因
- 父子进程的地址空间相互独立,子进程中执行的
msg.mtype = 1赋值操作,仅会修改子进程地址空间内的msg变量副本,父进程地址空间中的msg结构体的mtype字段没有初始化,是随机的脏值。 - 父进程调用
msgsnd(msgid2, &msg, sizeof(msg), 0)发送消息时,发出的消息的mtype不符合子进程msgrcv指定接收的1的要求,子进程匹配不到符合条件的消息,就会永久阻塞在打印3之前的msgrcv调用行。
其他潜在问题
- 消息收发的长度参数不符合System V消息队列的接口要求:
msgsnd和msgrcv的第三个参数是除mtype字段之外的消息正文长度,代码中直接传入整个结构体的大小,相当于多计算了mtype的长度,可能引发消息截断、内存读写越界问题。 - 消息队列删除时机错误:子进程发送完第二个
msgPing后直接删除两个消息队列,可能导致父进程还没来得及读取最后一个msgPing,队列就被销毁,引发后续异常。
修复方案
- 在父进程发送
msg前主动初始化msg.mtype = 1,或者直接在fork操作之前就完成所有全局消息结构体的初始化,保证父子进程拿到的初始值一致。 - 修正收发长度参数:
msgPing的收发长度改为sizeof(msgPing) - sizeof(long),msg的收发长度改为sizeof(msg) - sizeof(long)。 - 把消息队列销毁的逻辑移到父进程所有操作执行完成之后,避免提前销毁队列导致消息丢失。
核心修改参考代码
// fork前初始化全局变量,保证父子进程初始值一致 msg.mtype = 1; msgPing.mtype = 1; if(fork() == 0) { // 子进程 int msgid1; int msgid2; msgid1 = msgget(MSG_KEY1, 0666 | IPC_CREAT); msgid2 = msgget(MSG_KEY2, 0666 | IPC_CREAT); printf("1 - started, sending msgPing\n"); // 修正发送长度 msgsnd(msgid1, &msgPing, sizeof(msgPing) - sizeof(long), 0); // 修正接收长度 msgrcv(msgid2, &msg, sizeof(msg) - sizeof(long), 1, 0); printf("3 - msg received, sending msgPing\n"); msgsnd(msgid1, &msgPing, sizeof(msgPing) - sizeof(long), 0); // 子进程不再提前销毁队列 return 0; } // 父进程 sleep(1); int msgid1; int msgid2; msgid1 = msgget(MSG_KEY1, 0666 | IPC_CREAT); msgid2 = msgget(MSG_KEY2, 0666 | IPC_CREAT); msgrcv(msgid1, &msgPing, sizeof(msgPing) - sizeof(long), 1, 0); printf("2 - msgPing received, sending msg\n"); msgsnd(msgid2, &msg, sizeof(msg) - sizeof(long), 0); msgrcv(msgid1, &msgPing, sizeof(msgPing) - sizeof(long), 1, 0); printf("4 - msgPing received, finished\n"); // 父进程最后销毁队列 msgctl(msgid1, IPC_RMID, 0); msgctl(msgid2, IPC_RMID, 0); return 0;
内容的提问来源于stack exchange,提问作者pigeon_gcc
相关产品推荐
相关产品推荐

