0%

前言

當年在學校學操作系統的回憶痛苦而蒼白,還記得剛上了兩堂課就知道這位老師的課上了也沒意義,估計他都不曉得自己在說什麼,整個學期下來,我連操作系統該有什麼樣的功能都很含糊,這個現象在同學之間普遍存在,一間高等學校好幾屆學生的系統知識就被一個毫無素養的老師給毀了,我每每回想起來感到很悲哀。

還很印象很深刻的是,他說操作系統太過複雜,要在課堂上寫一個是很不可能的。但過了幾年,我瞭解到了國外知名的 xv6 項目,明白自己完全被糊弄了。雖然離開校園已經好幾年,在 rust.cc 論壇上看到 rcore 訓練營的消息,我非常興奮地報了名。

第一階段總結

Rust 語言我還算是有些基礎,用它寫過一些簡單的程序。前面幾十道題過得還挺快,但當初學習沒能覆蓋到方方面面,在生命週期及 unsafe 操作裸指針上還是卡了一些關。這實在是 rust 真正獨創的地方,而操作記憶體在 OS 開發相必是重中之重,也期待在接下來的課程能進一步熟悉。

期許

下班後能利用的時間實在有限, rustlings 居然花了一個禮拜才搞定,接下來的第二階段必定挑戰更大,需要做好時間安排,潛心專研。

前言

作为一个长期依靠自学的科班选手,目前只系统学习过 Berkeley CS61A/61B,参与过炼丹项目,系统编程最近才起步(xv6)。加入训练营主要是借助 ddl 逼迫自己学习 Rust 以及 OS,拓展技术栈,同时也在为明年暑期实习和后续可能的 PL 研究增加知识储备。(能够遇到志同道合的朋友就更好了^^)

心得

之前早已听说 Rust 语言层面的安全性,但在实际上手之后才真正理解了这一点:严格的所有权、类型、生命周期、借用等机制,不断优化的编译时检查和对人类友好的报错信息/修改建议,包管理、配置管理的先进,以及零成本抽象的构建逻辑等,形成了区别于其他主流语言的独特风格。更加难得可贵的是 Rust 高质量的官方教程,讲解深入浅出,配合实际案例,一般路过萌新也能够顺利入门。

在 Rust 语法学习以及 Rustlings 练习中,主要难点有:读写/所有权机制、泛型/生命周期、智能指针、并发,以及一些 Safe Rust 目前难以解决的问题及,例如链表、自引用和循环引用。

Rust 不同于 C++ 等默认 copy semantics(复制语义) 的语言,转而默认对未实现 Copy trait 的类型 move semantics(移动语义),这一点使得很多初学者望而却步:即使编写简单逻辑程序,仍然需要对 Rust 的类型以及内存模型有一定了解,控制引用生命周期,同时明确程序每一处的逻辑,才能在 Safe Rust 的约束下通过编译。Rust 生命周期机制显式实现了对引用的有效期规定,从而有效控制借用范围。Rust 经过多次版本迭代,也引入了一系列语法糖,例如 Deref coersion、? match Result 等,一定程度降低了心智负担,不必成为“语言律师”才能上手开发。

总的来说,Rust 是一门高度现代化,注重内存安全和性能的系统级开源编程语言,同时具有逐渐完善的编译器和第三方库生态,可谓未来可期。

学习目标

第二阶段开始前,通过 Berkeley CS61C 以及 xv6 lab 提升对 RISC-V 指令集以及 OS 基本运行逻辑的理解。

