cg-tg-rcore-tutorial-ch8 0.0.1

A reproducible Rust learning crate for the tg-rcore-tutorial chapter 8 kernel experiment, including threading, synchronization, framebuffer I/O, and a user-mode Doom demo path.
## 一、实验要求分析

- 本次实验的核心任务是在 `tg-rcore-tutorial-ch8` 中补全死锁检测功能。
- 具体要求是新增并实现 `enable_deadlock_detect` 系统调用,在当前进程内按需开启或关闭死锁检测。
- 开启后,`mutex_lock``semaphore_down` 在检测到死锁风险时不能继续阻塞,而应直接返回 `-0xDEAD`- 同时需要保证本章原有的线程、互斥锁、信号量、条件变量等基础功能不被破坏,继续通过 `base``exercise` 两套测试。

## 二、代码实现逻辑

- 修改模块:
  - `src/process.rs`
  - `src/main.rs`

- `src/process.rs` 的修改思路:
  -`Process` 新增 `deadlock: DeadlockState` 字段,把死锁检测需要的资源状态集中放在进程层维护。
  - `DeadlockState` 中分别维护两套状态:
    - semaphore 检测状态:`semaphore_total``semaphore_alloc``semaphore_need`
    - mutex 检测状态:`mutex_owner``mutex_wait`
  - 这样做的原因是:本章里同步原语属于进程共享资源,线程只是执行流;死锁检测也应站在“进程内所有线程共享资源”的视角上做。

- `src/main.rs` 的修改思路:
  -`semaphore_create` / `mutex_create` 时注册新的资源类型。
  -`semaphore_down` 路径上:
    - 先读取当前进程的活跃线程集合;
    - 若已经开启死锁检测,则基于 `Available / Allocation / Need` 做一次安全性检查;
    - 若不安全,直接返回 `-0xDEAD`    - 若安全,再进入原本的 `down` 逻辑;成功则更新分配矩阵,阻塞则更新需求矩阵。
  -`semaphore_up` 路径上:
    - 除了唤醒线程,还要把“资源从当前线程转交给被唤醒线程”的状态同步到检测矩阵里。
  -`mutex_lock` 路径上:
    - 使用等待图做环检测;
    - 一旦沿“当前想拿的锁 -> 持锁线程 -> 该线程正在等待的锁 -> 下一持锁线程”形成回路,则返回 `-0xDEAD`  -`mutex_unlock` 路径上:
    - 和内核已有语义保持一致,若唤醒了等待线程,则锁所有权直接转交给被唤醒线程。
  -`condvar_wait` 路径上:
    - 由于其内部会发生一次“解锁/再尝试加锁”,因此也同步更新 mutex 的持有关系,避免后续死锁检测读到过期状态。
  -`enable_deadlock_detect` 路径上:
    - 只做参数校验和开关切换;
    - 资源分配状态始终维护,这样即使进程运行中途再开启检测,也能基于当前真实资源状态工作。

- 核心代码片段 1:在进程层维护 semaphore 的检测状态

```rust
#[derive(Default)]
pub struct DeadlockState {
    pub enabled: bool,
    semaphore_total: Vec<usize>,
    semaphore_alloc: BTreeMap<ThreadId, Vec<usize>>,
    semaphore_need: BTreeMap<ThreadId, Vec<usize>>,
    mutex_owner: Vec<Option<ThreadId>>,
    mutex_wait: BTreeMap<ThreadId, usize>,
}
```

- 关键说明:
  - `semaphore_total[j]` 表示第 `j` 类资源总量。
  - `semaphore_alloc[i][j]` 表示线程 `i` 当前已持有的该类资源数。
  - `semaphore_need[i][j]` 表示线程 `i` 当前阻塞等待的该类资源数。

- 核心代码片段 2:`semaphore_down` 中的死锁检测入口

```rust
if current_proc.deadlock.enabled
    && current_proc
        .deadlock
        .semaphore_would_deadlock(active_threads.as_slice(), tid, sem_id)
{
    return DEADLOCK_DETECTED;
}
if !sem.down(tid) {
    current_proc.deadlock.semaphore_wait(tid, sem_id);
    -1
} else {
    current_proc.deadlock.semaphore_acquired(tid, sem_id);
    0
}
```

- 关键说明:
  - 只有在开关开启时才拒绝请求。
  - 若当前请求会把系统带入不安全状态,则不让线程真正进入等待队列。
  - 若只是普通阻塞,则仍走原有的线程阻塞调度路径。

- 核心代码片段 3:`mutex_lock` 的等待环检测

```rust
pub fn mutex_would_deadlock(&self, tid: ThreadId, mutex_id: usize) -> bool {
    let Some(mut holder) = self.mutex_owner.get(mutex_id).copied().flatten() else {
        return false;
    };
    loop {
        if holder == tid {
            return true;
        }
        let Some(wait_mutex_id) = self.mutex_wait.get(&holder).copied() else {
            return false;
        };
        let Some(next_holder) = self.mutex_owner.get(wait_mutex_id).copied().flatten() else {
            return false;
        };
        holder = next_holder;
    }
}
```

- 关键说明:
  - mutex 是单实例资源,因此用等待图判环比矩阵算法更直接。
  - 这也满足实验说明里“mutex 和 semaphore 可分别检测,无需混合考虑”的要求。

- 最终验证命令全部在 Docker 容器中执行:

