0%

why?

去年秋冬季已经参加过一次训练营了,今年第二次参加,感觉和去年相比,今年的整体流程更加完善了,相应的训练题目也更加合理了。
本身是非科班出身的,一直以来有一个愿望就是弄清计算机是如何运行的,如何从一个个逻辑门到我们所看到的PC,尤其是在软硬件接口处,OS是软件中距离软硬件接口最近的部分,更像是软件领域的“大管家”,如果说编程是创造的过程,那OS就是编程世界的上帝,上帝为程序员准备好了一切支持,程序员才可以进行创造,所以,OS is charming!每个程序员都应该在创造的基础上更进一步,做自己的god。

Rust

没有最好的编程语言,也没有最好的编程模型,只有最适合的。编程语言的设计需要平衡两件事情:运行速度与抽象程度。越抽象的,越贴近人类思想的,运行速度大概率就越慢,极端情况就是脚本语言。越贴近计算机思想的,运行速度就越快,但是编写起来就越麻烦,极端情况就是二进制指令。
在编程的时候,我们不仅要考虑业务逻辑的实现,还要为自己所写的代码的稳定性和安全性负责,其中最常见的问题就是内存泄漏和并发问题,为了更加贴近人类思想,有些语言会加入GC垃圾处理机制,这种机制可以帮我们实现内存安全,但是代价就是速度,为了并发安全,Python中直接使用了GLI锁,代价也是速度。

C++是什么都没加,是够快,但是代价就是,程序员要负责一切,只有一些基础的安全保障。
Rust的目的就是,在尽量不影响速度的前提下,实现内存安全和并发安全。
但是实际上,有一些算法或者业务逻辑的实现,是不可能在Rust所定义的安全情况下完成的,针对这种情况,Rust提供了unsafe模块,在unsafe包裹的代码中,程序员有更大的自由度,但也不是绝对自由的。

所以,归根结底,想要做到写出的代码具有稳定性以及安全性,Rust只是为我们提供了一个更加完善的支持,但是我们还是要心中对于内存安全和并发安全有相关的概念,才能做到真正安全,GAMES101的闫令琪老师在课上说,API和知识是两码事,学习API不能替代学习知识,对于Rust我想也是这样的,学习Rust的各种语法设置以及安全设置这很重要,但是他们为什么这么设计,为什么这样能实现内存安全和并发安全,这才是更重要的。对于不得不自己实现unsafe代码的这种情景,我不知道是否符合抽象泄漏的定义,但是我想这种不得不亲自上手的感觉是差不多的。Rust就像是我们雇的一个大厨,跟着大厨做饭肯定色香味俱全,但是大厨不可能在任何时间任何地点都陪在我们身边,这种情况就要检验我们自己做饭的水平了。

That is all.

总结

去年有幸参加过2023年球季的操作系统训练营,当时学的较为粗糙,rust中的很多重要的概念都掌握的不是很深刻,经过这一年的学习,感觉自己对rust精进了不少,希望今年能顺利完成训练营。

第一阶段的学习让我对rust有了更深的理解,尤其是智能指针和生命周期的部分,这次的训练同样也是复习之前的内容,为接下来rcore的学习打好基础。

2024春季开源操作系统训练营第一阶段总结

Motivation

刚好最近想要做一些OS相关的工作,又趁着OS训练营开营,于是赶着ddl把rustling干完了。在以前的学习中,常用C/C++,这次新学习了Rust语言,编程习惯还是有些不同的,要花大功夫去掌握难点。

学习过程

我主要使用了《Rust 程序设计语言》官方文档作为学习指南,并结合rustling进行知识点的学习和巩固。开始的基础语法较容易,很快就做完了,但是到后面的tests和algorithm部分,难度增大了,花费了不少时间才完成题目呜呜呜。
总体来说在第一阶段这个学习过程还是挺顺利的,因为网络平台上存在许多大量的资源供学习参考。

感想

在老师的悉心指导下和交流群互帮互助下,从最开始的 Hello World 起步,到深入系统地学习了操作系统的基本概念、原理,这使我对Rust和OS 有了更加全面和深入的理解。最后,衷心感谢两位教学讲师对我们的授课讲学。