尽可能在预计时间内完成第二阶段任务,同时备战期末(

契机

事情的起因是在威神群里看到的清华操作系统训练营。一个教你用rust写os的训练营!

受到了炸药哥、cjj等多位体系结构大佬的影响,且本身自己就有一个想把cs和ai结合起来的梦想。而且了解到开源操作系统研究组研究方向涉及高性能计算,于是果断报名。

其实我自己也不知道是否对操作系统感冒,对rust的兴趣也不及cpp。我只是喜欢了解计算机科学的底层,加之想找hpc的暑期实习但简历却石沉大海(悲),且也想扎实自己os基础便来了这里。

rust学习曲线真的好陡峭(痛苦面具)

从rust零基础到完成rustlings大概用了两周时间。

看过rust圣经,rust程序设计语言,做过rust最佳实践的练习,在b站看过网课,还尝试做过两个项目: 用rust实现基本数据结构和用rust实现lua解释器。 真的想说一句:”我xxx!” (请读者自行想象)

其实依旧是有很多不明白的东西,比如链表的布局其实我是似懂非懂的,只是照搬了它的布局。但为何这样布局当时并没有太清楚。
这样的地方还有很多,就且留到以后慢慢体会。毕竟我是来学os的(嘿嘿),我始终坚信一点:所有的疑问都会在不断学习的过程中找到答案!

幻想成为英雄

南京大学jyy在他的操作系统课上曾说过一句话:”这个世界需要英雄!”

我真的真的很喜欢这句话,在这个浮躁的时代,越来越多的人选择卷绩点,水科创,水竞赛,学前后端狠赚一笔()…

但这样不酷,真的不酷。
我要打下最扎实的计算机科学基础,我要实现真正顶层和底层的结合,我要做ai system!

那么就从认真完成这次操作系统训练营开始吧,第二阶段见!

我们,一定未来可期!

在第一阶段的学习中,主要对rust的所有权以及指针等方面进行了着重学习,下面对相关知识点进行简要总结

所有权相关规则

一个值只能被一个变量所拥有,这个变量被称为所有者

一个值同一时刻只能有一个所有者,即不能有两个变量拥有相同的值

当所有者离开作用域,其拥有的值被丢弃,内存得到释放。

当出现所有权冲突时,可以通过调用 data.clone() 把 data 复制一份出来给 data1,这样做可以创建 data 的深拷贝,避免出现所有权问题

Move 语义:当一个值被赋值给另一个变量时,它的所有权会转移,原始变量将不再有效,默认情况下,大部分自定义类型都是具有 Move 语义(优点:可以避免使用拷贝操作,提高性能)

Copy 语义:不希望值的所有权被转移,赋值或者传参时,值会自动按位拷贝(浅拷贝),两个变量都拥有独立的内存空间。 Copy 语义通常适用于简单的基本类型,如整数、浮点数、布尔值等

Borrow 语义:不希望值的所有权被转移,又无法使用 Copy 语义,可以“借用”数据,其允许在不转移所有权的情况下借用值的引用,包括不可变引用(&T)和可变引用(&mut T),允许同时存在多个不可变引用,但不允许同时存在可变引用和不可变引用(优点:使得代码更加安全,因为它在编译时进行所有权检查,防止了一些常见的错误,如悬垂指针和数据竞争)

一个值给多个所有者

  • Rc

对一个 Rc 结构进行 clone(),不会将其内部的数据复制,只会增加引用计数,当引用计数为0时,内存释放

Arc 与 Rc 类似,但是使用原子操作来保证引用计数的线程安全性,支持线程共享数据。

(如果不用跨线程访问,可以用效率非常高的 Rc。如果要跨线程访问,那么必须用 Arc)

use std::rc::Rc;
  • RefCell
    允许在不可变引用的情况下修改数据,采用borrow_mut(可变)、borrow(不可变)

Mutex 和 RwLock 都用在多线程环境下,当需要多线程时,可直接替换RefCell

use std::cell::RefCell;

智能指针Box

Rust 中,凡是需要做资源回收的数据结构,且实现了 Deref/DerefMut/Drop,都是智能指针。

允许将数据分配在堆上,当 Box 离开作用域时,它指向的堆内存会被自动清理。常用于: 在编译时大小未知的数据;大型数据结构,以避免栈溢出;拥有数据,确保只有一个所有者。

Box<dyn Trait> 表示一个指向实现了指定 trait 的类型的堆上分配的指针。
Trait 可以是任何 trait,它定义了一组行为或方法,而具体的类型则实现了这些方法。
通过 Box 将其放置在堆上,可以在运行时动态确定对象的具体类型,并通过指针进行访问。
运行时动态派发(动态调用)是通过虚函数表来实现的,这意味着在运行时确定调用的具体方法。

trait Animal {
    fn sound(&self);
}

struct Dog;
impl Animal for Dog {
    fn sound(&self) {
        println!("The dog barks!");
    }
}

struct Cat;
impl Animal for Cat {
    fn sound(&self) {
        println!("The cat meows!");
    }
}

fn main() {
    let dog: Box<dyn Animal> = Box::new(Dog);
    let cat: Box<dyn Animal> = Box::new(Cat);

    make_sound(&dog);
    make_sound(&cat);
}

fn make_sound(animal: &Box<dyn Animal>) {
    animal.sound();
}

unsafe代码块

unsafe绕过了 Rust 的安全检查,错误使用可能导致内存不安全、数据竞态等问题。

  • 解引用裸指针
let mut x = 10;
let ptr = &mut x as *mut i32;
unsafe {
    *ptr += 1;
}
  • 访问或修改静态变量:在 Rust 中,修改静态变量是不安全的操作。
static mut COUNT: i32 = 0;
unsafe {
    COUNT += 1;
}
  • 实现不安全的 trait,如 Send 和 Sync

  • 进行内存布局的低级操作:如结构体的字段重叠或内存对齐。

类型之间的相互转换

  • as 运算符:as 运算符用于类型转换,可以用于将一个值从一种类型转换为另一种类型。例如,将一个 u32 转换为 u64。
  • into 和 from 方法:这些方法是 From 和 Into trait 的一部分,用于在不同类型之间进行转换。这些方法通常会涉及类型的所有权转移。
  • try_into 和 try_from 方法:这些方法是 TryFrom 和 TryInto trait 的一部分,用于尝试在不同类型之间进行转换。这些方法在转换失败时会返回一个错误(Err)
  • cast 方法:在特定场景下,尤其是与裸指针相关的操作中,cast 方法可以用于将一个指针转换为另一种类型的指针。
let ptr: *const i32 = &10 as *const i32;
let ptr_void: *const std::ffi::c_void = ptr.cast();
  • transmute 方法:transmute 是一个不安全的操作,它可以在不同类型之间进行任意转换。这是一个高级的转换函数,使用时必须非常小心。
use std::mem::transmute;

let x: u32 = 42;
let y: f32 = unsafe { transmute(x) };

as用于显式转换,所有权不变;into/from用于隐式转换,所有权转移;try_into/try_from和into/from不同的地方是,转换失败会返回Err

以上是一些本阶段学习的知识点总结,通过第一阶段的训练,整体对rust的语法与编写有了初步地认识,不过在代码调试,多线程、链表操作方面还有所欠缺,将在后续空闲时间不断学习。

两年前就有听说过rust了,但是只是简单的看了下the rust book,并没有实际使用rust进行编程。这次打算借着rcore进一步实践rust。

目前的感受是rust的设计给了很多的限制,同时rust似乎也和cpp一样算是多范式的语言?就比如迭代一个列表既可以写成下面的形式

1
2
3
for ... in something.iter() {

}

写可以写成

1
something.iter().sum();

这样的形式。说实话,我不是很喜欢多范式的编程语言。因为选择太多了,对于我来说阅读别人的代码就会很麻烦。所以这里我更喜欢go的设计。

此外我用rust刷了一些leetcode上的简单的算法题,感觉挺头疼的。需要关注特别多的细节,而且leetcode特别喜欢给i32类型数据,而rust中访问容器用的下标都是usize类型的。导致代码写起来一点也不美观。有特别多的xx as usize

此外,感觉rust的标准库看起来是提供了很多方法来代替本来可用指针操作的方法。比如vector的swap,一般情况下其它语言里可能是这样写

1
a,b = b,a

rust对数据类型还特别敏感,只要有越界就会报错,必须得用饱和减法saturating_sub之类的方式来处理。

总之,我感觉rust的限制太多了,这让我对写好一个rust程序来说感觉心智负担很重。对于不依靠编辑器写完整程序没有信心。

希望接下来的rcore能改变我当前对rust的看法。

rust的学习

因为之前学过一些c++,有一定的基础,对rust的学习真的帮助挺大的。rust里的所有权,智能指针,移动语义都能在c++里找到对应的东西,rust学起来还是挺轻松的。

相较于c++,rust使用起来真的太舒服了。使用match进行流程控制十分优雅,宏也比c++的宏更加强大,rust泛型编程的类型萃取使用起来也比c++方便,更不用说cargo包管理对c++简直就是降为打击。

代价是rust的unsafe用起来有点丑陋。

rustlings实验

前一百道题通过看 Rust 程序设计rust官方api文档 和问gpt都能够轻松的完成。

后十道数据结构题目就遇到麻烦了,主要是对 rust unsafe 操作的不熟悉,写起来真的折磨。在认真学习了解rust 指针操作后,有算法基础数据结构实现还是比较好实现的。

总结

rust入门可以认真看完 《Rust 程序设计》,不理解的地方问gpt和百度。再写完rustlings, 最后在github上找个练手项目去理解代码尝试自己实现。

前言

在水群的时候,无意间看到这个训练营的宣传,点进去看到是OS训练营还有项目实战,正好自己对OS比较感兴趣并且正想要一个项目来练练手,就报名了该训练营。并且将该训练营转给队友,发现他们也感兴趣,于是乎,我们三人又组起了队。

第一阶段总结

第一阶段主要完成了rustlings的110道题的训练,前面100个主要针对某个小的知识点/语法,一般添加几个关键词,做几个小的移动就过了,难度并不大,收获比较大的还是后面的10道算法题,感觉做第一个的时候是最难的,后面的借鉴第一个很快就能写出来。比较喜欢这10的算法的原因是,因为之前有其他语言的经验,更加喜欢通过比较来学习,看rust跟其他语言编写同样功能的区别(又称用rust重写)。

下一步

最近事情有点多,又要做读研的准备,又要做找工作的准备,希望自己渡劫成功,能够顺利完成吧。

第一阶段 - Rust学习

0 前言

Rust作为rCore操作系统的开发语言,学习并掌握它对于后面学习rCore OS是非常重要的(关于我Rust学了个大概就去硬啃rCore差点自闭这件事)。

这次训练营也是我二刷了,上次23秋季由于被学校抓去打互联网+等比赛了,🤦‍♂️实在是没有时间弄了,进了二阶段就没然后了。

翻车gif

一阶段时间过的好快,感觉还没学多少就过去了(时间都用去debug操作系统大赛的fatfs了🤦‍♂️)。所以总结的内容就比较普普通通了,各位大佬见谅🙏。

1 环境与工具软件等

由于本人并不是CS相关专业,过去主攻的是EE,所以CS里面的很多东西过去都没接触过。从23年秋季第一次接触训练营开始到现在,接触到了很多过去没有用过的工具软件以及一些开发环境,在此简单的列举了一些。

1.1 git和github

是的,你没有看错。我之前真没怎么用过git和github。一方面原因是,上大学后搞的都是嵌入式裸机的开发,远古单片机IDE(Keil)不支持高级的代码版本管理,加上我的学习路线上也没碰到过使用代码版本管理,索性代码直接放本地(别人要的话就u盘拷贝🤦‍♂️),就很少用git去把代码放仓库(之前实习时迫不得已去折腾了下)。另一方面是,git和github有些时候真的太卡了(之前不会挂代理也不敢挂梯子)。

keil

不过,现在熟悉了git的各个命令后,配合上github去做项目的代码版本管理是真爽呀。

胡桃摇

个人过去常用的git命令和个人理解如下:

1
2
3
4
5
6
7
8
9
git clone 			// 将repo(仓库)克隆到本地
git pull // 合并远程repo的更新到本地
git add . // 将更改的文件放入暂存区
git commit -m "<message>" // 创建一个新的提交,-m 后便是提交的消息
git push // 将提交推到远程仓库
git branch // 查看现有的分支
git branch <new_branch> // 创键新的分支
git checkout <branch> // 切换到某分支
git status // 查看本地仓库状态

还有一些比如本地新建的分支需要推到远程、放弃某文件的修改等命令,就没去记了。我是做相关操作的时候,看一下报错建议的命令或在搜一下、问一下gpt,然后改一下。

另外初次使用可能需要配置用户信息:

1
2
git config --global user.name "Your Name"
git config --global user.email your.email@example.com

1.2 Linux操作系统(Ubuntu、CentOS)

虽说之前玩jeston和个人博客的时候也用过,但是基本上就是靠readme+baidu+CtrlCV+GPT(而且用了就忘),这次也算是又学习并加深了了解吧。这里就简单的列一下这期间用到的还记得的命令吧(大佬们清点喷555)

派蒙抱大腿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
ls                      	# 列出当前目录下的文件和文件夹
cd [目录名] # 改变当前目录
sudo [命令] # 以超级用户身份执行命令
sudo apt install [包名] # 以超级用户身份通过apt安装软件包
apt-get install [包名] # 通过apt-get安装软件包
touch [文件名] # 创建一个空文件或修改文件的时间戳
vim [文件名] # 打开文件进行编辑
rm [文件名/目录名] # 删除文件或目录
make # 用于编译程序的命令
cp [源文件/目录] [目标文件/目录] # 复制文件或目录
mv [源文件/目录] [目标文件/目录] # 移动或重命名文件或目录
mkdir [目录名] # 创建一个新的目录
rmdir [目录名] # 删除一个空目录
pwd # 显示当前工作目录的路径
cat [文件名] # 查看文件内容
file [文件名] # 查看文件信息
tar [选项] [文件名] # 打包或解包文件
gzip [文件名] # 压缩文件
gunzip [文件名] # 解压缩文件
wget [URL] # 从网络上下载文件
curl [URL] # 传输数据的工具,支持多种协议
ssh [用户名]@[IP地址] # 通过SSH协议远程登录到另一台计算机
scp [源文件/目录] [用户名]@[IP地址]:[目标路径] # 通过SSH远程拷贝文件
df -h # 显示磁盘使用情况
du -h [文件名/目录名] # 显示文件或目录的磁盘使用情况
free -m # 显示内存使用情况
top # 显示实时的系统进程信息
ps # 显示当前进程的快照
kill [进程号] # 结束一个进程
logout # 注销当前会话
reboot # 重新启动计算机
shutdown -h now # 立即关机
fdisk # 一个用于磁盘分区的程序,可以创建、删除、修改和重新组织分区表
losetup # 用于设置和控制Loop设备,它可以将一个文件模拟成块设备,常用于挂载文件系统镜像

1.3 RustRover

强烈推荐,我刚开始是用的VSCode+RA插件或Vim+YouCompleteMe。后来换成rr后,感觉开发真的舒服了好多。一是因为二阶段写OS,在ubuntu里我用的VSCode写一会就会崩,比较卡。用Vim的话,代码文件一多切来切去麻烦的很;二是因为他的各个界面和PyCharm、IDEA是一样的,上手很快;三是多了很多有用的功能,比如你use了某个外部creat,rr会自动帮你在cargo.toml中加上依赖。

RustRover下载地址:https://www.jetbrains.com/zh-cn/rust/

Linux环境安装步骤:

1
2
3
4
5
6
7
1. 下载
2. cd到下载的路径
3. 解压下载的安装包:
4. cd到解压的路径
5. cd bin
6. ./rustrover.sh
7. 勾选一些东西确认安装后,等待片刻,便会进入到欢迎界面

在欢迎界面左下角点击设置图标,再点击(Create Desktop Entry)将rustrover添加到桌面目录。之后便可以使用了。

xiao

2 Rust知识

2.1 Rust学习资料

1.Rust 中文圣经

2.Rust 程序设计语言

3.Rust 中文文档汇总

4.Rust 官方文档中文翻译

5.Rust 半小时学习
半小时看不完呀!

派蒙吃惊

2.2 个人觉得Rust比较有意思的知识点

2.2.1 所有权(Ownership)和生命周期(Lifetimes):

  • Rust的所有权和生命周期机制,可以防止诸如空悬指针、缓冲区溢出等内存安全问题。
  • 在Rust中,每个值都有且只有一个所有者。
  • 而在C语言中,并没有所有权概念,内存管理完全由程序员负责,这就可能导致内存泄漏和野指针等问题。
    1
    2
    3
    4
    5
    6
    // Rust
    {
    let s1 = String::from("hello");
    let s2 = s1;
    // 此时 s1 已经失效,不能被使用
    }
    1
    2
    3
    4
    // C
    char *s1 = "hello";
    char *s2 = s1;
    // 此时 s1 和 s2 都指向相同的内存

    2.2.2 借用检查(Borrowing):

  • Rust的数据借用可以允许多个读访问,但只要有一个可变借用,就不能有其他读或写访问。
  • C语言没有这样的机制,因此需要手动管理指针和数据的共享。
    1
    2
    3
    4
    5
    6
    7
    8
    // Rust
    {
    let mut s = String::from("hello");
    {
    let r1 = &mut s;
    // 在此作用域内,不能借用 s 的其他引用
    } // r1 超出作用域,s 可以被再次借用
    }
    1
    2
    3
    4
    // C
    char s[] = "hello";
    char *p1 = s;
    char *p2 = s; // p1 和 p2 都指向相同的内存

    2.2.3 模式匹配(Pattern Matching):

  • Rust提供了强大的模式匹配功能,可以用于枚举、结构体、元组和字面量等。
  • C语言没有内置的模式匹配功能,通常需要使用switch语句和多个if-else链来实现。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Rust
    enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
    }
    let coin = Coin::Penny;
    match coin {
    Coin::Penny => println!("Penny"),
    Coin::Nickel => println!("Nickel"),
    Coin::Dime => println!("Dime"),
    Coin::Quarter => println!("Quarter"),
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // C 
    switch (coin) {
    case PENNY:
    printf("Penny");
    break;
    case NICKEL:
    printf("Nickel");
    break;
    case DIME:
    printf("Dime");
    break;
    case QUARTER:
    printf("Quarter");
    break;
    }

    2.2.4 并发(Concurrency):

  • Rust提供了安全并发的工具,如std::threadArc(原子引用计数)和Mutex(互斥锁)。
  • C语言标准库没有内置的并发工具,通常需要使用操作系统提供的线程库(如POSIX线程)和同步原语。
    1
    2
    3
    4
    5
    6
    // Rust
    use std::thread;
    let handle = thread::spawn(|| {
    // 在新线程中运行的代码
    });
    handle.join().unwrap();
    1
    2
    3
    4
    5
    6
    7
    // C 
    pthread_t thread;
    int status = pthread_create(&thread, NULL, &thread_function, NULL);
    if (status != 0) {
    // 错误处理
    }
    pthread_join(thread, NULL);

    2.2.5 错误处理(Error Handling):

  • Rust使用ResultOption类型来处理可能的错误或空值,这鼓励开发人员进行显式的错误处理。
  • C语言通常使用整数错误码和返回值,这可能导致错误被忽略或错误处理。
    1
    2
    3
    4
    // Rust
    fn open_file(file_name: &str) -> Result<File, io::Error> {
    File::open(file_name)
    }
    1
    2
    3
    4
    5
    6
    7
    8
    // C
    int open_file(const char *file_name) {
    FILE *file = fopen(file_name, "r");
    if (!file) {
    return -1; // 返回错误码
    }
    return 0; // 表示成功
    }

    3 总结

    说来惭愧,这次训练营第一阶段大部分时间花在折腾OS大赛上了。本来到rCore的ch7时都挺顺利的,结果尝试去运行OS大赛的FAT32镜像里的测例就碰壁了。先是easyfs和fat32不是一个东西,得把照着rCore写的OS的fs换成支持fat32的,瞎折腾了两周总算是能读取fat32里的文件了。然后又发现大赛OS的elf文件导入OS运行又有问题,瞎折腾也搞不出来了(基础不牢靠,不懂为啥会loadpage Fault),索性打算重新复习。

这次训练营又学到了很多新的知识,不过也能很清楚地感觉到掌握的不牢靠,后面还需不断加强巩固。同时我也知道了如果自己后面想往OS方向发展,还有很多需要去学习的。说实话,刚开始搞rCore的时候感觉还挺好的,而后到了ch4后,计组等知识的欠缺就很致命了,搞的差点放弃了(fat32那也是,不过好在搞出来了)。然后到了运行OS大赛的elf这就真搞不懂了,只能试着去好好复习下Rust、看看计组、CSAPP等后,再来看看了。

感激社区提供了这样一个学习平台,它为我打开了一扇探索操作系统奥秘的大门。希望后续的学习我还能够坚持下去吧!

npn

4 图片记录

1

照着rCore写的FeatOS移植FAT32文件系统成功

2024春操作系统训练营一阶段总结

水群的时候看到了别人分享的邀请链接,于是就报名了训练营

刚接触rust,很多东西理解起来很困难,尤其是lifetime和闭包、迭代器这些内容。

虽然写完了rustling,但还是有许多细节掌握得不够熟练,最后那十道algorithm一做一个不吱声🤡

参考的书籍有 rust程序设计语言 rust语言圣经

以及 rust by examplecrates.io 我不太会用😅)

