多进程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
相关产品推荐
相关产品推荐