展望

希望我能把三阶段给坚持下来吧~

Rust学习感悟

早在今年寒假时就打算学习Rust,奈何一直没有开工。四月份在群里偶然看到了2024春夏季开源操作系统训练营,这才算是开始了Rust学习之旅。

我的Rust入门学习主要是通过阅读和练习两点。

阅读其一是Rust 语言圣经

阅读其二是The Rust Programming Language

练习其一是Rust By Practice

练习其二则是训练营第一阶段的rustlings

  • Github: GitHub - rust-lang/rustlings: :crab: Small exercises to get you used to reading and writing Rust code!

  • intro ✅ 2024-04-09

  • variables ✅ 2024-04-09

  • functions ✅ 2024-04-11

  • if ✅ 2024-04-11

  • primitive_types ✅ 2024-04-11

  • vecs ✅ 2024-04-16

  • primitive_types ✅ 2024-04-16

  • move_semantics ✅ 2024-04-16

  • structs ✅ 2024-04-16

  • enums ✅ 2024-04-16

  • strings ✅ 2024-04-16

  • modules ✅ 2024-04-16

  • options ✅ 2024-04-16

  • hashmaps ✅ 2024-04-16

  • error_handing ✅ 2024-04-16

  • generics ✅ 2024-04-20

  • lifetimes ✅ 2024-04-20

  • traits ✅ 2024-04-20

  • iterators ✅ 2024-04-20

  • tests ✅ 2024-04-21

  • macros ✅ 2024-04-21

  • threads ✅ 2024-04-21

  • clippy ✅ 2024-04-21

  • conversions ✅ 2024-04-21

  • smart_pointers ✅ 2024-04-21

  • quiz ✅ 2024-04-21

  • algorithms ✅ 2024-04-22

在此过程中,Rust的所有权(Ownership)、生命周期(Lifetimes)、特型(Traits),以及函数式编程中常见的模式匹配(Patterns and Matching)等概念给我留下了深刻的印象。当然,最为深刻的还是Rust的编译器。

我认为在编写极为熟练的代码时,Rust严苛的编译器确实拖慢了我的编码速度(例如写个链表那叫一个费劲),但是从另一方面看,它尽可能地保证了我写出没有歧义的、不会在运行时错误的更健壮的代码。

总的来说,Rust的入门体验还不错,希望在接下来的学习中,我能更进一步地掌握它的特性,以及它在操作系统中内核级代码的应用。

2024 开源操作系统训练营第一阶段总结

rust 学习历程

以前是做 c 和 java 开发的,也使用过 js、python 等语言,无意中接触到 rust,被宣称的安全和性能吸引,资料东拼西凑学习,写各种小工具,因为概念很多,而且写程序编译器各种报错,差点被劝退,想着既然学习了,就再坚持下,接触 rust 一段时间,慢慢喜欢上了这门语言,编译器提示的很详细,而且只要编译成功后,程序的运行机会不会有问题,rust 是一门上线很高,下线也很低的一门语言,之前都是用 rust 写写小工具,都是在应用层面的开发,这次看到开源操作系统训练营报名,果断报名了,想去深入到新的领域,接触新的东西。

参加了训练营一阶段的培训,跟着老师的教学内容,动手实践,又有 rustlings 的练手,自己对 rust 的理解又更加进了一步,学习到了很多内容,和之前自己自学不太一样,这次又老师的讲解,很多疑惑一扫而光。在做 rustlings 实践中,前面的题都很轻松解决,到了算法的内容时,被卡住了很久,一个是算法很久没有接触了,这个在网上搜搜内容能很快熟悉起来,难得是 rust 的所有权机制以及指针这块,自己之前没怎么接触指针模块,总是卡在编译上,不断的查资料,最终磕磕绊绊总算搞完了,确实学到了很多内容,短短的三周时间过完了,觉得很充实。

总结