对了,rustling里面好像没有异步,记得去补一下

希望下个阶段顺利🦀🦀

2024 春夏季训练营记录

说明

本文为训练营的每日实践记录索引,主要为按日期较随意地写下每天的记录与想法。

同时还包含对其他资料、笔记的索引。

更详细的记录在 github 仓库里 https://github.com/hehepig166/2024-OS-camp-notes

工具与资料

训练营社区 https://opencamp.cn/os2edu/camp/2024spring/

Github LearningOS https://github.com/Learningos

Open-Source OS Training Comp 2024 https://github.com/LearningOS/rust-based-os-comp2024

日历

04-01 04-02 04-03 04-04 04-05 04-06 04-07 (开营)
04-08 (第一阶段) 04-09 04-10 04-11 04-12 04-13 04-14
04-15 04-16 04-17 04-18 04-19 04-20 04-21
04-22 04-23

04-07

群公告
欢迎大家加入春夏季操作系统训练营!
4月7日正式开营,已报名的同学请及时学习导学视频内容!
网址:https://opencamp.cn/os2edu/camp/2024spring/stage/0?tab=video

在训练营常见问题Q&A文档https://docs.qq.com/doc/DY3VMc0tOc29KTWZ5 中,对于本期训练营有一些基本问题的介绍,也可以在群里询问

