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

使用clone()创建带UTS/PID命名空间的子进程,为何wait()不等待其终止?

问题原因分析

你的问题核心出在两个关键点:

  1. clone回调函数返回类型错误:clone系统调用要求回调函数类型为int (*)(void *),但你的child_container是void类型。这会导致子进程执行完函数后返回值未定义,可能触发异常退出,使得父进程的wait()提前捕获到退出信号,不管system()是否执行完毕。

  2. PID命名空间init进程的特性:使用CLONE_NEWPID时,子进程成为新PID命名空间的init进程(PID 1)。init进程的特殊行为是:如果它退出,内核会向整个命名空间内所有进程发送SIGKILL信号,直接终止它们。若回调函数返回类型错误导致init进程意外退出,就会中断system()启动的交互式进程(如/bin/sh)。

另外,使用wait()而非waitpid()可能引入不确定性,虽非核心问题,但建议等待特定子进程。


修复方案

1. 修正clone回调函数返回类型

将child_container改为返回int类型,符合clone的要求,确保子进程正常退出。

2. 明确设置clone退出信号

确保父进程能收到子进程退出的SIGCHLD信号(默认会发送,但明确设置更稳妥)。

3. 使用waitpid等待特定子进程

避免wait()等待任意子进程的不确定性。

4. 为PID 1进程添加SIGCHLD处理(可选但推荐)

作为新命名空间的init进程,需处理SIGCHLD信号清理僵尸进程,避免资源泄漏。


修复后的完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sched.h>
#include <unistd.h>

#define BUF_SIZE 0x3ff
#define STACK_SIZE 8192

static char child_stack[STACK_SIZE];
char *cmd;

char *concat_args(char **args, int arglen);
int child_container(void *arg);
void parse_command(char *cmd, char **args, int argc);
void run(char **args, int arglen);
void sigchld_handler(int sig);
void error(const char *msg);

// 处理SIGCHLD信号,清理僵尸进程
void sigchld_handler(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

char *concat_args(char **args, int arglen) {
    char *cmd = (char *)malloc((BUF_SIZE + 1) * sizeof(char));
    if (!cmd) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    memset(cmd, 0, BUF_SIZE + 1);
    strncat(cmd, args[0], BUF_SIZE);
    for (int i = 1; i < arglen; i++) {
        strncat(cmd, " ", BUF_SIZE);
        strncat(cmd, args[i], BUF_SIZE);
    }
    return cmd;
}

void run(char **args, int arglen) {
    printf("[*] Starting PID: %d\n", getpid());

    cmd = concat_args(args, arglen);

    // 创建新进程,指定UTS和PID命名空间
    pid_t child_pid = clone(child_container, child_stack + STACK_SIZE,
                            CLONE_NEWUTS | CLONE_NEWPID, NULL);
    if (child_pid == -1) {
        perror("clone");
        free(cmd);
        exit(EXIT_FAILURE);
    }

    // 等待特定子进程退出
    int status, e_code;
    e_code = waitpid(child_pid, &status, 0);
    if (e_code == -1) {
        perror("waitpid");
    } else {
        printf("[*] Wait returned PID: %d\n", e_code);
        if (WIFEXITED(status)) {
            printf("[*] Child exited with code: %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("[*] Child killed by signal: %d\n", WTERMSIG(status));
        }
    }

    free(cmd);
    return;
}

// 修正返回类型为int,符合clone要求
int child_container(void *arg) {
    // 设置SIGCHLD信号处理
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigchld_handler;
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    printf("This is the container!\n");
    printf("[*] Child PID: %d\n", getpid());
    system(cmd);
    printf("test\n");

    return 0; // 正常退出返回0
}

void parse_command(char *cmd, char **args, int argc) {
    if (!strcmp(cmd, "run")) {
        run(args, argc - 2);
    } else {
        error("No such command!");
    }
}

// 实现error函数
void error(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv) {
    const char *format = "./container run [cmd] [args]";
    if (argc < 3) {
        error("Wrong format!\nThe commands must be formatted in the following way:\t./container run [cmd] [args]");
    }

    char *cmd = argv[1];
    char **args = &argv[2];

    parse_command(cmd, args, argc);
    return 0;
}

额外说明
  • 权限要求:使用CLONE_NEWUTS和CLONE_NEWPID需要CAP_SYS_ADMIN权限,必须以root身份运行程序(如sudo ./container run /bin/sh)。
  • system()的局限性:system()会调用shell解析命令,若想避免shell开销,可直接使用fork()+exec()替代,能更精确控制子进程生命周期。

内容的提问来源于stack exchange,提问作者Marco Balo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 05:46:09