Skip to content

Commit 963ccde

Browse files
authored
fix(process): fix wait semantics for child processes in thread groups (#1427)
* fix(process): fix wait semantics for child processes in thread groups Signed-off-by: longjin <[email protected]> * ci: enable unshare_test Signed-off-by: longjin <[email protected]> --------- Signed-off-by: longjin <[email protected]>
1 parent 2d48f12 commit 963ccde

File tree

5 files changed

+107
-18
lines changed

5 files changed

+107
-18
lines changed

kernel/src/process/exit.rs

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,47 @@ pub fn kernel_waitid(
169169
Ok(0)
170170
}
171171

172+
/// 检查子进程是否可以被当前线程等待
173+
///
174+
/// 根据 Linux wait 语义:
175+
/// - 默认情况下,线程组中的任何线程都可以等待同一线程组中任何线程 fork 的子进程
176+
/// - 如果指定了 __WNOTHREAD,则只能等待当前线程自己创建的子进程
177+
///
178+
/// # 参数
179+
/// - `child_pcb`: 要检查的子进程
180+
/// - `options`: 等待选项
181+
///
182+
/// # 返回值
183+
/// 返回 true 如果当前线程可以等待该子进程
184+
fn is_eligible_child(child_pcb: &Arc<ProcessControlBlock>, options: WaitOption) -> bool {
185+
let current = ProcessManager::current_pcb();
186+
let current_tgid = current.tgid;
187+
188+
// 获取子进程的 real_parent
189+
let child_parent = match child_pcb.real_parent_pcb() {
190+
Some(p) => p,
191+
None => return false,
192+
};
193+
194+
if options.contains(WaitOption::WNOTHREAD) {
195+
// 带 __WNOTHREAD:只能等待当前线程自己创建的子进程
196+
// 检查子进程的 real_parent 是否就是当前线程
197+
Arc::ptr_eq(&child_parent, &current)
198+
} else {
199+
// 默认情况:线程组中的任何线程都可以等待同一线程组中任何线程创建的子进程
200+
// 检查子进程的 real_parent 的 tgid 是否与当前线程的 tgid 相同
201+
child_parent.tgid == current_tgid
202+
}
203+
}
204+
205+
/// 获取当前线程组 leader 的 PCB
206+
///
207+
/// 用于在 wait 时遍历整个线程组的 children
208+
fn get_thread_group_leader(pcb: &Arc<ProcessControlBlock>) -> Arc<ProcessControlBlock> {
209+
let ti = pcb.thread.read_irqsave();
210+
ti.group_leader().unwrap_or_else(|| pcb.clone())
211+
}
212+
172213
/// 参考 https://code.dragonos.org.cn/xref/linux-6.1.9/kernel/exit.c#1573
173214
fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
174215
let mut retval: Result<usize, SystemError> = Ok(0);
@@ -207,11 +248,13 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
207248

208249
let child_pcb = pid.pid_task(PidType::PID).ok_or(SystemError::ECHILD)?;
209250

210-
let parent = ProcessManager::current_pcb();
251+
let current = ProcessManager::current_pcb();
211252

212-
// 检查是否是当前进程的子进程,否则返回ECHILD
213-
let is_child = parent.children.read().contains(&child_pcb.raw_pid());
214-
if !is_child {
253+
// 检查子进程是否可以被当前线程等待
254+
// 根据 Linux 语义:
255+
// - 默认情况下,线程组中的任何线程都可以等待同一线程组中任何线程 fork 的子进程
256+
// - 如果指定了 __WNOTHREAD,则只能等待当前线程自己创建的子进程
257+
if !is_eligible_child(&child_pcb, kwo.options) {
215258
return Err(SystemError::ECHILD);
216259
}
217260

@@ -220,6 +263,13 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
220263
return Err(SystemError::ECHILD);
221264
}
222265

266+
// 获取用于等待的 PCB(线程组 leader 或当前线程,取决于 WNOTHREAD)
267+
let parent = if kwo.options.contains(WaitOption::WNOTHREAD) {
268+
current.clone()
269+
} else {
270+
get_thread_group_leader(&current)
271+
};
272+
223273
// 等待指定子进程:睡眠在父进程自己的 wait_queue 上
224274
// 子进程退出时会发送信号并唤醒父进程的 wait_queue
225275
loop {
@@ -265,8 +315,16 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
265315
}
266316
}
267317
PidConverter::All => {
268-
// 等待任意子进程:使用父进程的 wait_queue,避免丢唤醒
269-
let parent = ProcessManager::current_pcb();
318+
// 等待任意子进程:使用线程组 leader 的 wait_queue 和 children 列表
319+
// 这样线程组中的任何线程都可以等待同一线程组中任何线程 fork 的子进程
320+
let current = ProcessManager::current_pcb();
321+
let parent = if kwo.options.contains(WaitOption::WNOTHREAD) {
322+
// 带 __WNOTHREAD:只使用当前线程的 children
323+
current.clone()
324+
} else {
325+
// 默认:使用线程组 leader 的 children
326+
get_thread_group_leader(&current)
327+
};
270328
loop {
271329
// 注册等待
272330
let _ = parent.wait_queue.prepare_to_wait_event(true);
@@ -285,6 +343,11 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
285343
let pcb =
286344
ProcessManager::find_task_by_vpid(*pid).ok_or(SystemError::ECHILD)?;
287345

346+
// 检查子进程是否可以被当前线程等待(考虑 __WNOTHREAD)
347+
if !is_eligible_child(&pcb, kwo.options) {
348+
continue;
349+
}
350+
288351
// 检查子进程是否匹配等待选项(__WALL/__WCLONE)
289352
if !child_matches_wait_options(&pcb, kwo.options) {
290353
continue;
@@ -387,8 +450,13 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
387450
PidConverter::Pgid(Some(pgid)) => {
388451
// 修复:根据 Linux waitpid 语义,waitpid(-pgid, ...) 只等待调用者的
389452
// **子进程**中属于指定进程组的进程,而不是进程组中的所有进程。
390-
// 因此,这里遍历父进程的 children 列表,检查每个子进程是否属于目标进程组。
391-
let parent = ProcessManager::current_pcb();
453+
// 因此,这里遍历线程组 leader 的 children 列表,检查每个子进程是否属于目标进程组。
454+
let current = ProcessManager::current_pcb();
455+
let parent = if kwo.options.contains(WaitOption::WNOTHREAD) {
456+
current.clone()
457+
} else {
458+
get_thread_group_leader(&current)
459+
};
392460
loop {
393461
// 注册等待
394462
let _ = parent.wait_queue.prepare_to_wait_event(true);
@@ -416,6 +484,11 @@ fn do_wait(kwo: &mut KernelWaitOption) -> Result<usize, SystemError> {
416484
}
417485
};
418486

487+
// 检查子进程是否可以被当前线程等待(考虑 __WNOTHREAD)
488+
if !is_eligible_child(&pcb, kwo.options) {
489+
continue;
490+
}
491+
419492
// 检查子进程是否属于目标进程组
420493
// 注意:即使进程已退出并从进程组的 tasks 列表中 detach,
421494
// task_pgrp() 仍然返回它之前所属的进程组

kernel/src/process/fork.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -671,13 +671,17 @@ impl ProcessManager {
671671
}
672672

673673
// 将当前pcb加入父进程的子进程哈希表中
674+
// 注意:根据 Linux 语义,子进程应该被添加到 **线程组 leader** 的 children 列表中
675+
// 而不是创建它的线程的 children 列表中。这样线程组中的任何线程都可以 wait 这个子进程。
676+
// real_parent_pcb 存储实际创建子进程的线程,用于 __WNOTHREAD 选项的判断。
674677
if pcb.raw_pid() > RawPid(1) {
675-
if let Some(ppcb_arc) = pcb.parent_pcb.read_irqsave().upgrade() {
676-
let mut children = ppcb_arc.children.write_irqsave();
677-
children.push(pcb.raw_pid());
678-
} else {
679-
panic!("parent pcb is None");
680-
}
678+
// 获取线程组 leader
679+
let thread_group_leader = {
680+
let ti = current_pcb.thread.read_irqsave();
681+
ti.group_leader().unwrap_or_else(|| current_pcb.clone())
682+
};
683+
let mut children = thread_group_leader.children.write_irqsave();
684+
children.push(pcb.raw_pid());
681685
}
682686

683687
if pcb.raw_pid() > RawPid(0) {

kernel/src/process/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,21 @@ impl ProcessManager {
410410
parent_pcb
411411
.wait_queue
412412
.wakeup_all(Some(ProcessState::Blocked(true)));
413+
414+
// 根据 Linux wait 语义,线程组中的任何线程都可以等待同一线程组中任何线程创建的子进程。
415+
// 由于子进程被添加到线程组 leader 的 children 列表中,
416+
// 因此还需要唤醒线程组 leader 的 wait_queue(如果 leader 不是 parent_pcb 本身)。
417+
let parent_group_leader = {
418+
let ti = parent_pcb.thread.read_irqsave();
419+
ti.group_leader()
420+
};
421+
if let Some(leader) = parent_group_leader {
422+
if !Arc::ptr_eq(&leader, &parent_pcb) {
423+
leader
424+
.wait_queue
425+
.wakeup_all(Some(ProcessState::Blocked(true)));
426+
}
427+
}
413428
}
414429
// todo: 这里还需要根据线程组的信息,决定信号的发送
415430
}

user/apps/tests/syscall/gvisor/blocklists/wait_test

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ WaitTest.WaitidRusage
44
WaitTest.TraceeWALL
55
# 卡死
66
Waiters/WaitAnyChildTest.WaitedChildRusage/*
7-
# 缺少 SYS_SCHED_GETPARAM SYS_SCHED_GETSCHEDULER
8-
Waiters/WaitSpecificChildTest.SiblingChildren/*
97
Waiters/WaitAnyChildTest.IgnoredChildRusage/*
10-
# 缺少 SYS_SCHED_GETPARAM SYS_SCHED_GETSCHEDULER
11-
Waiters/WaitSpecificChildTest.SiblingChildrenWNOTHREAD/*
128

139
# 缺少 /bin/true
1410
Waiters/WaitSpecificChildTest.AfterChildExecve/*

user/apps/tests/syscall/gvisor/whitelist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ sigtimedwait_test
6767
# 其他测试
6868
itimer_test
6969
pipe_test
70+
unshare_test

0 commit comments

Comments
 (0)