```bash
docker exec -it rcore-container /bin/sh -lc 'cd /tmp/tg-rcore-tutorial/tg-rcore-tutorial-ch8 && TG_USER_DIR=/tmp/tg-rcore-tutorial/tg-rcore-tutorial-user cargo build --features exercise'
docker exec -it rcore-container /bin/sh -lc 'cd /tmp/tg-rcore-tutorial/tg-rcore-tutorial-ch8 && TG_USER_DIR=/tmp/tg-rcore-tutorial/tg-rcore-tutorial-user bash ./test.sh exercise'
docker exec -it rcore-container /bin/sh -lc 'cd /tmp/tg-rcore-tutorial/tg-rcore-tutorial-ch8 && TG_USER_DIR=/tmp/tg-rcore-tutorial/tg-rcore-tutorial-user bash ./test.sh base'
```

## 三、遇到的问题与 Debug 记录(核心重点)

- **Bug 描述**:第一次在容器里编译时直接报错:
  - `/bin/sh: 1: cd: can't cd to /Users/chaoge/workspace/OS/tg-rcore-tutorial/tg-rcore-tutorial-ch8`
- **原因排查**  - 宿主机绝对路径没有直接挂到容器内。
  - 容器里真正可用的教程仓库在 `/tmp/tg-rcore-tutorial/tg-rcore-tutorial-ch8`,而不是宿主机路径。
- **解决过程**  - 先用 `docker exec -it rcore-container pwd``ls /mnt``find / -name tg-rcore-tutorial` 定位容器内仓库位置。
  - 确认容器自带完整 `tg-rcore-tutorial` 仓库后,把测试工作目录切到 `/tmp/tg-rcore-tutorial/tg-rcore-tutorial-ch8`
- **Bug 描述**:第一次在容器仓库执行 `cargo build --features exercise` 时,构建脚本失败:
  - `error: no such command: clone`
  - `failed to clone tg-rcore-tutorial-user@0.4.8 ... ensure cargo-clone is installed or set TG_USER_DIR`
- **原因排查**  - `build.rs` 默认希望通过 `cargo clone` 拉取 `tg-rcore-tutorial-user`  - 容器里没有安装 `cargo-clone`,但仓库根目录已经自带了 `tg-rcore-tutorial-user`- **解决过程**  - 不去改 `build.rs`,直接在测试命令里显式注入:
    - `TG_USER_DIR=/tmp/tg-rcore-tutorial/tg-rcore-tutorial-user`
  - 这样构建脚本就会复用现成用户态仓库,问题消失。

- **Bug 描述**:第二次编译进入本 crate 后,报错:
  - `error: unused #[macro_use] import`
  - 位置在 `src/main.rs:61:1`
- **原因排查**  - `main.rs``extern crate alloc;` 前面仍保留了 `#[macro_use]`  - 当前代码并没有在该作用域直接依赖 `alloc` 导出的宏,因此在 `deny(warnings)` 下被提升为编译错误。
- **解决过程**  - 删除 `#[macro_use] extern crate alloc;` 中的 `#[macro_use]`  - 保留 `extern crate alloc;` 本身,重新编译后该错误消失。

- **Bug 描述**:第一次执行整测时脚本没有真正跑起来:
  - `/bin/sh: 1: ./test.sh: Permission denied`
- **原因排查**  - 容器仓库里的 `test.sh` 没有可执行位。
- **解决过程**  - 不修改脚本权限,直接改用:
    - `bash ./test.sh exercise`
    - `bash ./test.sh base`
  - 这样既满足容器化测试要求,也避免额外改动仓库文件属性。

- **Bug 描述**:实现 semaphore 死锁检测时,存在一个高风险误判点:`ch8_deadlock_sem2` 里多个子线程会先持有资源,再统一阻塞在 barrier semaphore 上;如果只看“当前阻塞的子线程”,很容易把这个场景误判成死锁。
- **原因排查**  - `ch8_deadlock_sem2` 中主线程会提前占有 barrier semaphore 的资源,随后再释放给子线程。
  - 如果安全性检查时没有把主线程也纳入 Allocation 矩阵,那么 `Available` 会一直显示为 0,导致所有子线程都看起来“无法推进”。
- **解决过程**  -`semaphore_down` 的检测逻辑里,不只看当前阻塞线程,而是通过 `PThreadManager::get_thread(pid)` 获取当前进程全部活跃线程。
  - 这样主线程持有的 barrier 资源也会进入 `Allocation`,安全性分析才能正确判断“主线程可先运行并释放资源”,从而避免误报。

- **Bug 描述**`condvar_wait` 虽然不是本次练习重点,但它内部会解锁再尝试加锁;如果不更新 mutex 的死锁跟踪状态,后续 mutex 检测就可能读到过期持有关系。
- **原因排查**  - 本章 `condvar_wait` 的实现并不是简单睡眠,而是带有一次 mutex 所有权变更。
  - 死锁检测状态若只在 `mutex_lock/unlock` 外层更新,会和真实内核状态脱节。
- **解决过程**  -`condvar_wait` 返回 `(flag, waking_tid)` 后:
    - 先记录一次 mutex 释放 / 转交;
    - 再根据 `flag` 判断当前线程是重新持锁还是进入等待;
  - 这样可以保证 mutex 相关检测状态始终与真实锁状态一致。

- **最终结果**  - `bash ./test.sh exercise` 通过,checker 显示 `25/25`
  - `bash ./test.sh base` 通过,checker 显示 `22/22`