rust 现在是我喜欢的语言,到现在自己感觉还是没有入门,我一定会继续学习和使用这们语言工具,去拓展更多的领域空间。

基础阶段报告 yyh929

前言

RSICV架构是目前我接触最多的指令架构,学习过RISCV处理器的设计,但没有深入了解/写过RISCV操作系统,正好借训练营补足。

阶段总结

基础阶段主要是rust语法的学习。
之前了解到,Rust 以性能著称,可以在语言级别支持并发和并行编程,并且有严格的内存管理,适合编写对于内存安全和性能有很高的要求的程序。
对我而言,rust中的所有权,生命周期,智能指针,闭包,并发编程是比较新的概念。
基础阶段的学习还是挺精彩的,看着《Rust 程序设计语言 简体中文版》做完了rustlings,只有后面数据结构的题目比较有难度。
但是感觉做完全部题目还算不算真正入门rust,之后还会再输入研读一下《Rust语言圣经》。期待后面训练营的OS课。

耗时两周的工作日。

看课程结合rust圣经一起学,收获还行。虽然到目前为止,几乎没有一次是写完代码就直接能够通过编译器检测的。

不过写rust的一个好处就是,通过了编译器基本上代码也就不太愁了,涉及到裸指针这种也有unsafe 知道要着重检查哪里。还记得之前csapp的一个lab用c语言写malloc函数的底层的时候,用了半天的时间写完,结果找bug找了将近两天。指针飞舞,飞到这飞到那,都不知道自己咋错的。

还有一个很nice的地方就是不能隐式类型转换,并且溢出也会报错,参加程序设计竞赛用c++写题目的时候,已经不知道有多少罚时是因为整形溢出造成的了。

不过生命周期真的感觉很头大,现在也还不能够完全把握住。

然后这次写rustlings的时候一开始看到algorithm里面要用到链表的时候就去看了圣经部分的链表,当时看了两天,只感觉,emm这也太难了吧,那各种考虑因素捋了好久才勉强想通一点,到后面还要实现图论,感觉起飞。但真正去看题目的时候,才发现,nm,这直接用的裸指针实现的,后面建图也是用的邻接表建图。。。花了半天多一点的时间搞完就收工了。

接下来就是riscv的学习,之前看csapp学过x86 就把risc-v手册用了半天跳着通读了一下。感觉risc-v寄存器多这一点很舒服,基本上不太用放入栈。然后看了下特权机制,这个之前csapp的x86没说。

基本数据类型-原生类型

image-20240410195209385

  • 元组(tuple)

​ 允许各个元素类型不相同

1
2
3
fn main(){
let tup: (i32,f64,u8) = (500,6.4,1);
}
  • 字符串
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// quiz2.rs
//
// This is a quiz for the following sections:
// - Strings
// - Vecs
// - Move semantics
// - Modules
// - Enums
//
// Let's build a little machine in the form of a function. As input, we're going
// to give a list of strings and commands. These commands determine what action
// is going to be applied to the string. It can either be:
// - Uppercase the string
// - Trim the string
// - Append "bar" to the string a specified amount of times
// The exact form of this will be:
// - The input is going to be a Vector of a 2-length tuple,
// the first element is the string, the second one is the command.
// - The output element is going to be a Vector of strings.
//
// No hints this time!

// I AM NOT DONE

pub enum Command {
Uppercase,
Trim,
Append(usize),
}

mod my_module {
use super::Command;

// TODO: Complete the function signature!
pub fn transformer(input: Vec<(String,Command)>) -> Vec<String> {
// TODO: Complete the output declaration!
let mut output: Vec<String> = vec![];
for (string, command) in input.iter() {
// TODO: Complete the function body. You can do it!
match command{
Command::Uppercase => output.push(string.to_uppercase()),
Command::Trim => output.push(string.trim().to_string()),
Command::Append(n) => {
let mut append_stirng = string.clone();
for _ in 0..*n {
append_stirng += "bar";
}
output.push(append_stirng);
}
}

}
output
}
}

