PKU 操作系统 TacOS Lab2 踩坑记录

书接上回

5. 为什么 tool/ 下面的工具又能用了?

大概就是做 Lab1 的时候死活用不了,结果准备做 Lab2 的时候代码一点没改突然就能用了。具体为啥我也不知道,可能是因为我在做 Lab2 之前和远程仓库同步了一下导致一些配置改了,然而现在直接在根目录下面 cargo run 又用不了了。

当然这不是问题,我们可以直接 cargo tt -b lab2 看测试结果,但是问题是调试会比较麻烦。这里建议如果某个 case 爆了就直接使用 kprintln! 打印调试信息,然后使用 cargo tt -c <failed-case> -v 查看完整输出。如果需要打开 debug 参数查看原始代码里面给出的调试输出的信息,可以直接在 tool/ 下面的 test.rs 里面修改运行参数。很容易找到需要修改的位置。

总之,只要 tool/ 下面和根目录下面有一个能跑,就不成问题。

6. 虚拟内存的用法

由于我不会 Rust 所以也暂时没完全看懂它的虚拟内存代码,这里就只给出需要的用法了:

  • 检查指针是否有效时,需要在页表中提取对应项,并使用 is_valid 查看是否有效,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
pub fn check_valid_va_ptr(ptr: usize) -> bool {
let current = thread::current();
if let Some(pt) = &current.pagetable {
if let Some(entry) = pt.lock().get_pte(ptr) {
entry.is_valid()
} else {
false
}
} else {
false
}
}
  • 真正使用指针时,不需要进行任何翻译。例如在 read 中向用户提供的 buffer 中写入数据可以这么写:
1
2
3
4
5
let buf_slice = unsafe { core::slice::from_raw_parts_mut(buffer, size) };
return match file.0.read(buf_slice) {
Ok(n) => n as isize,
Err(_) => -1,
};

对于 Lab2,我们暂时不需要其他更复杂的用法。

7. 参数调用

熟悉虚拟内存的用法之后就很简单。所有的参数都需要加载到用户栈上,包括 argv 的所有字符串,以及指向这些字符串的指针。当然由于 argv 的类型是 char**,所以指向字符串的指针所处的位置必须是连续的,并且从低地址到高地址依次指向 argv[0]argv[1],以此类推,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
↑ High address (stack bottom)
┌─────────────────┐
│ argv[0] str │ "cp"
│ ended by '\0' │
├─────────────────┤
│ argv[1] str │ "input.txt"
│ ended by '\0' │
├─────────────────┤
│ ... │
├─────────────────┤
│ NULL (0) │ ← end of argv array
├─────────────────┤
│ argv[n] addr. │
│ ... │
│ argv[1] addr. │
│ argv[0] addr. │ ← argv point here
└─────────────────┘
(stack top here)

注意所有指针都要 8 字节对齐。最后根据 calling convention,argcargv 指针要放到寄存器中。注意到 execute 函数中本来就有设置寄存器的代码(设置的是 x[2] 即栈指针),仿照其写即可。需要将 x[10] 设为 argc 并将 x[11] 设置为 argv

8. 进程相关系统调用

由于 TacOS 没有要求我们做进程内多线程,我们可以巨大偷懒,直接把进程当作线程的一个属性。后面我可能会混用进程/线程,可以认为进程指的就是拥有该进程的线程。

首先我们直接令进程编号等于线程编号,这样我们不需要额外维护任何编号信息。