如何在C语言中通过信号实现子进程向父进程传递多个值?
用信号传递多值的解决方案:实时信号+数据打包/多信号映射
要解决这个问题,核心思路是利用实时信号的可靠传递特性,结合sigqueue函数携带额外数据的能力——因为普通信号(如SIGUSR1)不保证可靠传递,而实时信号(SIGRTMIN到SIGRTMAX)会排队,确保所有发送的信号都能被父进程接收。
下面提供两种可落地的实现方案,你可以根据需求选择:
方案一:单信号+数据打包(节省信号资源)
这种方案用一个实时信号,把「子进程标识」「值的序号」「实际数值」打包到一个整数里,通过sigval传递给父进程,父进程再拆分数据并存储。
完整代码示例(C语言)
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/wait.h> #define NUM_CHILDREN 2 #define NUM_VALUES_PER_CHILD 3 // 存储每个子进程的接收数据 typedef struct { int values[NUM_VALUES_PER_CHILD]; int received_count; // 已接收的值的数量 } ChildData; ChildData child_data[NUM_CHILDREN]; pid_t child_pids[NUM_CHILDREN]; int all_values_received = 0; // 信号处理函数:仅做数据拆分和存储,避免调用非异步安全函数 void signal_handler(int sig, siginfo_t *info, void *unused) { pid_t sender_pid = info->si_pid; int packed_data = info->si_value.sival_int; // 拆分打包数据:高8位=子进程索引,中8位=值的序号,低16位=实际数值 int child_idx = (packed_data >> 24) & 0xFF; int val_idx = (packed_data >> 16) & 0xFF; int value = packed_data & 0xFFFF; // 验证数据合法性 if (child_idx >= NUM_CHILDREN || val_idx >= NUM_VALUES_PER_CHILD) { fprintf(stderr, "Invalid data from PID %d\n", sender_pid); return; } // 存储数据并更新计数 child_data[child_idx].values[val_idx] = value; child_data[child_idx].received_count++; // 检查是否所有值都已接收 all_values_received = 1; for (int i = 0; i < NUM_CHILDREN; i++) { if (child_data[i].received_count < NUM_VALUES_PER_CHILD) { all_values_received = 0; break; } } } int main() { // 配置信号处理:启用SA_SIGINFO以获取发送进程和携带的数据 struct sigaction sa; sa.sa_sigaction = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; if (sigaction(SIGRTMIN, &sa, NULL) == -1) { perror("Failed to set signal handler"); exit(EXIT_FAILURE); } // 初始化子进程数据结构 for (int i = 0; i < NUM_CHILDREN; i++) { child_data[i].received_count = 0; for (int j = 0; j < NUM_VALUES_PER_CHILD; j++) { child_data[i].values[j] = 0; } } // 创建两个子进程 for (int i = 0; i < NUM_CHILDREN; i++) { pid_t pid = fork(); if (pid == -1) { perror("Fork failed"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程逻辑:生成三个值并发送给父进程 int child_idx = i; int values[NUM_VALUES_PER_CHILD]; // 替换为你的实际业务值 if (child_idx == 0) { values[0] = 10; values[1] = 15; values[2] = 20; } else { values[0] = 30; values[1] = 35; values[2] = 40; } // 发送三个打包后的信号 for (int j = 0; j < NUM_VALUES_PER_CHILD; j++) { union sigval sv; sv.sival_int = (child_idx << 24) | (j << 16) | (values[j] & 0xFFFF); if (sigqueue(getppid(), SIGRTMIN, sv) == -1) { perror("Failed to send signal"); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); } else { child_pids[i] = pid; } } // 父进程等待所有信号接收完成 while (!all_values_received) { pause(); // 暂停直到收到信号 } // 输出结果(主进程中调用printf是安全的) printf("Received data from children:\n"); for (int i = 0; i < NUM_CHILDREN; i++) { printf("Child %d (PID: %d): %d, %d, %d\n", i+1, child_pids[i], child_data[i].values[0], child_data[i].values[1], child_data[i].values[2]); } // 等待子进程退出,避免僵尸进程 for (int i = 0; i < NUM_CHILDREN; i++) { waitpid(child_pids[i], NULL, 0); } return EXIT_SUCCESS; }
关键说明
- 实时信号的可靠性:使用
SIGRTMIN而非普通信号,确保多个信号不会丢失。 - 数据打包规则:这里假设数值不超过65535(16位),如果你的数值更大,可以调整位分配(比如用24位存数值,4位存子进程和值的序号)。
- 异步信号安全:信号处理函数中只做简单的赋值和计数,避免调用
printf这类非安全函数。
方案二:多信号映射(直观易维护)
这种方案用不同的实时信号对应不同的「值序号」,比如SIGRTMIN对应第一个值,SIGRTMIN+1对应第二个,SIGRTMIN+2对应第三个。父进程根据信号编号判断是第几个值,再结合发送进程的PID区分子进程。
核心代码调整
信号处理函数
void signal_handler(int sig, siginfo_t *info, void *unused) { pid_t sender_pid = info->si_pid; int value = info->si_value.sival_int; int val_idx = sig - SIGRTMIN; // 计算值的序号 // 找到发送信号的子进程索引 int child_idx = -1; for (int i = 0; i < NUM_CHILDREN; i++) { if (child_pids[i] == sender_pid) { child_idx = i; break; } } if (child_idx == -1 || val_idx >= NUM_VALUES_PER_CHILD) { fprintf(stderr, "Invalid signal from PID %d\n", sender_pid); return; } child_data[child_idx].values[val_idx] = value; child_data[child_idx].received_count++; // 检查是否全部接收完成(同方案一) all_values_received = 1; for (int i = 0; i < NUM_CHILDREN; i++) { if (child_data[i].received_count < NUM_VALUES_PER_CHILD) { all_values_received = 0; break; } } }
父进程注册多个信号
// 注册三个实时信号的处理函数 for (int i = 0; i < NUM_VALUES_PER_CHILD; i++) { if (sigaction(SIGRTMIN + i, &sa, NULL) == -1) { perror("Failed to set signal handler"); exit(EXIT_FAILURE); } }
子进程发送对应信号
for (int j = 0; j < NUM_VALUES_PER_CHILD; j++) { union sigval sv; sv.sival_int = values[j]; if (sigqueue(getppid(), SIGRTMIN + j, sv) == -1) { perror("Failed to send signal"); exit(EXIT_FAILURE); } }
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 单信号打包 | 占用信号少,适合需要大量信号的场景 | 需要处理位运算,数值范围受限于位分配 |
| 多信号映射 | 逻辑直观,无需位运算,数值无限制 | 占用多个实时信号(系统通常提供至少32个,完全足够) |
内容的提问来源于stack exchange,提问作者user8706163
相关产品推荐
相关产品推荐

