C语言信号处理程序陷入死循环,无法同时终止父子进程
问题分析与解决方案
核心问题点
- 父进程信号处理函数未注册:你的
signal(SIGUSR1, sig_handler_parent);放在了while(true)死循环之后,这段代码永远不会被执行到,父进程根本没有设置SIGUSR1的自定义处理逻辑,后续信号触发后行为不可控。 - 子进程信号处理后未退出:子进程触发SIGUSR1信号后,仅给父进程发信号却未主动退出,导致父进程的
waitpid一直阻塞,子进程可能继续执行后续逻辑,甚至重复触发信号引发死循环。 - 信号处理函数使用非安全函数:
puts属于非异步信号安全函数,在信号处理函数中调用可能引发未定义行为,应该改用write这类安全函数。 - 信号发送逻辑冗余:子进程无需给自己发SIGUSR1来触发处理函数,直接在
myfunc2中给父进程发信号后退出即可,绕圈触发信号反而增加复杂度。
修正后的代码示例
信号处理函数优化
#include <unistd.h> // 子进程信号处理函数 void sig_handler_child(int __attribute__ ((unused)) signum) { const char msg[] = "dead child\n"; write(STDOUT_FILENO, msg, sizeof(msg)-1); kill(getppid(), SIGUSR1); _exit(0); // 必须主动退出子进程 } // 父进程信号处理函数 void sig_handler_parent(int __attribute__ ((unused)) signum) { const char msg[] = "killing parent\n"; write(STDOUT_FILENO, msg, sizeof(msg)-1); // 处理空指针情况,避免atoi崩溃 char *exit_val = getenv("EXIT_VAL"); int ext_val = exit_val ? atoi(exit_val) : 0; exit(ext_val); }
主逻辑修正
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/wait.h> #include <string.h> #include <unistd.h> // 函数声明 void sig_handler_child(int); void sig_handler_parent(int); void myfunc(char *env[]); void myfunc2(char *argv[]); int main(int argc, char *argv[], char *env[]) { // 保留你的初始化逻辑 myfunc(env); return 0; } void myfunc(char *env[]) { pid_t session; int wstatus; // 父进程提前注册信号处理函数,必须放在循环之前 signal(SIGUSR1, sig_handler_parent); while (1) { // 替换成你的用户输入获取逻辑 char *test_argv[] = {"exit", "123", NULL}; myfunc2(test_argv); session = fork(); if (session == -1) { perror("fork"); exit(EXIT_FAILURE); } if (session == 0) { signal(SIGUSR1, sig_handler_child); // 这里执行你的命令逻辑,比如exec系列函数 _exit(0); } waitpid(session, &wstatus, 0); } } void myfunc2(char *argv[]) { char *exitc = "exit"; if (strcmp(argv[0], exitc) == 0) { // 设置退出值 if (argv[1] != NULL) setenv("EXIT_VAL", argv[1], 1); else setenv("EXIT_VAL", "0", 1); // 直接给父进程发信号,然后子进程退出 kill(getppid(), SIGUSR1); _exit(0); } }
更简洁的替代方案(无需信号)
如果担心退出码判断冲突,可以约定一个专属退出码(比如255,避开常规命令的退出码范围),子进程遇到exit命令时用该码退出,父进程通过waitpid结果判断:
// myfunc2修改 void myfunc2(char *argv[]) { char *exitc = "exit"; if (strcmp(argv[0], exitc) == 0) { if (argv[1] != NULL) setenv("EXIT_VAL", argv[1], 1); else setenv("EXIT_VAL", "0", 1); _exit(255); // 约定专属退出码 } } // myfunc循环内修改 waitpid(session, &wstatus, 0); if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 255) { char *exit_val = getenv("EXIT_VAL"); int ext_val = exit_val ? atoi(exit_val) : 0; exit(ext_val); }
这种方式比信号处理更简单可靠,避免了信号相关的各种潜在问题。
额外注意事项
- 信号处理函数中只能调用异步信号安全的函数,比如
write、_exit、kill等,禁止使用printf、puts这类非安全函数。 signal函数存在兼容性问题,建议改用更可靠的sigaction注册信号。- 如果需要让父进程响应Ctrl+C,需单独注册SIGINT信号处理函数,否则进程可能因信号逻辑混乱陷入无法终止的状态。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