#[cfg(test)]
mod tests {
// TODO: What do we need to import to have `transformer` in scope?
use super::my_module::transformer;
use super::Command;

#[test]
fn it_works() {
let output = transformer(vec![
("hello".into(), Command::Uppercase),
(" all roads lead to rome! ".into(), Command::Trim),
("foo".into(), Command::Append(1)),
("bar".into(), Command::Append(5)),
]);
assert_eq!(output[0], "HELLO");
assert_eq!(output[1], "all roads lead to rome!");
assert_eq!(output[2], "foobar");
assert_eq!(output[3], "barbarbarbarbarbar");
}
}
  • String

image-20240414142656028

  • "rust is fun!".to_owned() 是一个字符串字面量(string literal),.to_owned() 是一个字符串切片(&str)的方法,用于将字符串切片转换为一个拥有所有权的 String 类型的对象。这个方法的作用是创建一个新的 String 对象,其中包含了字符串切片的内容,同时拥有了自己的内存空间,与原始的字符串切片无关。

  • "nice weather".into() 是 Rust 中的一个特殊的语法,它实际上是调用了 From trait 中的实现,将一个类型转换为另一个类型。在这种情况下,"nice weather".into() 将一个字符串字面量(&str 类型)转换为 String 类型。这种转换是通过实现了 From<&str> for String 的 trait 来完成的,它会将给定的字符串切片(&str)复制到一个新的 String 对象中,并返回这个新对象。这种转换通常被称为”类型推导”,因为编译器会根据上下文自动推导出需要转换的目标类型。

  • Option

    和match配合

    image-20240417114555485

  • 通配符

image-20240417114753525

  • if let 简介控制流

相当于二元控制流

  • 泛型

image-20240417120202658

  • Trait(interface)

image-20240417120533444

(1) trait作为参数

image-20240417230445201

小计:

.fold()

1
2
3
4
5
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("The sum is: {}", sum); // 输出:The sum is: 15
}

.fold()迭代器方法

1
2
3
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,

这里的参数含义是:

  • init 是初始值,它是要合并的类型的默认值或起始状态。
  • f 是一个闭包函数,它接受两个参数:累积值(accumulator)和当前迭代的元素,并返回一个新的累积值。

.fold() 方法会迭代迭代器的每个元素,并在每次迭代中调用闭包函数 f,将当前累积值和当前元素作为参数传递给闭包函数,然后将闭包函数的返回值作为新的累积值。最终,.fold() 方法返回的值是所有元素被合并后得到的单个值。

智能指针

通常用结构体来实现,不同之处在于实现了DerefDrop traitDereftrait 允许智能指针结构体示例表现的像引用一样,Drop trait允许我们自定义当智能指针离开作用域时运行的代码

  • Box

box允许将值直接放到到堆上

递归类型

cons list

每一项都包含两个元素:当前的值和下一项。其最后一项

  • 计算非递归类型的大小

image-20240421090726614

Rust知道为Message分配多少空间时,他会检查每一个成员,并发现quit并不需要任何空间,Move需要两个i32空间,依此类推

Deref

实现Deref允许我们重载解引用运算符* 我们无法直接使用指针所指向的数据,需要通过解引用运算符

Drop

制定在值离开作用域时应该执行Droptrait。一般是自动进行。

当我们想手动执行,就可以使用公用的drop

image-20240421092345178

Rc< T >智能指针

引用计数

  • Rc< T >来共享数据

image-20240421092803615

  • 可以用来计数

image-20240421092829590

会打印出引用计数,可以调用strong_count函数获得

但是,虽然可以共享数据,其还是没有违反借用和所有权的定义,只是使别的变量具有a变量的可读性

RefCell < T >和内部可变性

  • RefCell 大量使用unsafe代码来模糊rust规则,要手动检查是否符合规则

结合Rc < T >和 RefCell < T > 来拥有多个可变数据所有者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
let value = Rc::new(RefCell::new(5));

let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

*value.borrow_mut() += 10;

println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}

可以拥有一个表面上不可变的List,不过可以使用RefCell< T>中提供内部可变性的方法来再需要时修改

多线程

spawn创建多线程

