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

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,能正常列出当前目录文件。

问题原因分析

  1. 终端模式未恢复:cat运行时会修改终端为非规范模式(无需回车即可读取输入),当按下Ctrl+Z暂停子进程后,终端模式没有自动恢复为Shell的默认规范模式,导致后续输入无法被parse_input正确解析。而sleep不会修改终端属性,因此后续输入正常。
  2. 信号处理逻辑冗余:自定义信号处理函数为空操作,不如直接使用SIG_IGN忽略信号简洁,且存在信号继承的潜在问题。
  3. 子进程信号重置不明确:虽然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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 10:45:00