2024春夏季训练营启动仪式回放
录制文件:https://meeting.tencent.com/v2/cloud-record/share?id=95ecb9c0-64f2-4934-a671-78474f735af2&from=3&record_type=2
密码:0407

  • 开营仪式

    • rustlings -> Rust 学习

    • rcore -> Rust 重写内核实验

    • ArceOS -> 组件化 OS 项目

之前几周已经开始学习 Rust,做了 rust-lang 下的 rustlings

rust-lang::rustlings https://github.com/rust-lang/rustlings

我的解答与极其简陋随性的学习日志 https://github.com/hehepig166/my-solution-to-rustlings

(主要是 Rust 资料实在是太多了,我就只写一点关键词提一下记忆即可)

配置了一下 wsl

04-08

@所有人  各位同学,第一阶段“Rust编程语言 & Rustlings答疑”第一次课程将于今晚8点开始,今晚的主要内容为Rustlings练习入门、基本数据类型,slice类型,所有权等,总共一个课时,请大家预留时间,按时进入课堂!!

上课方式:点击 https://opencamp.cn/os2edu/camp/2024spring/stage/1 链接,签到并进入课堂进行直播上课

https://learningos.cn/rust-rustlings-2024-spring-ranking/

https://github.com/LearningOS/rust-rustlings-2024-spring-hehepig166

