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

多进程C程序中子进程间信号传播失效问题排查

问题:多进程信号无法正确传播

我正在开发一个C程序,包含三个协作进程:

  • 进程1:逐行读取文件
  • 进程2:统计行数
  • 进程3(主进程):维护最终行数统计

进程间通过管道通信,同时要求收到SIGUSR1、SIGUSR2、SIGCONT、SIGINT时,必须将信号传播给另外两个进程,信号处理设置如下:

signal(SIGUSR1, handle_s1);  // 终止
signal(SIGUSR2, handle_s2);  // 挂起
signal(SIGCONT, handle_s3);  // 恢复
signal(SIGINT, handle_s4);   // 消息处理

现在的问题是:当信号发送给子进程(pid1或pid2)时,信号无法正确传播到其他进程,子进程只会处理自身的信号操作,不会通知其他进程。

我的信号传播实现存在什么问题?如何确保三个进程间的信号能正确传播?


完整代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <time.h>

#define BUFFER_SIZE 1024
#define FIFO_1 "/tmp/fifo_1"
#define FIFO_2 "/tmp/fifo_2"
#define FIFO_3 "/tmp/fifo_3"

typedef struct {
    int signal_type;    
    pid_t sender_pid;   
    char message[256];  
    int line_count;     
} Message;

volatile sig_atomic_t is_suspended = 0;
volatile sig_atomic_t should_exit = 0;
time_t start_time;

pid_t pid1, pid2, pid3;

void propagate_signal(int sig, pid_t sender_pid) {
    if (sender_pid == pid1) {
        printf("Propagating signal from Process 1 (PID: %d) to Process 2 and Process 3\n", sender_pid);
        kill(pid2, sig);
        kill(pid3, sig);
    } else if (sender_pid == pid2) {
        printf("Propagating signal from Process 2 (PID: %d) to Process 1 and Process 3\n", sender_pid);
        kill(pid1, sig);
        kill(pid3, sig);
    } else if (sender_pid == pid3) {
        printf("Propagating signal from Process 3 (PID: %d) to Process 1 and Process 2\n", sender_pid);
        kill(pid1, sig);
        kill(pid2, sig);
    }
}

void handle_s1(int sig) {
    should_exit = 1;
    printf("Process %d: Received termination signal (S1)\n", getpid());
    propagate_signal(SIGUSR1, getpid());
}

void handle_s2(int sig) {
    is_suspended = 1;
    printf("Process %d: Received suspension signal (S2)\n", getpid());
    propagate_signal(SIGUSR2, getpid());
}

void handle_s3(int sig) {
    is_suspended = 0;
    printf("Process %d: Received resume signal (S3)\n", getpid());
    propagate_signal(SIGCONT, getpid());
}

void handle_s4(int sig) {
    Message msg;
    char fifo_path[20];
    sprintf(fifo_path, "/tmp/fifo_%d", getpid());
    
    int fifo_fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
    if (fifo_fd >= 0) {
        if (read(fifo_fd, &msg, sizeof(msg)) > 0) {
            printf("Process %d: Received message from process %d: %s\n", 
                   getpid(), msg.sender_pid, msg.message);
            propagate_signal(SIGINT, getpid());
            if (msg.line_count > 0) {
                printf("Line count information received: %d\n", msg.line_count);
            }
        }
        close(fifo_fd);
    }
}

void send_message_to_others(int signal_type, const char* msg, int line_count) {
    Message message;
    message.signal_type = signal_type;
    message.sender_pid = getpid();
    message.line_count = line_count;
    strncpy(message.message, msg, 255);
    
    for (int i = 1; i <= 3; i++) {
        char fifo_path[20];
        sprintf(fifo_path, "/tmp/fifo_%d", i);
        int fd = open(fifo_path, O_WRONLY | O_NONBLOCK);
        if (fd >= 0) {
            write(fd, &message, sizeof(message));
            close(fd);
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <file_path>\n", argv[0]);
        exit(1);
    }

    mkfifo(FIFO_1, 0666);
    mkfifo(FIFO_2, 0666);
    mkfifo(FIFO_3, 0666);

    signal(SIGUSR1, handle_s1);
    signal(SIGUSR2, handle_s2);
    signal(SIGCONT, handle_s3);
    signal(SIGINT, handle_s4);

    int pipe1[2], pipe2[2];
    start_time = time(NULL);

    if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
        perror("pipe");
        exit(1);
    }

    pid1 = fork();
    if (pid1 == 0) { 
        pid_t pid = getpid();
        printf("Process 1 (PID: %d) started\n", pid);
        close(pipe1[0]);
        close(pipe2[0]);
        close(pipe2[1]);

        FILE *file = fopen(argv[1], "r");
        if (file == NULL) {
            perror("fopen");
            exit(1);
        }

        char buffer[BUFFER_SIZE];
        size_t bytes_read;
        int line_number = 0;

        while (fgets(buffer, BUFFER_SIZE, file) != NULL && !should_exit) {
            while(is_suspended) {
                usleep(100000); 
            }
            bytes_read = strlen(buffer);
            if (write(pipe1[1], buffer, bytes_read) != bytes_read) {
                perror("write to pipe");
                break;
            }
            line_number++;
            printf("Process 1: Read line %d: %s", line_number, buffer);
            send_message_to_others(4, "Read new line", line_number);
            usleep(100000);
        }

        printf("Process 1: Finished reading file (read %d lines)\n", line_number);
        fclose(file);
        close(pipe1[1]);
        exit(0);
    }

    pid2 = fork();
    if (pid2 == 0) { 
        pid_t pid = getpid();
        printf("Process 2 (PID: %d) started\n", pid);
        close(pipe1[1]);
        close(pipe2[0]);

        char ch;
        int line_count = 0;
        ssize_t bytes_read;
        int last_reported_count = 0;

        while ((bytes_read = read(pipe1[0], &ch, 1)) > 0 && !should_exit) {
            while(is_suspended) {
                usleep(100000); 
            }
            if (ch == '\n') {
                line_count++;
                if (line_count != last_reported_count) {
                    printf("Process 2: Counted line %d\n", line_count);
                    write(pipe2[1], &line_count, sizeof(line_count));
                    send_message_to_others(4, "Counted line", line_count);
                    last_reported_count = line_count;
                }
            }
        }

        printf("Process 2: Finishing operation\n");
        close(pipe1[0]);
        close(pipe2[1]);
        exit(0);
    }

    pid3 = getpid();
    printf("Process 3 (main, PID: %d) started\n", pid3);
    close(pipe1[0]);
    close(pipe1[1]);
    close(pipe2[1]);

    int last_count = 0;
    int final_count;

    while (read(pipe2[0], &final_count, sizeof(final_count)) > 0 && !should_exit) {
        while(is_suspended) {
            usleep(100000); 
        }
        if (final_count != last_count) {
            printf("Main process: Updated line count: %d\n", final_count);
            send_message_to_others(4, "Updated line count", final_count);
            last_count = final_count;
        }
    }

    printf("Main process: Finishing operation. Final line count: %d\n", last_count);

    waitpid(pid1, NULL, 0);
    waitpid(pid2, NULL, 0);

    close(pipe2[0]);
    unlink(FIFO_1);
    unlink(FIFO_2);
    unlink(FIFO_3);

    return 0;
}

