You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

关键说明

  1. 实时信号的可靠性:使用SIGRTMIN而非普通信号,确保多个信号不会丢失。
  2. 数据打包规则:这里假设数值不超过65535(16位),如果你的数值更大,可以调整位分配(比如用24位存数值,4位存子进程和值的序号)。
  3. 异步信号安全:信号处理函数中只做简单的赋值和计数,避免调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:37:25