使用 ssh 链接 clone,用 ssh 鉴权

wsl2 /mnt/ 下 rustlings 更新编译可能会出问题,开发最好不要在 /mnt 目录下操作

module 的 pub 关键字只往上 pub 一层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mod delicious_snacks {
// TODO: Fix these use statements
pub use self::fruits::PEAR as fruit;
pub use self::veggies::CUCUMBER as veggie;

mod fruits {
pub const PEAR: &'static str = "Pear";
pub const APPLE: &'static str = "Apple";
}

mod veggies {
pub const CUCUMBER: &'static str = "Cucumber";
pub const CARROT: &'static str = "Carrot";
}
}

fn main() {
println!(
"favorite snacks: {} and {}",
delicious_snacks::fruit,
delicious_snacks::veggie
);

// let a = delicious_snacks::fruits::PEAR; // 无法访问
let b = delicious_snacks::fruit; // 可以访问
}

04-09

std::f64::NAN

Rust 中两个 NAN 是不相等的,要判断一个浮点数是不是 NAN,可以用 .is_nan() 函数来判断。

完成了 rust-lang 里的 rustlings。

在 Rust 中,? 是一个简便的错误处理操作符,通常用于简化对 ResultOption 类型的值进行处理的代码。它的作用是在操作成功时返回成功的结果,如果遇到错误则将错误提早返回。

