MAC自制Shell中SIGTSTP信号处理异常:cat与sleep表现差异
自制Shell信号处理异常问题排查与解决
需求背景
- Shell自身忽略
SIGINT(Ctrl+C)、SIGTSTP(Ctrl+Z)、SIGQUIT(Ctrl+)信号 - 外部命令(如sleep、cat)的子进程需按默认逻辑处理上述信号
异常代码实现
void sig_handler(int signo) { if (signo == SIGINT || signo == SIGQUIT || signo == SIGTSTP) { // ignores } else { // other signals, behaves default signal(signo, SIG_DFL); raise(signo); } } int main() { signal(SIGINT, sig_handler); signal(SIGTSTP, sig_handler); signal(SIGQUIT, sig_handler); while (1){ printf(">> "); fflush(stdout); //parse the input; skip the code for simplicity cmd = parse_input(); // for example, cmd = {"cat", NULL}; or cmd = {"sleep", "60", NULL}, or cmd = {"ls", NULL} pid_t child_pid; pid = fork(); if (pid < 0) { perror("error:fork"); exit(EXIT_FAILURE); } if (pid == 0) { child_pid = pid; execvp(cmds[0][0], cmds[0]); perror("error:exe"); exit(EXIT_FAILURE); } else { int status; waitpid(child_pid, &status, WUNTRACED | WCONTINUED); } } }
测试异常现象
- 输入
cat后按Ctrl+Z,Shell回到提示符;接着输入ls,仅输出换行,再次回车才恢复正常提示符,ls未执行。 - 输入
sleep 10后按Ctrl+Z,Shell回到提示符;接着输入ls,能正常列出当前目录文件。
问题原因分析
- 终端模式未恢复:
cat运行时会修改终端为非规范模式(无需回车即可读取输入),当按下Ctrl+Z暂停子进程后,终端模式没有自动恢复为Shell的默认规范模式,导致后续输入无法被parse_input正确解析。而sleep不会修改终端属性,因此后续输入正常。 - 信号处理逻辑冗余:自定义信号处理函数为空操作,不如直接使用
SIG_IGN忽略信号简洁,且存在信号继承的潜在问题。 - 子进程信号重置不明确:虽然
execvp会将信号处理重置为默认,但显式重置更稳妥,避免系统兼容性问题。
解决方案
1. 替换自定义信号处理为直接忽略
无需写空处理函数,直接用signal注册SIG_IGN忽略目标信号:
int main() { signal(SIGINT, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGQUIT, SIG_IGN); // ... 后续代码 }
2. 保存并恢复终端模式
在Shell启动时保存默认终端属性,子进程暂停后恢复该属性:
#include <termios.h> struct termios shell_termios; int main() { // 保存Shell默认终端属性 tcgetattr(STDIN_FILENO, &shell_termios); // ... 信号注册、循环代码 // waitpid后检查子进程状态,暂停则恢复终端 else { int status; waitpid(pid, &status, WUNTRACED | WCONTINUED); if (WIFSTOPPED(status)) { tcsetattr(STDIN_FILENO, TCSANOW, &shell_termios); } } }
3. 子进程显式重置信号
在子进程execvp前,显式将三个信号重置为默认处理,确保不受父进程影响:
if (pid == 0) { signal(SIGINT, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGQUIT, SIG_DFL); execvp(cmds[0][0], cmds[0]); perror("error:exe"); exit(EXIT_FAILURE); }
最终修正核心代码片段
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/wait.h> #include <termios.h> struct termios shell_termios; int main() { // 保存Shell默认终端属性 tcgetattr(STDIN_FILENO, &shell_termios); // 直接忽略三个信号 signal(SIGINT, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGQUIT, SIG_IGN); while (1){ printf(">> "); fflush(stdout); // 解析输入 char **cmd = parse_input(); if (!cmd) continue; pid_t pid = fork(); if (pid < 0) { perror("error:fork"); exit(EXIT_FAILURE); } if (pid == 0) { // 子进程重置信号为默认 signal(SIGINT, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGQUIT, SIG_DFL); // 恢复终端默认模式 tcsetattr(STDIN_FILENO, TCSANOW, &shell_termios); execvp(cmd[0], cmd); perror("error:exe"); exit(EXIT_FAILURE); } else { int status; waitpid(pid, &status, WUNTRACED | WCONTINUED); // 子进程暂停时恢复Shell终端属性 if (WIFSTOPPED(status)) { tcsetattr(STDIN_FILENO, TCSANOW, &shell_termios); } } // 释放输入解析的内存 free(cmd); } return 0; }
内容的提问来源于stack exchange,提问作者ElleryL
相关产品推荐
相关产品推荐

