Linux下C语言sigqueue传递struct指针至子进程异常排查
问题
我正在学习Linux下C语言的信号相关知识,想通过sigqueue函数,借助信号处理函数的siginfo_t结构体参数,把一个struct Data整数结构体的指针从父进程传给fork出的子进程(信号处理函数用sigaction定义,原型是void handler(int, siginfo_t*, void*))。
父进程定义了union sigval,把sival_ptr字段设为指向struct Data的指针(结构体包含两个整数),然后调用sigqueue(child_pid, SIGUSR1, my_sigval)发送信号。
子进程通过sigaction设置了SIGUSR1的处理函数,启用了SA_SIGINFO标志。在信号处理函数里,我定义了struct Data*类型指针data,执行data = (struct Data*) (si->si_value.sival_ptr)强转,然后尝试打印父进程发送的数据。
但实际运行时,子进程会在打印struct Data内的值前退出,而该语句之前的代码都能正常执行输出。请问哪里出错了?是父进程传递指针的方式不对,还是子进程处理函数里的强转有问题?
父进程代码
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #define MY_SIGNAL SIGUSR1 // 定义包含两个整数的结构体 struct Data { int value1; int value2; }; int main() { // 准备数据结构 struct Data* data = (struct Data*) malloc(sizeof(struct Data)); data->value1 = 10; data->value2 = 20; // 创建子进程 pid_t pid = fork(); if (pid == -1) { perror("fork failed"); return 1; } else if (pid == 0) { // 子进程 // 子进程逻辑 execlp("./c2","c2", NULL); } else { // 父进程 // 发送携带数据结构体指针的信号 union sigval value; value.sival_ptr = (void*) data; // value.sival_int = 7; sleep(1); sigqueue(pid, MY_SIGNAL, value); sleep(1); } wait(NULL); return 0; }
子进程代码
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #define MY_SIGNAL SIGUSR1 // 定义包含两个整数的结构体 struct Data { int value1; int value2; }; // 信号处理函数 void handler(int sig, siginfo_t *si, void *unused) { printf("Received signal: %d\n", sig); // printf("Received signal value: %d\n", si->si_value.sival_int); struct Data* data; data = (struct Data*)(si->si_value.sival_ptr); printf("Received values: %d, %d\n", data->value1, data->value2); // 将接收的数据复制到分配的内存中 } int main() { // 设置信号处理 struct sigaction sa; sa.sa_sigaction = handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(MY_SIGNAL, &sa, NULL); pause(); return 0; }
错误原因分析
核心问题是进程地址空间隔离:
- 父进程和你用
execlp替换镜像后的子进程,拥有完全独立的虚拟地址空间。父进程中malloc出来的struct Data指针,仅在父进程地址空间内有效,子进程地址空间里这个指针指向的是无效内存区域。 - 当子进程尝试通过该指针访问内存时,会触发段错误(SIGSEGV),导致进程直接退出,因此
printf("Received values: ...")语句根本没机会执行。 - 你看到的“前面代码正常输出”,是因为段错误发生在访问
data->value1的瞬间,之前的printf("Received signal: %d\n", sig)已经完成输出。
修复方案
不能直接跨进程传递指针,要传递数据本身或者使用进程间共享内存的方式,以下是两种可行方案:
方案1:用sival_int传递小数据
如果结构体数据量小,可以把两个整数打包到sival_int中(通过位操作实现),关键修改如下:
父进程修改部分
// 把value1存在高16位,value2存在低16位 union sigval value; value.sival_int = (data->value1 << 16) | (data->value2 & 0xFFFF); sleep(1); sigqueue(pid, MY_SIGNAL, value); sleep(1);
子进程处理函数修改部分
void handler(int sig, siginfo_t *si, void *unused) { printf("Received signal: %d\n", sig); int value1 = (si->si_value.sival_int >> 16) & 0xFFFF; int value2 = si->si_value.sival_int & 0xFFFF; printf("Received values: %d, %d\n", value1, value2); }
方案2:使用共享内存传递大数据
如果结构体数据量较大,用共享内存让父子进程访问同一块物理内存:
父进程修改部分
#include <sys/shm.h> // 新增头文件 int main() { // 创建共享内存段 int shmid = shmget(IPC_PRIVATE, sizeof(struct Data), IPC_CREAT | 0666); if (shmid == -1) { perror("shmget failed"); return 1; } // 映射共享内存到父进程地址空间 struct Data* data = (struct Data*) shmat(shmid, NULL, 0); if (data == (void*)-1) { perror("shmat failed"); return 1; } data->value1 = 10; data->value2 = 20; pid_t pid = fork(); if (pid == -1) { perror("fork failed"); return 1; } else if (pid == 0) { // 子进程 // 把共享内存ID作为参数传给子进程 char shmid_str[10]; snprintf(shmid_str, sizeof(shmid_str), "%d", shmid); execlp("./c2","c2", shmid_str, NULL); } else { // 父进程 sleep(1); // 发送信号通知子进程读取共享内存 union sigval value; sigqueue(pid, MY_SIGNAL, value); sleep(1); // 解除映射并删除共享内存 shmdt(data); shmctl(shmid, IPC_RMID, NULL); } wait(NULL); return 0; }
子进程修改部分
#include <sys/shm.h> // 新增头文件 int shmid; // 全局变量存储共享内存ID void handler(int sig, siginfo_t *si, void *unused) { printf("Received signal: %d\n", sig); struct Data* data = (struct Data*) shmat(shmid, NULL, 0); if (data == (void*)-1) { perror("shmat failed"); return; } printf("Received values: %d, %d\n", data->value1, data->value2); shmdt(data); } int main(int argc, char* argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <shmid>\n", argv[0]); exit(1); } shmid = atoi(argv[1]); // 设置信号处理 struct sigaction sa; sa.sa_sigaction = handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(MY_SIGNAL, &sa, NULL); pause(); return 0; }
内容的提问来源于stack exchange,提问作者Hamza Awashra
相关产品推荐
相关产品推荐