在函数中使用 ? 时,该函数的返回类型必须是 ResultOption。如果表达式的结果是 Ok(对于 Result 类型)或 Some(对于 Option 类型),则 ? 会将其中的值提取出来并返回;如果结果是 ErrNone,则整个函数会提前返回并将 ErrNone 作为整个函数的返回值。

04-10

@所有人 各位同学,第一阶段“Rust编程语言 & Rustlings答疑”第二次课程将于今晚8点开始,今晚的主要内容为Rustlings答疑,总共一个课时,请大家预留时间,按时进入课堂!!

上课方式:点击 https://opencamp.cn/os2edu/camp/2024spring/stage/1 链接,签到并进入课堂进行直播上课。

如果同学们有什么问题也可以在问题收集页面https://docs.qq.com/doc/DSXFzRkdodExxQUVO给老师提一下哦~

rustlings test 章节

  • 条件编译

#[cfg()]

cfg() 可以接受一些不同的条件,用于控制编译时的行为。这些条件可以是 Rust 编译器理解的一些特定标识符,也可以是自定义的条件。下面是一些常见的 cfg() 条件:

  • target_os: 目标操作系统,如 "windows", "linux", "macos" 等。
  • target_arch: 目标架构,如 "x86", "x86_64", "arm" 等。
  • target_env: 目标环境,如 "gnu", "msvc", "musl" 等。
  • target_pointer_width: 目标指针宽度,如 "32", "64" 等。
  • feature: 启用的特性,如 "myfeature"
  • any(): 如果任一条件为真则为真,语法为 cfg(any(condition1, condition2, ...))
  • all(): 如果所有条件为真则为真,语法为 cfg(all(condition1, condition2, ...))
  • not(): 取反,语法为 cfg(not(condition))
  • 自定义条件:你可以在 build.rs 或者 Cargo.toml 中定义自己的条件,然后在 cfg() 中使用。

