0%

2024-秋冬季开源操作系统训练营第一二阶段总结-陈栩民

前言

我是来自华中科技大学计算机科学与技术专业的一名本科生,课堂上做过类似的操作系统实验,但当时忙于其他事情,只是草草应付了事,所以想趁着这个机会,重新详细认识一下操作系统的基本逻辑,也学习一下rust这门语言。

第一阶段

主要参考资料:https://course.rs/basic/intro.html

之前日常学习都是c和c++写得多,习惯了各种指针等非常自由的操作,遇到rust确实非常不适应,感觉编译器时时刻刻都要和我作对,我明明知道这么写没问题,但编译器就是不让。
但是随着我对于所有权、借用引用、生命周期这些核心概念的了解,慢慢我也体会到了rust这种“安全编程”带来的好处(后面写操作系统实验时,也比之前课堂上用c写bug少了很多)。
此外,“万物皆是模式匹配”的思想,也让整个编程风格看起来优雅了许多。

第二阶段

Lab 1

一开始接触这个系统,主要还是不太了解rust的rs文件之间是如何包含的,在引用一个外部模块时,实际上这个查询路径是怎样的。

如果是在c语言中,就是很自然地,只要有对应的PATH环境变量,根据对应的相对路径去include就可以了,但在rust里面,是通过各个目录下一个名为mod.rs的文件去形成一个文件树的,各种文件以什么程度可见,都由mod.rs控制。

具体见:https://skyao.io/learning-rust/docs/build/module/rust-module-system-explanation/

核心在于这句话:

当执行mod my_module;则编译器可以在同一目录下寻找到 my_module.rs 或 my_module/mod.rs 。

Lab2

在这个实验被一个小问题卡了很久。

1
2
3
4
5
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start, len, prot));
assert_eq!(mmap(start + len, len * 2, prot), 0);

上面的代码会在第二个mmap是触发如下错误:

1
2
3
4
5
let pte = self.page_table.find_pte(start);
if pte.is_some() {
println!("conflict_vpn: {:?}", start);
return -1;
}

当时百思不得其解,自己造了一些其他测试,发现下面这样的操作居然不会报错:

1
2
3
4
5
let start: usize = 0x10000000;
let len: usize = 4096;
let prot: usize = 3;
assert_eq!(0, mmap(start - len, len, prot));
assert_eq!(mmap(start, len * 2, prot), 0);

这两个测试有什么区别,我仔细思考了一下虚拟地址转换为物理地址的过程,怎么使用多级页表一步步得到最终的物理页,结合这个如此“整数”的start,我发现了它们的区别很有可能在于,第二个测试,两个请求会处在不同的子页表中,而第一个测试则都在同一个子页表。因此,我终于开始详细看find_pte的流程,发现它是只要找到对应页表项就会返回,不会判断该页表项是否Valid。

那么此时,如果某个子页表已经分配,只是对应页表项不Valid,那么conflict的判断就会失误。

这个故事告诉我们:不要主观臆测一个函数的用途,在使用一个函数之前,一定要确切知道它的输入输出表示什么意思。

此外,也不应该使用find_pte函数,本来这个函数就不是pub的,有一个判断了是否Valid的封装好了的函数供使用。

Lab3

这个lab挺简单的,就看懂fork和exec的执行逻辑之后,找到那些二者都改了的数据,只执行exec的对应改动,减少无意义的赋值。

stride算法,我使用了一个溢出标记的方法来防止溢出,即计算某个进程的stride值加完之后溢出了,就把该进程标记一下,之后都不考虑调度它,直到所有进程都溢出了。其实这样徒增了很多计算量,问答作业中的方法其实才是比较好的解法:

思想大致如下,没实际运行过,可能有一些细节上的问题:

1
2
3
4
5
6
7
8
9
10
11
impl PartialOrd for Stride {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let a = (self.0 & 0xff) as u16;
let b = (other.0 & 0xff) as u16;
if (a > b && a < b + 255 / 2) || (a < b && a + 255 / 2) < b {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
}
}
}

Lab4

这个实验做的时候遇到很多多重借用的错误,一个比较好的编程习惯是,在函数A调用一个函数B之前,先看看B里面有没有对什么进行了借用,如果有,注意先在调用之前把对应的借用drop掉。

Lab5

银行家算法,增减数据的时候记住,allocated + remian = all,有bug的时候就多看看这个规则有没有违背。以及记得在获得对应数据之后减need。

总结

这些实验确实都聚焦在了操作系统中基本且核心的问题,而且架构也是循序渐进,让人能够慢慢了解虚拟化、进程线程、文件系统这些都是怎么一步步得来的,感觉受益匪浅。