PKU 操作系统 TacOS Lab1 踩坑记录

我能在完全不会 Rust 的情况下战胜 TacOS 吗??!!

Lab0

这个其实没啥坑,照着手册的 Option A 直接配就好了。只要你有一个好用的 73\frac 73 根木棍就不是问题。

同学用 CLab 好像出了神秘问题,不建议使用 CLab。

Lab1

从这里开始坑就比较多,以下按我踩到的顺序依次写。

tool/ 下面的工具怎么用?

答案是……我也不知道。我首先尝试了直接 cd 进去然后 cargo tt -b lab1,但是按照 Option A 安装时自带的 rust 版本是 1.70,编译时有一些 package 需要更新的版本,于是就倒闭了。具体错误信息如下:

1
2
3
4
error: package `quote v1.0.45` cannot be built because it requires rustc 1.71 or newer, while the currently active rustc version is 1.70.0
Either upgrade to rustc 1.71 or newer, or use
cargo update -p quote@1.0.45 --precise ver
where `ver` is the latest version of `quote` supporting rustc 1.70.0

当然这是难不倒我的 它已经给出了一个升级提示,于是我按照该方式操作:先 rustup update stablerustup default stable

这样我成功获得了一个 1.94 版本的 rustc,然后 cargo h 就可以用了,但是 cargo tt -b lab1 仍然用不了,报错似乎是 src/fs/disk.rs 里面的一个 const u8const i8 的类型不匹配问题。

于是我又尝试了在根目录下面 override 使用 rustc 1.70 并在 tool/ 下面使用 rustc 1.94,但是这样似乎仍然无法通过编译,报错与使用 1.94 版本编译 OS 是相同的。问 AI 说的是要增加一些文件,但是这样我不知道会不会导致本地通过然后提交上去少文件过不了,所以也没这么干。

最后也没有解决,所以就压根没用上这个 tool,采用的 workaround 是 cargo tt -b lab1 --dry 可以显示测试各个 test case 的命令,在根目录下面一条一条的跑就行了。

例如在 tool 下面运行 cargo tt -b lab1 --dry 的输出为:

1
2
3
4
5
6
7
8
9
10
11
Command for donation-three:
cargo run -r -q -F test-schedule -- -append donation-three
Command for priority-alarm:
cargo run -r -q -F test-schedule -- -append priority-alarm
Command for alarm-zero:
cargo run -r -q -F test-schedule -- -append alarm-zero

...

Command for alarm-multiple:
cargo run -r -q -F test-schedule -- -append alarm-multiple

在根目录下面运行 cargo run -r -q -F test-schedule -- -append alarm-zero,即可单独测试对应的 test case。这个 case 应该是直接可以通过的(因为 sleep 提供的是一个 working example)。看到如下输出说明测试正常。

1
2
3
4
[21 ms] Hello, World!
[42 ms] [PASS]
[42 ms] Leaving test...
[57 ms] Goodbye, World!

Alarm clock

我们观察到 src/thread.rs 下面有需要修改的 sleep 函数,而 src/sbi/timer.rs 下面有一个更改计时器的 tick 函数,从而我们需要做的就是:

  • 调用 sleep(time) 的时候,我们直接把当前线程 block 住,然后搞一个全局数据结构记录下来我们需要在当前 tick + time 时间唤醒这个进程。
  • 调用 tick() 的时候,我们从这个全局数据结构里面弹出唤醒时间在当前时间之前的线程,把这些线程唤醒。

理论上这个功能我们可以搞一个优先队列,但是由于 TacOS 里面似乎把 std 给 ban 了,并且也没有给我们提供 std::cmp::Ordering 的等价物,从而我们只能使用 BTreeMap 维护 (time, thread) 对。但是这个东西比较麻烦的地方在于它不支持两个相同的 key,从而我们需要维护 (time, Vec<thread>) 将唤醒时间相同的进程存到一起。

由于我们需要保证 sleep 是并发安全的,所以我们需要 Mutex 锁。但是这个东西还有一个不是 const function 的神秘问题,所以我们还需要一个叫 Lazy 的东西。至于这玩意是啥我也不知道(我完全不会 Rust),反正直接用就好了。最终这样声明全局数据结构 ALARM

1
2
static ALARM: Lazy<Mutex<BTreeMap<i64, Vec<Arc<Thread>>>>> =
Lazy::new(|| Mutex::new(BTreeMap::new()));

需要的所有东西的位置:

1
2
3
4
5
use crate::sync::{Lazy, Mutex};
use crate::thread::Thread;
use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use alloc::vec::Vec;

使用 ALARM.lock() 访问内部受保护的 BTreeMap

剩下的代码写起来都比较符合直觉,就不多说了。如果遇到奇怪的类型不匹配问题(例如多引用 / 不变量 vs 变量这种),基本上把代码和错误信息粘给 LLM 都能直接帮你调出来。

如果测试的时候爆了,可以通过 cargo run -r -q -F test-schedule,debug -- -append <test-case> 查看单个 test case 的详细运行过程。

如果出现类似 panicked on no thread is ready 的错误信息,并且 debug 发现 Idle 线程或者别的线程莫名其妙被 block 了,可以检查一下是不是死锁了。如果你的循环 / if 条件里面写的是 if let Some(v) = ALARM.lock().get_mut(&time) 然后循环 / if 里面还有 ALARM.lock() 就会死锁。这个时候需要在循环 / if 外面声明一个 let guard = ALARM.lock(); 然后在 if 条件和内部都只用 guard 访问。

这里我暂时通过了 alarm 开头的五个 case。