问题分析

1. 子进程全局PID变量未正确初始化

主进程中pid1和pid2是fork后的子进程PID,但子进程会复制主进程的内存空间,fork后子进程不会更新这些全局变量的值:

  • 进程1中,pid1是fork返回的0,pid2和pid3保持主进程未fork时的初始值(0)
  • 进程2中,pid2是fork返回的0,pid1是主进程记录的进程1PID,但propagate_signal的判断条件依赖sender_pid == pid1/pid2/pid3,此时子进程的pid1或pid2为0,和自身PID不匹配,导致分支逻辑完全不执行,无法传播信号。

2. 信号处理函数存在非可重入风险

printf和部分文件操作属于非可重入函数,在信号处理函数中调用可能触发未定义行为(比如信号嵌套处理时破坏程序状态)。

3. SIGCONT信号处理逻辑冗余

代码用自定义is_suspended变量控制进程暂停,但SIGCONT的系统语义是唤醒被暂停的进程,当前处理逻辑未和系统行为对齐,可能导致状态不一致。


解决方案

1. 让子进程获取所有进程的PID

通过已创建的FIFO,主进程在fork完成后,将所有进程的PID发送给子进程:

  • 主进程在fork完两个子进程后,添加:
// 给进程1发送进程2的PID
int fd1 = open(FIFO_1, O_WRONLY);
write(fd1, &pid2, sizeof(pid_t));
close(fd1);

// 给进程2发送进程1的PID
int fd2 = open(FIFO_2, O_WRONLY);
write(fd2, &pid1, sizeof(pid_t));
close(fd2);
  • 进程1初始化时读取PID:
pid1 = getpid();
pid3 = getppid();
// 读取进程2的PID
int fd = open(FIFO_1, O_RDONLY);
read(fd, &pid2, sizeof(pid_t));
close(fd);
  • 进程2初始化时读取PID:
pid2 = getpid();
pid3 = getppid();
// 读取进程1的PID
int fd = open(FIFO_2, O_RDONLY);
read(fd, &pid1, sizeof(pid_t));
close(fd);

2. 简化信号传播逻辑

无需判断发送者PID,直接排除自身PID,发送信号给另外两个进程:

void propagate_signal(int sig) {
    pid_t self = getpid();
    if (pid1 != 0 && self != pid1) kill(pid1, sig);
    if (pid2 != 0 && self != pid2) kill(pid2, sig);
    if (pid3 != 0 && self != pid3) kill(pid3, sig);
    
    char msg[100];
    snprintf(msg, sizeof(msg), "Process %d propagated signal %d\n", self, sig);
    write(STDOUT_FILENO, msg, strlen(msg));
}

3. 替换非可重入函数

将信号处理函数中的printf替换为write(write是系统调用,属于可重入函数),避免未定义行为:

void handle_s1(int sig) {
    should_exit = 1;
    char msg[100];
    snprintf(msg, sizeof(msg), "Process %d: Received termination signal (S1)\n", getpid());
    write(STDOUT_FILENO, msg, strlen(msg));
    propagate_signal(sig);
}

4. 对齐SIGCONT的系统语义

保留自定义is_suspended变量的同时,确保SIGCONT处理函数正确重置状态,避免重复传播:

void handle_s3(int sig) {
    is_suspended = 0;
    char msg[100];
    snprintf(msg, sizeof(msg), "Process %d: Received resume signal (S3)\n", getpid());
    write(STDOUT_FILENO, msg, strlen(msg));
    propagate_signal(sig);
}

内容的提问来源于stack exchange,提问作者Kris

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:29:51