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

如何让自定义信号处理器仅影响Shell启动的子进程

实现自定义信号处理器仅终止Shell启动的进程

核心思路

要实现只终止Shell启动的进程,必须追踪这些进程的PID/进程组ID,然后在信号处理器中仅向这些目标发送信号。关键是修改原Shell代码的命令执行逻辑,加入fork+exec的流程并维护活跃子进程列表。

具体修改步骤

1. 新增全局变量维护活跃子进程PID

首先定义全局数组(或链表)存储Shell启动的子进程PID,以及对应的增删函数:

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

// 存储活跃子进程PID,可根据需求调整数组容量
pid_t active_children[100];
int child_count = 0;

// 添加子进程PID到列表
void add_child(pid_t pid) {
    if (child_count < 100) {
        active_children[child_count++] = pid;
    }
}

// 从列表中移除已退出的子进程PID
void remove_child(pid_t pid) {
    for (int i = 0; i < child_count; i++) {
        if (active_children[i] == pid) {
            // 覆盖移除的PID,调整计数
            for (int j = i; j < child_count - 1; j++) {
                active_children[j] = active_children[j+1];
            }
            child_count--;
            break;
        }
    }
}

2. 修改命令执行逻辑为fork+exec

替换原代码中直接执行命令的逻辑(比如原execvp调用),改成创建子进程执行命令,父进程保存PID:

void execute_cmd(char **args) {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:执行目标命令
        if (execvp(args[0], args) == -1) {
            perror("shell");
        }
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        // 父进程:记录子进程PID
        add_child(pid);
        
        // 如果支持后台进程(&结尾),这里要用非阻塞waitpid或SIGCHLD处理
        // 以下是前台进程的处理:等待子进程退出后清理PID
        int status;
        waitpid(pid, &status, 0);
        remove_child(pid);
    } else {
        perror("fork failed");
    }
}

3. 编写自定义信号处理器

在信号处理器中,仅向列表中的子进程发送信号:

void sig_handler(int sig) {
    // 遍历所有活跃子进程,发送对应信号
    for (int i = 0; i < child_count; i++) {
        kill(active_children[i], sig);
    }
}

4. 注册信号与处理子进程退出

在main函数中注册信号处理器,同时处理SIGCHLD来清理后台进程的PID:

// 处理SIGCHLD信号,清理已退出的后台进程PID
void sigchld_handler(int sig) {
    pid_t pid;
    int status;
    // 循环处理所有退出的子进程,避免僵尸进程
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        remove_child(pid);
    }
}

int main() {
    // 注册信号处理器(比如SIGINT对应Ctrl+C,SIGTSTP对应Ctrl+Z)
    signal(SIGINT, sig_handler);
    signal(SIGTSTP, sig_handler);
    // 注册SIGCHLD处理后台进程退出
    signal(SIGCHLD, sigchld_handler);
    
    // 原Shell主循环:读取命令、解析、调用execute_cmd
    // ... 保留原有的命令读取、解析逻辑 ...
}

进阶优化:使用进程组管理

如果你的Shell支持管道、多进程命令,用进程组更高效:在子进程中设置新进程组,然后信号处理器向整个进程组发送信号:

// 在子进程执行前添加:
if (pid == 0) {
    setpgid(0, 0); // 将当前子进程设为新进程组的组长
    // 后续执行命令
}

此时父进程可以存储进程组ID,信号处理器中用kill(-pgid, sig)发送信号给整个进程组,确保关联进程都被终止。

关键注意事项

  • 如果原代码用了system()函数执行命令,必须替换为fork+exec,因为system()无法返回子进程PID,无法追踪。
  • 维护PID列表时要注意线程安全(如果Shell是多线程的话),但简易Shell一般是单线程,无需额外处理。
  • 测试时要注意区分前台/后台进程,确保信号只作用于Shell启动的进程,不会终止Shell本身。

内容的提问来源于stack exchange,提问作者Samuel Afon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:16:31