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

如何在后台子进程结束时打印其退出状态并保留父进程命令行权限

后台子进程结束时打印退出状态的实现方案

要实现父进程不阻塞、后台子进程结束时自动打印退出状态的功能,核心是利用SIGCHLD信号——当子进程终止时,内核会向父进程发送该信号。我们可以在父进程中注册信号处理函数,在函数内用waitpid异步获取子进程的退出状态,同时清理僵尸进程。

关键修改步骤

  • 注册SIGCHLD信号处理函数,异步处理子进程终止事件
  • 在信号处理函数中循环调用waitpid,用WNOHANG非阻塞模式处理所有已终止的子进程,避免僵尸进程
  • 维护后台进程列表,启动后台进程时添加到列表,进程终止时移除
  • 修复原代码的已知bug,比如switch分支缺少break、未初始化变量等

修改后的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

#define MAX_JOBS 100 // 最大同时运行的后台进程数

pid_t jobs[MAX_JOBS];
int job_count = 0;
int last_exit_status = 0;
char* last_termination = NULL;
int isForeGround = 1;
int isBackground = 0;
char *argv[1024]; // 存储命令及参数
int i = 0;

// 添加后台进程到列表
void addJob(pid_t pid) {
    if (job_count < MAX_JOBS) {
        jobs[job_count] = pid;
        job_count++;
    } else {
        printf("警告:后台进程数量已达上限\n");
    }
}

// SIGCHLD信号处理函数:处理子进程终止事件
void handle_sigchld(int sig) {
    int status;
    pid_t pid;

    // 循环处理所有已终止的子进程(避免僵尸进程)
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        // 从后台进程列表中移除当前PID
        for (int j = 0; j < job_count; j++) {
            if (jobs[j] == pid) {
                for (int k = j; k < job_count - 1; k++) {
                    jobs[k] = jobs[k+1];
                }
                job_count--;
                break;
            }
        }

        // 打印子进程退出状态
        if (WIFEXITED(status)) {
            printf("[后台进程] PID %d 正常退出,状态码: %d\n", pid, WEXITSTATUS(status));
            last_exit_status = WEXITSTATUS(status);
        } else if (WIFSIGNALED(status)) {
            printf("[后台进程] PID %d 被信号 %d (%s) 终止\n", 
                   pid, WTERMSIG(status), strsignal(WTERMSIG(status)));
            last_termination = strsignal(WTERMSIG(status));
        }
        fflush(stdout); // 确保输出立即显示
    }
}

// 实现命令行提示符,解析用户输入
char* prompt() {
    static char buf[1024];
    printf("myshell> ");
    fflush(stdout);

    // 读取用户输入
    if (fgets(buf, sizeof(buf), stdin) == NULL) {
        printf("\n");
        exit(EXIT_SUCCESS);
    }

    // 移除换行符
    buf[strcspn(buf, "\n")] = '\0';
    if (strlen(buf) == 0) {
        return NULL;
    }

    // 解析命令到argv数组
    i = 0;
    char *token = strtok(buf, " ");
    while (token != NULL) {
        argv[i++] = token;
        token = strtok(NULL, " ");
    }
    argv[i] = NULL;

    // 判断是否为后台进程(命令结尾带&)
    isBackground = 0;
    if (i > 0 && strcmp(argv[i-1], "&") == 0) {
        isBackground = 1;
        argv[i-1] = NULL; // 移除&参数
    }
    isForeGround = !isBackground;

    return buf;
}

int main() {
    // 注册SIGCHLD信号处理函数(用sigaction更可靠,避免信号丢失)
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = handle_sigchld;
    sa.sa_flags = SA_RESTART; // 重启被信号中断的系统调用(如read)
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction失败");
        exit(EXIT_FAILURE);
    }

    while (1) {
        char* command = prompt();
        if (command == NULL) {
            continue;
        }

        int childExitMethod;
        pid_t spawnPID = fork();

        switch (spawnPID) {
            case -1:
                perror("fork失败");
                last_exit_status = 1;
                break;

            case 0:
                // 子进程执行命令
                execvp(argv[0], argv);
                // 如果execvp返回,说明执行失败
                perror("execvp失败");
                last_exit_status = 1;
                exit(EXIT_FAILURE);

            default:
                if (isForeGround) {
                    // 前台进程:阻塞等待子进程结束
                    waitpid(spawnPID, &childExitMethod, 0);
                    if (WIFEXITED(childExitMethod)) {
                        last_exit_status = WEXITSTATUS(childExitMethod);
                        if (last_exit_status != 0) {
                            printf("命令执行失败,状态码: %d\n", last_exit_status);
                        }
                    } else if (WIFSIGNALED(childExitMethod)) {
                        printf("前台进程被信号 %d (%s) 终止\n", 
                               WTERMSIG(childExitMethod), strsignal(WTERMSIG(childExitMethod)));
                        last_termination = strsignal(WTERMSIG(childExitMethod));
                    }
                } else if (isBackground) {
                    // 后台进程:添加到列表,立即返回命令行控制权
                    addJob(spawnPID);
                    printf("[后台进程已启动] PID: %d\n", spawnPID);
                }
                break;
        }
    }

    return 0;
}

代码说明

  • 信号处理函数:handle_sigchld会在子进程终止时被触发,通过waitpid(-1, &status, WNOHANG)非阻塞地获取所有已终止子进程的状态,同时从后台进程列表中移除对应PID,避免内存泄漏。
  • 前台/后台判断:解析用户输入时,若命令结尾带&则标记为后台进程,父进程不阻塞,直接返回循环等待下一条命令。
  • 错误处理:修复了原代码中fork错误分支缺少break的问题,添加了execvp失败后的错误提示,确保异常情况可追踪。

内容的提问来源于stack exchange,提问作者Ahmed Zaidan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:44:55