这些条件可以根据实际需要组合使用,以便根据不同的情况编译不同的代码。

  • 外部链接 FFI(Foreign Function Interface)

extern

这段代码指定 my_demo_functionmy_demo_function_alias 从符号表中找名字为my_demo_function的函数链接。

而在 Foo::my_demo_function 中又指定了 #[no_mangle] ,即在编译后的目标文件中的符号名称可见并保持不变,被上面两个 extern 找到并使用。

注意这里利用这个东西,使这个函数的的 private 属性失效了。 FFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// tests9.rs


extern "Rust" {

#[link_name = "my_demo_function"]
fn my_demo_function(a: u32) -> u32;

#[link_name = "my_demo_function"]
fn my_demo_function_alias(a: u32) -> u32;
}

mod Foo {
// No `extern` equals `extern "Rust"`.
#[no_mangle]
fn my_demo_function(a: u32) -> u32 {
a
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_success() {
// The externally imported functions are UNSAFE by default
// because of untrusted source of other languages. You may
// wrap them in safe Rust APIs to ease the burden of callers.
//
// SAFETY: We know those functions are aliases of a safe
// Rust function.
unsafe {
my_demo_function(123);
my_demo_function_alias(456);
}
}
}
  • 分析 traits

    parse() 返回一个 Result<i64, xxxErr> 类型的东西,然后 ? 操作符对它进行处理,要是是 i64,就传给 x 然后正常继续,要是是 Err 就提前结束当前 main() ,调用 from 方法把 Err 自动转成 Box 作为 main 的返回结果?

traits 类似 C++ 中的虚基类和虚函数

1
2
3
4
5
6
fn main() -> Result<(), Box<dyn error::Error>> {
let pretend_user_input = "42";
let x: i64 = pretend_user_input.parse()?;
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
Ok(())
}
  • algorithm

区别 ref mut rootmut &root

04-11

rustlings algorithm5 bfs, algorithm6 dfs

“闭包”

04-12

@所有人 各位同学,第一阶段“Rust编程语言 & Rustlings答疑”第三次课程将于今晚8点开始,今晚的主要内容为crate,option,trait和泛型及生命周期,总共一个课时,请大家预留时间,按时进入课堂!!

上课方式:点击 https://opencamp.cn/os2edu/camp/2024spring/stage/1 链接,签到并进入课堂进行直播上课。

如果同学们有什么问题也可以在问题收集页面https://docs.qq.com/doc/DSXFzRkdodExxQUVO给老师提一下哦

rustlings algorithm7,8 stack, algorithm9 heap, algorithm10 graph

rustlings 完成了

继续学习rust生命周期

表达式 vs. 语句

super::

泛型, trait, 生命周期

trait 作为参数,可以指定多个trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 以下两个代码作用相同

pub fn notify(item: &impl Summary) { // item 为包含了Summary trait的一个引用
println!("Breaking news! {}", item.summarize());
}

pub fn notify<T: Summary>(item: T) { // item 为包含了Summary trait的一个引用
println!("Breaking news! {}", item.summarize());
}

// 多个 trait
pub fn notify(item: &(impl Summary + Display));
pub fn notify<T: Summary + Display>(item: &T);

// where
// 以下两个代码效果相同
fn fun<T: Display + Clone, U: Clone + Debug>(t: &U, u: &U) -> i32 {}

fn fun<T, U>(t: &T, u: &U) -> i32
where
T:Display + Clone,
U: Clone + Debug,
{

}

引用的生命周期

做一做 rust quiz

04-13

使用工具 rustfmt 可以自动将代码格式化。有助于分析代码。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
macro_rules! m {
($($s:stmt)*) => {
$(
{ stringify!($s); 1 }
)<<*
};
}

fn main() {
print!(
"{}{}{}",
m! { return || true },
m! { (return) || true },
m! { {return} || true },
);
}

这个是纯秀语法,没实际意义。

上面定义了一个宏规则 m

$()* 代表匹配一个或多个。

$s:stmt代表 s 是一个 statement。

=> {} 表示匹配到模式后要展开成的代码块规则。

$()<<* 表示对每个 statement 得到的代码块用 << 连接。

关键在于分析三行代码中各自有几个 statement。

第一个 return || true,是一个返回闭包 || true 的 return 语句。

第二个 (return) || true,是一个逻辑或语句。(虽然过不了编译,但是后面是转成字符串所以没事)。

第三个 {return} || true 是两个语句,一个是 {return} 语句块,另一个是 || true 闭包。

展开后,我的理解是这样:

1
2
3
4
5
6
7
8
fn main() {
print!(
"{}{}{}",
{ "return || true"; 1 },
{ "(return) || true"; 1 },
{ "{return}"; 1 }<<{ "|| true"; 1 },
)
}

04-14

继续看看 rust quiz

04-16

去看了吉卜力展

04-17

rust quiz

rcore 实验一

学习:Rust 生命周期

