Skip to content
Merged
20 changes: 15 additions & 5 deletions os/StarryOS/kernel/src/syscall/task/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ impl CloneArgs {
None
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Linux 语义偏差提醒

在 Linux 内核中,CLONE_VFORK 无论 stack 是否为 0 都会阻塞父进程。Linux 的 kernel_clone() 在设置了 CLONE_VFORK 时总会创建 completion 并调用 wait_for_vfork_done(),与 stack 参数无关。

当前修改仅在 stack == 0 时阻塞父进程,这意味着 CLONE_VM|CLONE_VFORK|SIGCHLD + 独立 child stack 的组合不再阻塞父进程。这是对 Linux CLONE_VFORK 语义的有意偏离,建议在代码注释中明确标注这一点,例如:

// NOTE: deviates from Linux — Linux's CLONE_VFORK always blocks the parent
// regardless of the stack parameter. StarryOS only blocks when the child
// reuses the parent's user stack (stack == 0).
let needs_vfork_block = flags.contains(CloneFlags::VFORK) && stack == 0;

这样可以帮助后续维护者理解这是一个有意的设计决策而非疏忽。

// NOTE: This intentionally differs from Linux: Linux blocks the parent
// for every CLONE_VFORK clone regardless of the stack argument.
// StarryOS only blocks for bare vfork(), where the child reuses the
// parent's user stack. CLONE_VM | CLONE_VFORK with a private child
// stack is allowed to synchronize in user space, as posix_spawn-style
// runtimes do.
let needs_vfork_block = flags.contains(CloneFlags::VFORK) && stack == 0;

let mut new_uctx = *uctx;
new_uctx.prepare_clone_child_return_state();
if stack != 0 {
Expand Down Expand Up @@ -283,10 +291,12 @@ impl CloneArgs {
}
*new_task.task_ext_mut() = Some(AxTaskExt::from_impl(thr));

// CLONE_VFORK: wire a shared `PollSet` to the child so it can wake us
// (PollSet, not WaitQueue, so the parent's wait is interruptible by
// `task.interrupt()` — see `wait_vfork_done`).
if flags.contains(CloneFlags::VFORK) {
// Bare vfork(2) reuses the parent's user stack, so the parent must
// sleep until the child execs or exits. posix_spawn-style clones pass
// a private child stack with CLONE_VM|CLONE_VFORK and synchronize in
// user space; blocking here can deadlock that handshake. Use PollSet
// so the parent's wait remains interruptible by task.interrupt().
if needs_vfork_block {
let poll = Arc::new(axpoll::PollSet::new());
new_proc_data.set_vfork_done(poll);
}
Expand All @@ -302,7 +312,7 @@ impl CloneArgs {
crate::kcov::on_fork(tid);

// Block the parent until the child exec's or exits.
if flags.contains(CloneFlags::VFORK) {
if needs_vfork_block {
new_proc_data.wait_vfork_done();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#define _GNU_SOURCE
#define _DEFAULT_SOURCE
#define _POSIX_C_SOURCE 199309L
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
Expand Down Expand Up @@ -34,6 +36,12 @@ static inline pid_t raw_vfork(void) {
#define do_vfork() vfork()
#endif

static int clone_child_sleep(void *arg) {
(void)arg;
sleep(2);
_exit(0);
}

/* Test 1: Memory Sniff - Check if vfork shares address space */
int test_vfork_memory_sniff(void) {
volatile int stack_var = 0;
Expand Down Expand Up @@ -92,15 +100,49 @@ int test_vfork_execution_order(void) {
return 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

测试与 Linux 行为对比

在标准 Linux 上运行此测试会失败,因为 Linux 的 clone(CLONE_VM|CLONE_VFORK|SIGCHLD, child_stack, ...) 会阻塞父进程直到子进程调用 _exit(0)(约 2 秒),使得 elapsed_ms 约为 2000,不满足 < 1000 的断言。

建议在测试注释中明确说明这是一个 StarryOS 特有行为测试,而非 Linux 兼容性测试:

/* NOTE: This tests StarryOS-specific behavior. On Linux, CLONE_VFORK always
   blocks the parent regardless of the stack parameter, so this test would
   fail there. StarryOS only blocks when stack == 0 (bare vfork). */
int test_vfork_clone_child_stack_nonblocking(void) {

此外,CI 的 "Test starry self-hosted board orangepi-5-plus / run_host" 检查失败了,建议确认该失败是否与本次修改相关。

}

/* Test 3: StarryOS-specific behavior. Linux blocks the parent for every
CLONE_VFORK clone, regardless of the stack argument, so this case would
fail there. StarryOS only blocks for bare vfork() with stack == 0; a
posix_spawn-style clone with a private child stack synchronizes in user
space instead. */
int test_vfork_clone_child_stack_nonblocking(void) {
static char child_stack[16384];
char *stack_top = child_stack + sizeof(child_stack);
struct timespec start, end;

clock_gettime(CLOCK_MONOTONIC, &start);
int pid = clone(clone_child_sleep, stack_top, CLONE_VM | CLONE_VFORK | SIGCHLD, NULL);
if (pid < 0) {
perror("clone(CLONE_VM|CLONE_VFORK) failed");
return -1;
}

clock_gettime(CLOCK_MONOTONIC, &end);

int status = 0;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
return 0;
}

long elapsed_ms = (end.tv_sec - start.tv_sec) * 1000 +
(end.tv_nsec - start.tv_nsec) / 1000000;

return (elapsed_ms < 1000) ? 1 : 0;
}

int main(void) {
int vfork_mem_pass = 0, vfork_exec_pass = 0;
int vfork_mem_pass = 0, vfork_exec_pass = 0, clone_stack_pass = 0;

/* Test 1: vfork memory sharing */
vfork_mem_pass = test_vfork_memory_sniff();

/* Test 2: vfork execution blocking */
vfork_exec_pass = test_vfork_execution_order();

/* Test 3: CLONE_VFORK with a private child stack does not block parent */
clone_stack_pass = test_vfork_clone_child_stack_nonblocking();

/* Report results */
if (vfork_mem_pass > 0) {
printf("VFORK: PASS (Memory shared)\n");
Expand All @@ -114,8 +156,14 @@ int main(void) {
printf("VFORK: FAIL (Parent NOT blocked)\n");
}

/* Return success only if both vfork tests pass */
if (vfork_mem_pass > 0 && vfork_exec_pass > 0) {
if (clone_stack_pass > 0) {
printf("VFORK: PASS (Child stack clone did not block parent)\n");
} else {
printf("VFORK: FAIL (Child stack clone blocked parent)\n");
}

/* Return success only if all vfork-related tests pass */
if (vfork_mem_pass > 0 && vfork_exec_pass > 0 && clone_stack_pass > 0) {
printf("VFORK TEST: ALL TESTS PASSED\n");
return 0;
} else {
Expand Down
Loading