本篇博客一部分内容复制于我的个人博客,略有修改。
一阶段
因为以前没怎么写过Rust,而且算法是我的薄弱项,所以多花了点时间。关于题目没什么好说的,下面说说我对Rust的整体理解。
类型系统
Rust的类型系统大致可以分成几个方面:
- System F,也就是parametric polymorphism
- trait,一种ad-hoc polymorphism,另外Rust没有函数重载
- Associated Type,类比Haskell的functional dependecies
- Const Generics,仅限于基础类型的type to kind promotion
- Ownership,类似于Linear Types,但是有很多例外
- Lifetime Parameter和相关的variance,例如
'static <: 'a
可以让全局的引用被更小的生命周期使用
Rust的trait可以看作具有强制类型参数Self
的typeclass,例如:
1 | trait A { |
本质上和下面Haskell的写法差不太多。
1 | class A Self where |
Rust的Associated Type和Haskell的Associated Type虽然同名,但Rust的并不是Type Family,作用上来说只是一类特殊的类型参数(见functional dependencies),防止一个trait因为类型参数不同被多次实现。
Const Generics允许将编译期常量当作类型参数,不过只有少数几个类型(primitive types)可以,如果非要在类型论中找个解释,大概是把这些类型promote到trait,然后把它们的编译期确定的值promote到type,类似Haskell的DataKinds。
Rust没有kind,没有高阶类型,因此没有Monad。可预见的未来大概也不会有高阶类型了,因为就算有也很难和现在默认具有Self类型参数的trait兼容。
但是令人惊讶的是Rust的问号?有专属语法和还有Try和Error的trait支持,还不算太难用。
ADT
Rust的Algebraic Data Types:
- Empty,即感叹号
!
- Unit,即
()
- 积类型,即tuple和
struct
- 和类型,即discriminated union,也就是Rust的
enum
Rust的Emtpy是编译器开的后门,程序员不能自己定义。这样的写法:
1 | struct Unit; |
居然是Rust的Unit,而大部分函数式编程语言或者proof assistant中这种没有constructor的定义都是Empty,就Rust搞特殊。
至于GADT,因为Rust完全没有高阶类型和阶(kind)的概念,所以GADT也基本不用想了。
所有权
Rust最有趣的部分莫过于Ownership和Borrow,目前我的实践还不够,只能大概谈一下。
首先是所有权,即
- 每个值都有所有权
- 一个值的所有权最多被一个变量持有
- 所有权可以在变量之间移动,或通过函数的参数和返回值移动
- 如果所有权没有被移动,那么当前的scope必须把它drop掉
类比到Linear Types,那么scope所有值都被使用刚好一次,即
- 要不然被转移
- 要不然被销毁
一个特殊情况是实现Copy trait的类型,这种类型的值(即stack-only的值)的所有权不会被转移而是被复制,它们不需要drop。
所有权可以被暂时借走(borrow),这种机制叫做引用,引用是类型,分为不可变和可变两种。
不可变引用可以有多个,且可以不限次数地复制,例如:
1 | let a = 2; |
其中test
函数定义为fn test(v: &i32) {}
。第三行只是把变量b复制给c,因此b并不会失去所有权,第四行就能把b传递给函数。
但是可变引用最多只有一个,下面这段代码会报错:
1 | let mut a = 2; |
其中test2
函数定义为fn test2(v: &mut i32) {}
。第四行会报错,因为变量b持有的所有权已经被转移给c。
下面这段代码能通过编译:
1 | let mut a = 2; |
因为第一个可变引用已经使用完并被销毁,第二个可变引用才得以创建。
下面这段代码又会报错:
1 | fn test2(v: &mut i32) {} |
因为main第二行b延续了a的可变引用的生命周期,导致第三行无法再借出可变引用。
智能指针和Interior Mutability
大部分智能指针,如Box、Arc等不可变,可理解成一个指向Heap上的不可变引用,连元数据也在堆上,所以长度仅有指针大小。
可变性需要内层的容器支持,叫做内部可变性,这种容器的长度等于容器的元数据长度加上内容长度,可以在堆上也可以在栈上。
包括:
- RefCell:运行时检查引用规则的容器
- UnsafeCell:同RefCell但是无检查
- Cell:非引用访问,只能”整存整取”
- Mutex:纯互斥锁,所有形式的访问都只能存在最多一个
- RwLock:类似Mutex,但是允许一个写或多个读
一般Heap上的可读可写数据由智能指针和内层的容器构成,例如Arc<Mutex<Foo>>
。
二阶段
首先二阶段以第一名拿下了,很开心o( ̄▽ ̄)ブ
刚开始那几天由于不了解,不小心看到V3去了,不过收获颇丰,之前没怎么用过gdb这次算是彻底学会了。
第二阶段还学会了很多指令比如readelf、hexdump等等的用法,还有命令行tmux的使用方式。
现在回想起来似乎一路都没有遇到什么特别大的困难,必做题都是比较简单的,除了ch8以外差不多是一天一章的工作量。
ch8确实也有点麻烦,我花了两天才把死锁检测做出来。因为刚开始一直和银行家算法分不清楚,仔细看了书之后3个小时左右就解决了。
最有趣的我觉得是ch4的内存管理和ch5的进程管理。
我在ch5上实现了多核支持,并且在四个hart并行的情况下通过了所有测例,多核支持涉及dtb、thread local storage等等多个方面的知识,还要十分小心各种同步问题。
比如__switch
中间必须加锁,否则其他hart可能加载还没有保存完毕的TaskContext
,这个问题我调试了几个小时才找到原因,为了解决专门读了RISC-V的atomic extension,最后用LR/SC解决了问题。
我在ch6上实现了:
- lazy allocation
- demand paging
- fork COW
- mmap文件映射,包括Shared和Private两种模式
算是对内存管理有了一个比较广泛的了解吧,比较可惜的是忙别的去了,没有把页面置换做出来。
到最后选做题也没有做完,不过顺利晋级就很满足了,我最感兴趣的方向是hypervisor,接下来开始准备第三阶段。