我能在完全不会 Rust 的情况下战胜 TacOS 吗??!!
Lab0
这个其实没啥坑,照着手册的 Option A 直接配就好了。只要你有一个好用的 根木棍就不是问题。
同学用 CLab 好像出了神秘问题,不建议使用 CLab。
Lab1
从这里开始坑就比较多,以下按我踩到的顺序依次写。
tool/ 下面的工具怎么用?
答案是……我也不知道。我首先尝试了直接 cd 进去然后 cargo tt -b lab1,但是按照 Option A 安装时自带的 rust 版本是 1.70,编译时有一些 package 需要更新的版本,于是就倒闭了。具体错误信息如下:
1 | 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 |
当然这是难不倒我的 它已经给出了一个升级提示,于是我按照该方式操作:先 rustup update stable 再 rustup default stable。
这样我成功获得了一个 1.94 版本的 rustc,然后 cargo h 就可以用了,但是 cargo tt -b lab1 仍然用不了,报错似乎是 src/fs/disk.rs 里面的一个 const u8 和 const 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 | Command for donation-three: |
在根目录下面运行 cargo run -r -q -F test-schedule -- -append alarm-zero,即可单独测试对应的 test case。这个 case 应该是直接可以通过的(因为 sleep 提供的是一个 working example)。看到如下输出说明测试正常。
1 | [21 ms] Hello, 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 | static ALARM: Lazy<Mutex<BTreeMap<i64, Vec<Arc<Thread>>>>> = |
需要的所有东西的位置:
1 | use crate::sync::{Lazy, Mutex}; |
使用 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。