使用syscall(SYS_clone3)+CLONE_VM时子进程无法启动的问题排查
子进程使用CLONE_VM标志后无法启动的问题
我需要让调用进程与子进程处于同一内存空间,因此使用了CLONE_VM标志,但子进程无法启动,推测是栈内存分配存在问题,恳请解释原因。
相关代码
#define _DEFAULT_SOURCE /* syscall() */ #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 /* getrlimit() */ #include <sched.h> /* CLONE_* constants */ #include <linux/sched.h> /* struct clone_args */ #include <sys/syscall.h> /* SYS_* constants */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> /* uintptr_t */ #include <err.h> /* err() */ #include <sys/resource.h> #include <sys/mman.h> #include <signal.h> void spawn(void) { struct rlimit rlim; struct clone_args cl_args = {0}; uint64_t stack_size; void *stackBot; void *stackTop; pid_t pid; if (getrlimit(RLIMIT_STACK, &rlim) == -1) err(EXIT_FAILURE, "getrlimit"); stack_size = rlim.rlim_cur; stackBot = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_STACK, -1, 0); if (stackBot == MAP_FAILED) err(EXIT_FAILURE, "mmap"); long count = stack_size / sizeof(uint64_t); stackTop = (uint64_t *)stackBot + count - 1; printf("stack_size: %lu\n", stack_size); printf("stack_size/2: %lu\n", stack_size / 2); printf("stack_size/4: %lu\n", stack_size / 4); printf("stack_size/8: %lu\n", stack_size / 8); printf("stack_size/16: %lu\n", stack_size / 16); printf("stack_size/4096: %lu\n", stack_size / 4096); /* PAGESIZE */ printf("stackBotAddr: %p\n", (void *)stackBot); printf("stackTopAddr: %p\n", (void *)stackTop); printf("stackBotVal: %lu\n", *(uint64_t *)(uintptr_t)stackBot); printf("stackTopVal: %lu\n", *(uint64_t *)(uintptr_t)stackTop); printf("stackTopAddr-stackBotAddr: %lu\n", (void *)stackTop - stackBot); printf("stackBotCast: %lu\n", (uint64_t)(uintptr_t)stackBot); printf("stackTopCast: %lu\n", (uint64_t)(uintptr_t)stackTop); cl_args.flags = CLONE_FILES | CLONE_IO | CLONE_VM; cl_args.exit_signal = SIGCHLD; cl_args.stack = (uint64_t)(uintptr_t)stackBot; cl_args.stack_size = (void *)stackTop - stackBot; pid = syscall(SYS_clone3, &cl_args, sizeof(cl_args)); switch(pid) { case -1: munmap(stackBot, stack_size); err(EXIT_FAILURE, "syscall"); case 0: /* Child */ printf("Hello from child. Child's pid: %d\n", getpid()); munmap(stackBot, stack_size); break; default: /* Parent */ printf("Hello from parent. Parent's pid: %d\n", getpid()); break; } } int main(int argc, char *argv[]) { spawn(); printf("Before last while\n"); while(1) { } return EXIT_SUCCESS; }
调试信息
使用gdb调试得到:
[Detaching after fork from child process 16095]
通过gdb attach进程ID得到:
Attaching to process 16095 warning: process 16095 is a zombie - the process has already terminated ptrace: Operation not permitted.
已尝试的方案
- 用过
fork():父子进程内存空间独立,不符合需求,放弃; - 用过
clone():带CLONE_VM时文件描述符会自发变更,无法使用; - 查阅过相关资料,但子进程始终无法启动。
问题原因分析
你的代码存在两个核心问题导致子进程崩溃退出:
栈地址参数错误
Linux栈是向下生长的,clone3要求stack参数传入栈的顶端高地址,但你传入的是栈的底部低地址。子进程启动时从错误地址执行,直接触发崩溃。共享内存下的栈释放错误
启用CLONE_VM后,父子进程共享内存空间,子进程执行munmap(stackBot, stack_size)会同时释放父进程的栈内存,导致父进程后续操作异常。
修复方案
1. 修正栈地址与大小参数
将clone_args的栈相关参数修改为:
// 传入栈顶端地址(栈空间的高地址) cl_args.stack = (uint64_t)(uintptr_t)((char *)stackBot + stack_size); // 栈大小直接使用完整的stack_size cl_args.stack_size = stack_size;
2. 移除子进程的栈释放操作
删除子进程分支里的munmap(stackBot, stack_size),由父进程在合适时机(比如子进程退出后)统一释放栈内存。
3. 可选:优化栈映射标志
手动创建的子进程栈不需要MAP_GROWSDOWN和MAP_STACK,这两个标志是给主线程栈设计的,改用MAP_PRIVATE | MAP_ANONYMOUS即可。
修改后的核心代码片段:
// 栈映射优化 stackBot = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // ... 其他代码不变 ... cl_args.flags = CLONE_FILES | CLONE_IO | CLONE_VM; cl_args.exit_signal = SIGCHLD; cl_args.stack = (uint64_t)(uintptr_t)((char *)stackBot + stack_size); cl_args.stack_size = stack_size; pid = syscall(SYS_clone3, &cl_args, sizeof(cl_args)); switch(pid) { case -1: munmap(stackBot, stack_size); err(EXIT_FAILURE, "syscall"); case 0: /* Child */ printf("Hello from child. Child's pid: %d\n", getpid()); // 移除子进程的munmap调用 break; default: /* Parent */ printf("Hello from parent. Parent's pid: %d\n", getpid()); // 等待子进程退出后释放栈内存 waitpid(pid, NULL, 0); munmap(stackBot, stack_size); break; }
额外说明
- 子进程退出后,父进程必须调用
waitpid或wait回收僵尸进程,避免系统资源泄漏; CLONE_VM会让父子进程共享所有内存,包括全局变量、堆内存等,操作时需注意同步问题。
内容的提问来源于stack exchange,提问作者0m3
相关产品推荐
相关产品推荐

