-
Notifications
You must be signed in to change notification settings - Fork 387
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 707state rCore blog
- Loading branch information
Showing
2 changed files
with
264 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
--- | ||
title: 2024秋操作系统训练营第一阶段总结——707state | ||
date: 2024-11-07 23:32:00 | ||
tags: | ||
- author:707state | ||
- repo:https://github.com/LearningOS/2024a-rcore-707state | ||
--- | ||
|
||
# Rustling总结 | ||
|
||
我想成为Rustacean,通过rustling的练习,我学到一些没接触过的Rust知识 | ||
|
||
# repr attribute | ||
|
||
在c/c++中,常常使用__attribute__((align))的方式来确保内存对齐,repr在rust的功能久石让每一个数据能够按照k的整数倍分配,k通常是基本类型。 | ||
|
||
|
||
# Rusty 阶乘 | ||
|
||
```rust | ||
pub fn factorial(num: u64) -> u64 { | ||
(1..=num).fold(1, |acc, x| acc * x) | ||
} | ||
``` | ||
|
||
范围 (1..=num): | ||
|
||
这个表达式创建了一个从 1 到 num 的范围,包括 num 本身。..= 是一个闭区间语法,表示这个范围是包含起始值和结束值的。 | ||
|
||
fold 方法: | ||
|
||
fold 是一个高阶函数,它用于将一个迭代器中的所有元素合并成一个单一的值。 | ||
它的第一个参数是初始值,在这里是 1。这个值会作为累加器的初始状态。 | ||
第二个参数是一个闭包(匿名函数),它接受两个参数:acc(累加器的当前值)和 x(迭代器中的当前值)。 | ||
在每一次迭代中,闭包会将 acc 和 x 相乘,并返回新的累加器值。 | ||
|
||
# 流敏感分析 | ||
|
||
依靠 Borrow Checker 确保内存安全,原理如下: | ||
|
||
执行路径分析:Rust 编译器在编译过程中会为每个变量跟踪其借用状态或所有权。在程序的不同控制流(如条件判断、循环、函数调用等)中,编译器会检查在这些不同路径下变量的状态变化,并在所有路径中保持一致性。 | ||
|
||
数据流分析:Rust 编译器通过数据流分析来确定变量的生存期、是否被借用、以及是否存在竞争条件。这个分析是流敏感的,意味着它会根据程序的控制流更新变量状态。 | ||
|
||
作用域检查:编译器在分析时会检查变量是否在作用域内、是否已经被销毁或者转移所有权。通过流敏感分析,编译器能够精确确定哪些变量在某条执行路径中被有效引用或借用。 | ||
|
||
|
||
# take | ||
|
||
在 Rust 中,take() 方法通常用于在某些容器类型(如 Option、Result 等)中“取走”值,将原来的值替换为一个默认值(通常是 None 或 Err),同时返回原来的值。 | ||
|
||
take() 经常用于以下场景: | ||
|
||
转移所有权:从一个可选值中取出所有权,并清空该值,避免复制或克隆。 | ||
|
||
链表或树结构:当你遍历或修改链表或树结构时,可以用 take() 来“取走”节点的引用并避免借用冲突。 | ||
|
||
# zero-cost futures | ||
|
||
async/await实现的future类型不会引入任何额外的运行时开销。 | ||
|
||
无运行时依赖:与其他编程语言(如 JavaScript 或 Python)不同,Rust 的 async/await 本身不依赖特定的运行时机制。Future 是惰性的,它本质上是一个状态机,只有在被轮询时才会前进。虽然需要某种形式的运行时(如 Tokio 或 async-std)来调度异步任务,但这些运行时并没有与 async/await 特性本身紧耦合。 | ||
|
||
# Any Trait | ||
|
||
Rust中的Any trait允许在运行时进行类型检查和类型转换。这个类型在处理动态类型时较为有用。 | ||
|
||
功能: | ||
|
||
1. 类型检查:通过is::<T>()方法,可以检查一个值是否是某种特定类型。 | ||
|
||
2. 类型转换:使用downcast_ref::<T>()和downcast::<T>()方法,可以将一个&dyn Any或者Box<dyn Any>类型的值转换回具体类型T。 | ||
|
||
注意:Any trait只能用于'static 生命周期的类型,这意味着他不能用于包含非静态引用的类型。 | ||
|
||
并且,Any会引入运行时开销,因为它依赖动态分派。 | ||
|
||
|
||
# UnsafeCell | ||
|
||
Rust 中内部可变性的核心原语。 | ||
|
||
如果您使用的是 &T,则通常在 Rust 中,编译器基于 &T 指向不可变数据的知识来执行优化。例如通过别名或通过将 &T 转换为 &mut T 来可变的该数据,被认为是未定义的行为。 UnsafeCell<T> 选择退出 &T 的不可变性保证:共享的引用 &UnsafeCell<T> 可能指向正在发生可变的数据。这称为内部可变性。 | ||
|
||
所有其他允许内部可变性的类型,例如 Cell<T> 和 RefCell<T>,在内部使用 UnsafeCell 来包装它们的数据。 | ||
|
||
所有其他允许内部可变性的类型,例如 Cell<T> 和 RefCell<T>,在内部使用 UnsafeCell 来包装它们的数据。 | ||
|
||
UnsafeCell API 本身在技术上非常简单: .get() 为其内容提供了裸指针 *mut T。正确使用该裸指针取决于您。 | ||
|
||
如果您使用生命周期 'a (&T 或 &mut T 引用) 创建安全引用,那么您不得以任何与 'a 其余部分的引用相矛盾的方式访问数据。 例如,这意味着如果您从 UnsafeCell<T> 中取出 *mut T 并将其转换为 &T,则 T 中的数据必须保持不可变 (当然,对 T 中找到的任何 UnsafeCell 数据取模),直到引用的生命周期到期为止。 同样,如果您创建的 &mut T 引用已发布为安全代码,则在引用终止之前,您不得访问 UnsafeCell 中的数据。 | ||
|
||
|
||
|
||
对于没有 UnsafeCell<_> 的 &T 和 &mut T,在引用过期之前,您也不得释放数据。作为一个特殊的例外,给定一个 &T,它在 UnsafeCell<_> 内的任何部分都可能在引用的生命周期期间被释放,在最后一次使用引用之后 (解引用或重新借用)。 因为您不能释放引用指向的部分,这意味着只有当它的每一部分 (包括填充) 都在 UnsafeCell 中时,&T 指向的内存才能被释放。 | ||
|
||
但是,无论何时构造或解引用 &UnsafeCell<T>,它仍必须指向活动内存,并且如果编译器可以证明该内存尚未被释放,则允许编译器插入虚假读取。 | ||
|
||
在任何时候,您都必须避免数据竞争。如果多个线程可以访问同一个 UnsafeCell,那么任何写操作都必须在与所有其他访问 (或使用原子) 相关之前发生正确的事件。 | ||
|
||
为了帮助进行正确的设计,以下情况明确声明为单线程代码合法: | ||
|
||
&T 引用可以释放为安全代码,并且可以与其他 &T 引用共存,但不能与 &mut T 共存 | ||
|
||
&mut T 引用可以发布为安全代码,前提是其他 &mut T 和 &T 都不共存。&mut T 必须始终是唯一的。 | ||
|
||
|
||
请注意,虽然可以更改 &UnsafeCell<T> 的内容 (即使其他 &UnsafeCell<T> 引用了该 cell 的别名) 也可以 (只要以其他方式实现上述不变量即可),但是具有多个 &mut UnsafeCell<T> 别名仍然是未定义的行为。 也就是说,UnsafeCell 是一个包装器,旨在通过 &UnsafeCell<_> 与 shared accesses (i.e. 进行特殊交互 (引用) ; 通过 &mut UnsafeCell<_> 处理 exclusive accesses (e.g. 时没有任何魔术) : 在该 &mut 借用期间, cell 和包装值都不能被别名。 | ||
|
||
.get_mut() 访问器展示了这一点,该访问器是产生 &mut T 的 safe getter。 | ||
|
||
UnsafeCell<T> 与其内部类型 T 具有相同的内存表示。此保证的结果是可以在 T 和 UnsafeCell<T> 之间进行转换。 将 Outer<T> 类型内的嵌套 T 转换为 Outer<UnsafeCell<T>> 类型时必须特别小心: 当 Outer<T> 类型启用 niche 优化时,这不是正确的。 | ||
|
||
|
||
# std::mem::MaybeUninit | ||
|
||
Rust 编译器要求变量要根据其类型正确初始化。 | ||
|
||
比如引用类型的变量必须对齐且非空。这是一个必须始终坚持的不变量,即使在 Unsafe 代码中也是如此。因此,零初始化引用类型的变量会导致立即未定义行为,无论该引用是否访问过内存。 | ||
|
||
编译器利用这一点,进行各种优化,并且可以省略运行时检查。 | ||
|
||
由调用者来保证MaybeUninit<T>确实处于初始化状态。当内存尚未完全初始化时调用 assume_init() 会导致立即未定义的行为。 | ||
```rs | ||
|
||
#![allow(unused)] | ||
|
||
fn main() { | ||
use std::mem::{self, MaybeUninit}; | ||
// 不符合:零初始化引用 | ||
let x: &i32 = unsafe { mem::zeroed() }; // undefined behavior! ⚠️ | ||
// 等价于 `MaybeUninit<&i32>`: | ||
let x: &i32 = unsafe { MaybeUninit::zeroed().assume_init() }; // undefined behavior! | ||
// 不符合:布尔值必须初始化 | ||
let b: bool = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️ | ||
// 等价于 `MaybeUninit<bool>`: | ||
let b: bool = unsafe { MaybeUninit::uninit().assume_init() }; // undefined behavior! | ||
// 不符合:整数类型也必须初始化 | ||
let x: i32 = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️ | ||
// 等价于 `MaybeUninit<i32>`: | ||
let x: i32 = unsafe { MaybeUninit::uninit().assume_init() }; | ||
|
||
// 不符合:Vec未初始化内存使用 set_len 是未定义行为 | ||
let mut vec: Vec<u8> = Vec::with_capacity(1000); | ||
unsafe { vec.set_len(1000); } | ||
reader.read(&mut vec); // undefined behavior! | ||
} | ||
``` | ||
|
||
|
||
主要就是这些收获。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
--- | ||
title: 2024秋操作系统训练营第二阶段总结——707state | ||
date: 2024-11-07 23:40:45 | ||
tags: | ||
- author:707state | ||
- repo:https://github.com/LearningOS/2024a-rcore-707state | ||
--- | ||
|
||
# 总结 | ||
|
||
去年就已经参加一次了,但是止步第一阶段,今年能做完第二阶段已经出乎我的意料了,收获良多。 | ||
|
||
# syscall | ||
|
||
syscall是怎么发起的,系统调用时发生了哪些事情,这些都是第一次接触。 | ||
|
||
## 收获 | ||
|
||
## spawn实现 | ||
|
||
如果不考虑其他因素,其实spawn类似于vfork+exec。 | ||
|
||
假定fork采用的是直接复制的策略,那vfork就是采用的阻塞父进程执行、获得父进程地址空间的引用、子进程运行、执行完成、恢复父进程。 | ||
|
||
|
||
## fork 调用 | ||
|
||
主要部分是调用TaskControlBlock::fork, 返回父进程的TaskControlBlock的拷贝,并分配一个新的Pid。 | ||
|
||
## Task部分各个结构的关系 | ||
|
||
### TaskControlBlock | ||
|
||
TaskControlBlock 代表一个任务或进程的控制块,用于存储任务的基本信息和状态。它包含了所有不会在运行时改变的内容,如进程ID (pid) 和内核栈 (kernel_stack)。此外,它还包含一个可变的 inner 字段,该字段封装了实际的任务状态信息: | ||
|
||
pid:任务的唯一标识符。 | ||
kernel_stack:内核栈,用于保存该任务在内核态运行的栈信息。 | ||
inner:包含该任务的动态状态信息,用于存储在运行中可能变化的内容,如内存空间、任务上下文、进程树信息等。 | ||
|
||
### TaskControlBlockInner | ||
|
||
TaskControlBlockInner 是 TaskControlBlock 的内部状态结构体,用于存储运行期间动态变化的内容,如任务的上下文、内存管理、父子关系等。每个 TaskControlBlockInner 都包含以下字段: | ||
|
||
trap_cx_ppn:存储陷入上下文(Trap Context)的物理页号,用于保存用户态的CPU上下文。 | ||
base_size:应用程序的基本大小,用于约束任务在内存中的地址空间。 | ||
task_cx:任务上下文,表示当前任务的 CPU 状态。 | ||
task_status:当前任务的状态(如 Ready、Running、Zombie)。 | ||
memory_set:用于管理该任务的地址空间。 | ||
parent 和 children:当前任务的父子进程关系。 | ||
exit_code:任务退出时的状态码。 | ||
heap_bottom 和 program_brk:用于管理堆内存的范围。 | ||
|
||
### TaskManager | ||
|
||
负责调度所有准备好运行的Task,它维护了一个 ready_queue 队列,包含了所有准备好运行的任务的 TaskControlBlock,从中取出任务并将其交给调度器: | ||
|
||
ready_queue:一个队列,存储处于“Ready”状态的任务。 | ||
|
||
add 和 fetch:add 将任务添加到 ready_queue 中,fetch 从队列中取出任务进行调度。 | ||
|
||
# 文件系统 | ||
|
||
## 目录项 | ||
|
||
目录项 | ||
|
||
对于文件而言,它的内容在文件系统或内核看来没有任何既定的格式,只是一个字节序列。目录的内容却需要遵从一种特殊的格式,它可以看成一个目录项的序列,每个目录项都是一个二元组,包括目录下文件的文件名和索引节点编号。 | ||
|
||
```rust | ||
// easy-fs/src/layout.rs | ||
|
||
const NAME_LENGTH_LIMIT: usize = 27; | ||
|
||
#[repr(C)] | ||
pub struct DirEntry { | ||
name: [u8; NAME_LENGTH_LIMIT + 1], | ||
inode_number: u32, | ||
} | ||
|
||
pub const DIRENT_SZ: usize = 32; | ||
|
||
impl DirEntry { | ||
pub fn empty() -> Self; | ||
pub fn new(name: &str, inode_number: u32) -> Self; | ||
pub fn name(&self) -> &str; | ||
pub fn inode_number(&self) -> u32 | ||
} | ||
``` | ||
|
||
在从目录中读取目录项,或将目录项写入目录时,需要将目录项转化为缓冲区(即字节切片)的形式来符合 read_at OR write_at 接口的要求 | ||
|
||
```rust | ||
// easy-fs/src/layout.rs | ||
|
||
impl DirEntry { | ||
pub fn as_bytes(&self) -> &[u8] { | ||
unsafe { | ||
core::slice::from_raw_parts( | ||
self as *const _ as usize as *const u8, | ||
DIRENT_SZ, | ||
) | ||
} | ||
} | ||
pub fn as_bytes_mut(&mut self) -> &mut [u8] { | ||
unsafe { | ||
core::slice::from_raw_parts_mut( | ||
self as *mut _ as usize as *mut u8, | ||
DIRENT_SZ, | ||
) | ||
} | ||
} | ||
} | ||
``` |