使用clone()创建带UTS/PID命名空间的子进程,为何wait()不等待其终止?
问题原因分析
你的问题核心出在两个关键点:
clone回调函数返回类型错误:clone系统调用要求回调函数类型为
int (*)(void *),但你的child_container是void类型。这会导致子进程执行完函数后返回值未定义,可能触发异常退出,使得父进程的wait()提前捕获到退出信号,不管system()是否执行完毕。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
相关产品推荐
相关产品推荐

