C语言编写shell时如何让signal信号处理函数仅在子进程触发
问题根源分析
- 方案1失效原因:全局注册信号处理函数后,父子进程默认属于同一进程组,终端产生的
SIGQUIT等信号会发送给整个前台进程组的所有进程,因此父进程也会触发处理逻辑,不符合提示符下无响应的需求。 - 方案2失效原因:
execvp会替换子进程的整个用户地址空间,你在exec前注册的自定义信号处理函数属于原shell的代码,exec后会被自动重置为SIG_DFL(默认处理),因此注册逻辑完全不生效。同时你没有修改进程组,父进程依然会收到终端信号。
正确实现逻辑
要实现和真实shell一致的信号行为,按以下步骤修改代码:
- 主进程初始化时忽略目标信号
shell主进程(提示符等待输入状态)直接将SIGINT、SIGQUIT设置为忽略,从根源避免提示符下按快捷键触发处理:
// main函数最开头添加 #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <termios.h> signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGTTOU, SIG_IGN); // 忽略后台进程尝试修改终端属性的信号,避免tcsetpgrp时报错
- fork子进程后修改子进程信号配置与进程组
子进程分支中,先把信号处理恢复为默认(或你需要的自定义处理,注意如果是执行外部命令,自定义处理会在exec后失效,仅内置命令可用),同时将子进程设为独立进程组,接管终端前台:
pid = fork(); if (pid == 0) { // 子进程:恢复信号默认处理,设置自身为进程组组长 signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); setpgid(0, 0); // 将子进程的进程组ID设为自身PID tcsetpgrp(STDIN_FILENO, getpid()); // 子进程接管前台 if (execvp(command, command_args) == -1) { perror(">"); exit(127); } } else if (pid > 0) { // 父进程:同步设置子进程的进程组,避免竞态 setpgid(pid, pid); tcsetpgrp(STDIN_FILENO, pid); // 父进程将前台切给子进程 int status; waitpid(pid, &status, 0); // 等待子进程退出 // 子进程退出后,将前台切回shell自身的进程组 tcsetpgrp(STDIN_FILENO, getpgrp()); // 这里可以额外处理子进程的退出状态,比如判断是否是SIGQUIT触发的coredump if (WIFSIGNALED(status) && WTERMSIG(status) == SIGQUIT) { printf("Quit (core dumped)\n"); } }
- 如果需要给子进程(仅内置命令场景)添加自定义信号处理,不要在exec前注册,改为针对不调用exec的内置命令分支单独注册即可。
额外说明
如果你确实需要子进程执行外部命令时触发自定义逻辑,不要在子进程注册处理函数,改为父进程在waitpid后通过返回的status判断子进程的终止信号,再执行对应的处理逻辑即可,这也是真实shell的通用实现方式。
内容的提问来源于stack exchange,提问作者digicolp
相关产品推荐
相关产品推荐

