System V消息队列周期性莫名重置,请求技术解析
问题分析与解决
你的代码出现消息队列内容周期性重置、消息丢失的核心原因是函数未声明导致的未定义行为,同时进程输出交错也会干扰你对实际执行流程的判断,以下是具体分析和修复方案:
1. 未声明函数引发的未定义行为
在main函数中调用了child_main,但该函数既没有在main前声明原型,也没有在全局作用域前置声明。在C语言中,未声明的函数会被编译器默认假设为返回int类型,且参数遵循默认类型提升规则,但你的child_main实际返回void类型,这种不匹配会破坏子进程的栈帧结构,导致后续代码执行出现不可预测的错误——比如消息读取逻辑异常、变量被意外覆盖,最终表现为消息丢失或队列状态异常。
2. 输出交错导致的流程误解
父进程和子进程的printf输出共享同一个标准输出流,由于进程调度的随机性,两者的输出会交错显示,可能让你误以为消息被“莫名重置”。比如父进程打印队列数量时,子进程可能正在读取消息,此时队列数量的变化是正常的,但输出顺序会干扰你对实际流程的判断。
修复方案
步骤1:添加函数原型声明
在main函数前添加child_main的原型声明,确保编译器正确识别函数的返回类型和参数:
void child_main(int msgQueue);
步骤2:区分进程输出(可选)
为父进程和子进程的输出添加标识,便于清晰区分两者的执行日志:
- 父进程打印修改为:
printf("[Parent] Messages in queue = %ld\n", queueInfo.msg_qnum); - 子进程打印修改为:
printf("[Child] read: %d\n", msg.mtext.value);
步骤3:添加队列清理逻辑
程序退出时(比如捕获SIGINT信号),删除创建的消息队列,避免残留资源影响后续运行:
#include <signal.h> int globalMsgQueue; void cleanup(int sig) { msgctl(globalMsgQueue, IPC_RMID, NULL); exit(EXIT_SUCCESS); } // 在main函数中初始化并注册信号处理 int main() { // ... 其他代码 globalMsgQueue = msgget(queueKey, IPC_CREAT | 0660); signal(SIGINT, cleanup); // ... 后续代码 }
完整修正后的代码
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> typedef struct { int value; } messageText; typedef struct { long mtype; messageText mtext; } queueMessage; // 前置声明child_main函数 void child_main(int msgQueue); int globalMsgQueue; void cleanup(int sig) { msgctl(globalMsgQueue, IPC_RMID, NULL); printf("\nCleaned up message queue.\n"); exit(EXIT_SUCCESS); } int main() { key_t queueKey = ftok(__FILE__, 'M'); globalMsgQueue = msgget(queueKey, IPC_CREAT | 0660); if (globalMsgQueue == -1) { perror("msgget"); exit(EXIT_FAILURE); } // 注册信号处理,用于退出时清理队列 signal(SIGINT, cleanup); pid_t pid = fork(); if (0 == pid) { child_main(globalMsgQueue); abort(); } queueMessage msg = { .mtype = 1, .mtext = { 0 } }; for (int i = 0; ; ++i) { msg.mtext.value = i; int rc = msgsnd(globalMsgQueue, &msg, sizeof(msg.mtext), 0); if (rc == -1) perror("msgsnd"); usleep(250000); // 0.25 sec struct msqid_ds queueInfo; if (msgctl(globalMsgQueue, IPC_STAT, &queueInfo) == -1) { perror("msgctl"); continue; } printf("[Parent] Messages in queue = %ld\n", queueInfo.msg_qnum); } return 0; } void child_main(int msgQueue) { queueMessage msg = { 0 }; int msgTyp = 0; // 接收队列中第一个消息,忽略类型 int msgRcvFlg = IPC_NOWAIT | MSG_NOERROR; while (1) { while (1) { int rc = msgrcv(msgQueue, &msg, sizeof(msg.mtext), msgTyp, msgRcvFlg); if (-1 == rc) { if (errno == ENOMSG) { goto sleep; } else { perror("msgrcv"); exit(EXIT_FAILURE); } } printf("[Child] read: %d\n", msg.mtext.value); } sleep: usleep(5000000); // 5 sec. } }
额外说明
- 从
ipcs -l的输出来看,你的系统消息队列限制远大于测试所需,因此不存在队列满导致的消息丢失问题。 - 修正后的代码运行时,子进程每次唤醒后会读取所有累积的消息,队列数量会在子进程读取后归零,这是符合预期的行为。
msgctl的IPC_STAT调用仅用于读取队列状态,不会修改队列内容。
内容的提问来源于stack exchange,提问作者tr1ang1e
相关产品推荐
相关产品推荐