  • 无界生命周期 unsafe

  • 生命周期约束 HRTB

    • 'a: 'b 表示 'a 至少活得跟 'b 一样久

    • T: 'a 类型 T 必须比 'a 活得久

  • 生命周期与子类型

    • 子类型至少比父类型大
  • 引用的生命周期:从借用处开始,直到最后一次使用的地方

  • reborrow

    • let mut p = Point {x: 0, y: 0};
      let r = &mut p;
      let rr: &Point = &*r;
      <!--6-->
      
      * >清空内存前,我们插入了一条奇怪的汇编指令 `fence.i` ,它是用来清理 i-cache 的。 我们知道, 缓存又分成 **数据缓存** (d-cache) 和 **指令缓存** (i-cache) 两部分,分别在 CPU 访存和取指的时候使用。 通常情况下, CPU 会认为程序的代码段不会发生变化,因此 i-cache 是一种只读缓存。 但在这里,我们会修改会被 CPU 取指的内存区域,使得 i-cache 中含有与内存不一致的内容, 必须使用 `fence.i` 指令手动清空 i-cache ,让里面所有的内容全部失效, 才能够保证程序执行正确性。
  • 特权级切换

    • 硬件机制
      • U/S 特权级
      • 相关 CSR
        • sstatus
        • spec
        • scause
        • stval
        • stvec
    • 用户栈、内核栈
    • trap 管理
  • 荣誉准则
  1. 在完成本次实验的过程(含此前学习的过程)中,我曾分别与 以下各位 就(与本次实验相关的)以下方面做过交流,还在代码中对应的位置以注释形式记录了具体的交流对象及内容:

    《你交流的对象说明》

  2. 此外,我也参考了 以下资料 ,还在代码中对应的位置以注释形式记录了具体的参考来源及内容:

    《你参考的资料说明》

  3. 我独立完成了本次实验除以上方面之外的所有工作,包括代码与文档。 我清楚地知道,从以上方面获得的信息在一定程度上降低了实验难度,可能会影响起评分。

  4. 我从未使用过他人的代码,不管是原封不动地复制,还是经过了某些等价转换。 我未曾也不会向他人(含此后各届同学)复制或公开我的实验代码,我有义务妥善保管好它们。 我提交至本实验的评测系统的代码,均无意于破坏或妨碍任何计算机系统的正常运转。 我清楚地知道,以上情况均为本课程纪律所禁止,若违反,对应的实验成绩将按“-100”分计。

04-23

Rust 学习

  • 闭包(匿名函数)

    • 本质:拥有可能关联上下文的匿名函数体

    • 允许捕获被定义时所在作用域中的值(不像函数,必须显式传参)

    • fn add_one_v1     (x: u32) -> u32 { x+1 }
      let add_one_v2 =  |x: u32| -> u32 { x+1 };
      let add_one_v3 =  |x|             { x+1 };
      let add_one_v4 =  |x|               x+1  ;

      后面两个要编译必须使用。

    • 使用 move 强制获取所有权

    • Fn trait

      • FnOnce
      • FnMut
      • Fn
  • 智能指针

    • Box -> 将值放在堆上而非栈上
      • 针对编译时位置大小的类型
      • 有大量数据并确保数据不被拷贝的情况下转移所有权时
      • 拥有一个值并只关心它的类型是否实现了特定trait而非具体类型时
      • Deref trait
    • Rc -> 引用计数
      • clone
      • 克隆 Rc<T> 会增加其引用计数
      • 不可变引用
    • RefCell -> 内部可变性指针
      • Interior mutability
        • 该数据结构中使用了 unsafe 代码来模糊 Rust 通常的可变性和借用规则
      • 任意时刻,只能拥有一个可变引用或任意数量的不可变引用之一
      • 引用必须总是有效的
  • async