image-20240421094452825

线程间传送数据

提供了信道(channel)来实现。信道分为发送者和接收者

image-20240421103518959

可以允许多个发送者 但是只能有一个接收者
类比java

信道与所有权转移

send函数会获取其参数的所有权并移动这个值归接收者所有。

image-20240421104012845

不想所有权转移:

互斥器Mutex< T >

image-20240421104351246

同步和异步async和多线程

异步返回的都是future

.await异步机制的实现 更像

Macro)指的是 Rust 中一系列的功能:使用 macro_rules!声明Declarative)宏,和三种 过程Procedural)宏:

  • 自定义 #[derive] 宏在结构体和枚举上指定通过 derive 属性添加的代码
  • 类属性(Attribute-like)宏定义可用于任意项的自定义属性
  • 类函数宏看起来像函数不过作用于作为参数传递的 token

为什么已经有了函数还需要宏呢?

宏是一种代码生成器,允许在编译时对代码进行操作和生成,可以在更大的范围内改变代码结构和行为。在某些情况下,使用宏可以提高代码的灵活性和效率,但同时也会增加代码的复杂性和难以理解性。

总结

怀着java的心来学rust,在很多情况下,rust所有权问题总是是一个大坑,rust是对底层深入的语言,相比于java非常多的工具链,rust给我最大的感觉就是如同汽车修理工那样,一个零件一个零件的卸下和装配。说实话,时间太短了,很多概念是第一次接受,并没有特别深入的了解,之后会尽力去做的。

感想:rust还是得多上手实践才行
一些笔记如下:

Variables

  • Rust中变量有严格的初始化要求

  • 在 Rust 中,变量的类型是静态类型的

  • 可以使用隐藏,重新声明一个变量来更改其类型

  • const声明常量,且必须包括类型

    Primitive Types

  • 数组切片,切片是引用,&a[1..5]

  • 元组按索引寻址,x.0

    Vectors

  • 初始化vec的宏为vec![]

  • vec2.rs,遍历vec

    Move Semantics

  • 使用vec.clone()可以创造一个新的对象

  • 在任何给定时间点,你只能拥有一个可变引用(不转移所有权的情况下修改数据)

  • String类型的所有权

    Structs

  • 三种结构体初始化方法

  • 从另一个结构体更新{..user}

    Enums

  • 定义枚举时可以将数据附加到枚举成员中,类似于结构体

  • match匹配时要加入结构体成员数据类型

    Strings

  • 可以用+号连接字符串

  • 区分&str和String

    Modules

  • 默认为private

  • 借用与结构体中的声明

    Options

  • if let语句

    Errors

  • 传播错误使用?, 出现错误直接抛出,需要与返回值兼容

  • Box智能指针与Trait

  • unwrap和expect可以解开Ok中的值或者报错

    Generics

  • 泛型,在方法处也是impl

    Traits

  • 类似于Java中的接口和C++中的抽象类

  • Trait可以用作参数来标识实现了Trait的类型 impl Trait和&dyn Trait,前者接受拥有所有权的实现类型,后者接受对实现该类型的不可变引用,可以用+号指定多个Trait,impl Trait1 + Trait2

    Lifetime

  • 确保引用有效,不出现悬垂引用

  • 需要在函数名处先定义,然后再引用,结构体中也是如此

    Tests

  • 使用#[should_panic]来检查panic

    Iterators

  • iterators3.rs 函数式

  • collect::Type 显式确定要收集的类型

  • (1..=num).fold(1, |acc, x| acc * x)

  • map, filter, sum

    Smart Pointers

  • ref with some metadata and capabilities

  • 普通引用是借用,在大部分情况下,智能指针 拥有 它们指向的数据

  • 实现了deref和drop的trait

  • Box,解决循环类型定义,因为编译时需要确定数据空间占用

  • Rc,Solar System: Planet and the Sun

  • Cow,灵活地确定借用还是拥有

    Threads

  • thread::spawn(move || {})

    Macros

  • 使用macro_rules!声明宏

  • #[macro_export]导出宏