DragonOS/docs/kernel/process_management/de_thread.md

160 lines
7.9 KiB
Markdown
Raw Permalink Normal View History

feat(exec): 实现多线程exec的去线程化功能 (#1682) * feat(exec): 实现多线程exec的去线程化功能 - 在de_thread()中实现线程组清理与PID交换逻辑 - 添加GROUP_EXEC标志防止exec期间创建新线程 - 修改fork、exit、signal处理以支持去线程化语义 Signed-off-by: longjin <longjin@DragonOS.org> * fix(process): 修复多线程exec时父进程提前回收旧leader的问题 - 在de_thread中增加对旧leader的回收逻辑,确保由exec线程负责回收 - 新增reap_blocked_by_group_exec函数,在do_wait和do_waitpid中检查并跳过被exec阻 塞的进程回收 Signed-off-by: longjin <longjin@DragonOS.org> * feat(ipc/sighand): 实现线程组exec的同步机制 - 在SigHand结构体中添加group_exec_wait_queue和group_exec_notify_count字段,用于 线程组exec期间的同步 - 新增wait_group_exec_event_interruptible和wait_group_exec_event_killable方法, 支持可中断和可终止的等待 - 在de_thread函数中使用新的等待机制替代忙等待,提高效率 - 在进程退出时正确处理group_exec_notify_count的递减和唤醒操作 - 修复fork时对GROUP_EXEC和GROUP_EXIT标志的检查,防止在exec期间创建新线程 - 调整进程父子关系处理逻辑,确保wait系统调用在__WNOTHREAD标志下的正确行为 - 在wait_queue模块中新增killable等待模式,支持可终止的信号等待 Signed-off-by: longjin <longjin@DragonOS.org> * docs: 新增de_thread机制文档并清理调试日志 - 新增de_thread机制原理文档,详细说明多线程exec的去线程化流程 - 清理exec.rs和exit.rs中的调试日志输出 Signed-off-by: longjin <longjin@DragonOS.org> * refactor(ipc): 添加 with_group_exec_check 方法以统一处理线程组并发插入 - 在 SigHand 结构体中新增 with_group_exec_check 方法,用于在 GROUP_EXEC/GROUP_EXIT 锁下执行关键区操作,避免并发插入线程组 - 重构 ProcessManager 中的线程组加入逻辑,使用新方法替代原有的手动标志检查,提升代码可维护性 Signed-off-by: longjin <longjin@DragonOS.org> * feat(ipc): 重构线程组exec状态管理 - 新增`start_group_exec`方法,合并设置exec标志与记录执行者操作 - 重构`exchange_tid_and_raw_pids`方法,整合线程ID与原始PID交换逻辑 - 修复`finish_group_exec`调用条件判断逻辑 Signed-off-by: longjin <longjin@DragonOS.org> --------- Signed-off-by: longjin <longjin@DragonOS.org>
2026-01-19 14:00:25 +08:00
# DragonOS 多线程 Exec (De-thread) 机制原理
:::{note}
Author: longjin <longjin@dragonos.org>
:::
## 1. 概述
在 POSIX 标准中,`execve` 系统调用用于执行一个新的程序替换当前进程的镜像。对于多线程程序Thread GroupPOSIX 要求 `exec` 成功后:
1. **PID 保持不变**:进程的 PID在内核中通常指 TGID即 Thread Group ID必须保持不变。
2. **单线程化**:原进程组中的所有其他线程都必须被终止,仅保留执行 `exec` 的那个线程(它将成为新的单线程进程)。
如果执行 `exec` 的是线程组的 Leader主线程事情相对简单它只需杀掉其他线程即可。但如果执行 `exec` 的是一个普通线程(非 Leader情况就变得复杂**它必须“变身”为 Leader接管原 Leader 的 PID并清理掉原 Leader**。这个过程被称为“去线程化”De-thread
本文档详细描述 DragonOS 内核中 `de_thread` 的实现原理、流程及并发控制机制。
## 2. 核心挑战
1. **身份窃取 (Identity Theft)**:执行 `exec` 的非 Leader 线程(以下简称 **Exec-Thread**)必须在内核层面交换身份,使得它在用户态和父进程看来,拥有原 Leader 的 PID。
2. **并发互斥**
- 同一进程组内可能有两个线程同时调用 `exec`,或者一个调 `exec` 一个调 `exit_group`。必须保证只有一个能成功开始。
- 在身份交换的临界区Critical Section进程的状态是不稳定的必须防止父进程通过 `wait` 系统调用看到并回收处于中间状态的旧 Leader。
3. **资源继承与清理**Exec-Thread 需要继承原 Leader 的父子进程关系、子进程列表等,同时负责回收旧 Leader 占用的资源。
## 3. 实现机制
DragonOS 参考了 Linux 内核的设计,但在具体实现上结合了 Rust 的所有权和并发安全特性。
### 3.1 关键数据结构
`SigHand`(信号处理结构,由线程组共享)中,引入了以下字段来管理状态:
- `SignalFlags::GROUP_EXEC`:标志位。表示当前线程组正在进行去线程化操作。
- `group_exec_task`: `Weak<ProcessControlBlock>`。指向正在执行 `exec` 的那个线程。
- `group_exec_wait_queue`: `WaitQueue`。用于等待组内其他线程退出。
- `group_exec_notify_count`: `isize`。需要等待退出的线程计数(用于唤醒等待队列)。
### 3.2 核心流程 (`de_thread`)
`de_thread` 函数位于 `kernel/src/process/exec.rs`,是实现该逻辑的核心。
#### 阶段一:发起与互斥
1. **加锁并互斥**:调用 `sighand.start_group_exec()`。若 `GROUP_EXEC``GROUP_EXIT` 已被设置,则返回 `EAGAIN`,保证与并发 `exec`/`exit_group` 互斥。
2. **设置执行者**:记录当前线程到 `group_exec_task`,并清空 `group_exec_notify_count`
3. **单线程快路径**:若线程组为空(仅自己),直接设置 `exit_signal = SIGCHLD` 并结束去线程化流程。
#### 阶段二:终止兄弟线程
1. **构建清理列表**:遍历线程组,收集所有仍存活且未处于退出路径的线程(包括旧 Leader
2. **发送信号**:向清理列表中的线程逐个发送 `SIGKILL`
3. **设置计数**:将清理列表长度写入 `group_exec_notify_count`,用于线程退出时唤醒等待队列。
#### 阶段三:等待同步
1. **进入等待**:在 `group_exec_wait_queue` 上以 killable 方式睡眠。
2. **唤醒条件**
- 遍历 `group_tasks`,确认除当前线程以外已无存活线程;
- 若当前不是 Leader还需确保旧 Leader 已经进入 `Zombie``Dead` 状态;
- 若收到 fatal signal 或等待被打断,则返回 `EAGAIN`
- *注:其他线程在退出路径(`exit_notify`)中会递减计数并唤醒等待队列;若执行者异常退出,也会在该路径中清理 `GROUP_EXEC` 标志。*
#### 阶段四:身份交换 (Identity Theft)
*仅当 Exec-Thread 不是 Leader 时执行*
1. **PID/TID 交换**:调用 `ProcessManager::exchange_tid_and_raw_pids`
- 在全局进程表中交换两者的 PID 映射关系,并交换 PCB 内部的 TID/PID 字段。
- **结果**Exec-Thread 获得原 TGIDOld-Leader 获得临时 PID。
2. **信号语义调整**
- 新 LeaderExec-Thread`exit_signal` 设为 `SIGCHLD`
- Old-Leader 的 `exit_signal` 设为 `INVALID`,避免被当作普通子进程处理。
3. **结构调整**:将 Exec-Thread 设为新的 `group_leader`,并清空两侧 `group_tasks`(去线程化后仅剩一个线程)。
4. **关系继承**
- **子进程**:将 Old-Leader 的 `children` 列表整体迁移到 Exec-Thread 下;
- **父进程**Exec-Thread 继承 Old-Leader 的 `parent`/`real_parent`,并更新 `fork_parent` 为自身。
#### 阶段五:资源清理
1. **回收旧 Leader**:若 Old-Leader 已是 Zombie 或 DeadExec-Thread 直接将其标记为 Dead 并释放 PID 资源。
2. **完成**:无论成功/失败,最终都会清除 `GROUP_EXEC` 标志并唤醒等待者。
### 3.3 并发保护:防止父进程误回收
这是实现中最微妙的部分。
**问题场景**
在阶段三和阶段四之间Old-Leader 已经收到 SIGKILL 并退出变成 Zombie 状态。此时,如果父进程调用 `wait()`,它可能会发现 Old-Leader (PID=TGID) 是 Zombie于是将其回收。
如果父进程在 Exec-Thread 完成 PID 交换**之前**回收了 Old-Leader那么 Exec-Thread 就无法窃取该 PID因为该 PID 对应的进程已经消失了),或者导致逻辑混乱。
**解决方案**
`kernel/src/process/exit.rs``do_wait` 逻辑中,增加了一个检查函数 `reap_blocked_by_group_exec`
```rust
fn reap_blocked_by_group_exec(child_pcb: &Arc<ProcessControlBlock>) -> bool {
// 如果子进程是 Leader且标记了 GROUP_EXEC
// 且执行 exec 的不是它自己,那么说明它正在等待被“篡位”,父进程不能回收它。
if !child_pcb.is_thread_group_leader() {
return false;
}
if !child_pcb.sighand().flags_contains(SignalFlags::GROUP_EXEC) {
return false;
}
let exec_task = child_pcb.sighand().group_exec_task();
exec_task
.as_ref()
.map(|t| !Arc::ptr_eq(t, child_pcb))
.unwrap_or(true)
}
```
这个检查确保了即使 Old-Leader 变成 Zombie只要 `GROUP_EXEC` 标志还在且 `exec_task` 不是它,父进程就无法回收它。这为 Exec-Thread 安全地进行 PID/TID 交换提供了保护伞。
## 4. 流程图
```mermaid
sequenceDiagram
participant P as Parent Process
participant OL as Old Leader (PID=100)
participant ET as Exec Thread (PID=101)
participant S as Sibling (PID=102)
Note over P, S: 初始状态TGID=100. Exec Thread 想要执行 exec
ET->>ET: start_group_exec()<br/>Set GROUP_EXEC flag
ET->>S: Send SIGKILL
ET->>OL: Send SIGKILL
ET->>ET: Wait for siblings & leader death
S->>S: Exit -> Zombie
S-->>ET: Notify (count--)
OL->>OL: Exit -> Zombie
OL-->>ET: Notify (count--)
Note right of P: Parent 调用 wait(PID=100)
P->>OL: Check PID 100 (Zombie)
P->>P: reap_blocked_by_group_exec?<br/>Yes! (Blocked by GROUP_EXEC)
Note right of P: Parent 无法回收 Old Leader继续等待
ET->>ET: Woken up (All dead)
Note over ET, OL: 开始身份交换 (Identity Theft)
ET->>OL: exchange_tid_and_raw_pids()
Note over ET, OL: ET 变成 PID=100<br/>OL 变成 PID=101
ET->>ET: Inherit Parent/Children
ET->>OL: Release/Reap Old Leader
ET->>OL: OL 彻底销毁
ET->>ET: Finish GROUP_EXEC
Note over ET: exec 成功ET 以 PID=100 运行新程序
```
## 5. 参考资料
- **Linux Kernel Source**: `fs/exec.c` (`de_thread` function)
- **DragonOS Source**:
- `kernel/src/process/exec.rs`
- `kernel/src/process/exit.rs`
- `kernel/src/ipc/sighand.rs`