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

使用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时文件描述符会自发变更,无法使用;
  • 查阅过相关资料,但子进程始终无法启动。

问题原因分析

你的代码存在两个核心问题导致子进程崩溃退出:

  1. 栈地址参数错误
    Linux栈是向下生长的,clone3要求stack参数传入栈的顶端高地址,但你传入的是栈的底部低地址。子进程启动时从错误地址执行,直接触发崩溃。

  2. 共享内存下的栈释放错误
    启用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 23:23:12