0%

阶段目标与晋级条件

  • Rust编程语言为学习操作系统设计与实现打下坚实基础

  • 通过完成100道Rustling编程题,强化训练Rust编程技能

  • 该阶段排行榜达到满分可晋级,进入专业阶段学习

阶段个人总结

Rust 语言是参加本训练营必须掌握的一门语言,因为本训练营的所有项目均基于本语言编写。由于本人已学习过 Rust 编程语言,故本阶段无太大压力,跟着晋级要求完成 rustlings习题后即达成目标。

而重点是后续专业阶段 rCore 操作系统的学习与代码理解,因操作系统相关知识早已忘记(虽然曾跟着网络教程阅读过 Linux 内核源码,但年代久远早已忘记细节),故需要一定时间重拾相关知识,并跟着实验在旧知识基础上提升自己的理解水平。

附:本人学习 Rust 语言的主要参考视频教程

第一阶段 - rust基础与算法

由于我有rust基础,没有遇到什么困难,但还是第一次完整地做一次rustlings,有一定收获

第二阶段 - 专业阶段

实验环境配置

rcore开发的环境配置比较繁琐,而我恰好比较熟悉nix,因此我用nix定义的一个完美兼容rcore的开发环境。由于不需要 rustup , 必须在 os/Makefile 中禁用 rust 环境的检测. flake.nix 如下:

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
{
description = "rCore dev flake";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
oldnixpkgs.url = "github:NixOS/nixpkgs/7cf5ccf1cdb2ba5f08f0ac29fc3d04b0b59a07e4";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};

outputs = {
nixpkgs,
oldnixpkgs,
flake-utils,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
oldpkgs = import oldnixpkgs {
inherit system;
};
in {
devShells.default = pkgs.mkShell {
packages = with pkgs;
[
(rust-bin.nightly."2024-05-02".minimal.override {
extensions = [
"rust-src"
"llvm-tools"
"rustfmt"
"rust-analyzer"
"rust-docs"
"clippy"
];
targets = ["riscv64gc-unknown-none-elf"];
})
cargo-binutils
python3
gdb
tmux
]
++ [oldpkgs.qemu];

# 进入环境后显示rust和qemu版本
shellHook = ''
rustc --version
cargo --version
qemu-system-riscv64 --version
qemu-riscv64 --version
'';
};
});
}

这份 flake.nix 也已经分享到官方问答论坛

第一/二章

阅读这两章需要先了解riscv,特别是特权级相关设计。

主要参考:

第三章:多道程序与分时多任务

学习了系统调用,陷入等知识,上下文切换过程中通用寄存器和CSR的使用加深了我对riscv特权级设计的理解。

本章练习较为简单。

第四章:地址空间

SV39的设计又引入了若干相关的寄存器,如satp, pmp csr。查阅riscv manul以加深理解。

本章练习中,为了处理请求映射已经被映射的页的错误,我使用了Result错误传递,无法想象如果不使用Result?语法糖我的代码会多么丑陋。然而很奇怪,整个rcore中极少使用Result

第五章:进程及进程管理

本章内容比较轻松,完善了TCB的设计并实现fork()exec()系统调用。

本章练习也比较简单。

第六章:文件系统

easy-fs is NOT easy!层层抽象几乎让我晕头转向!

尽管如此,easy-fs囊括了superblock、索引节点、blockcache等现代文件系统中的基础概念,似乎不能再精简了。

link和unlink操作主要是查找inode并创建/删除目录项。在inode_block里创建与删除目录项无非是一些线性序列的操作,但由于没有封装成&[DirEntry],需要手动操作,比较费劲。将来有空我会尝试改进一下。

第七章:进程间通信

本章内容较少,但进程间通信是个大话题,还需拓展学习。

第八章:并发

学习了多线程的同步与互斥。

练习:学习了死锁检测算法

第一阶段

好几年前就被 Rust 的性能和并发功能吸引,无奈日常没有运用,只能学而时习之。不过 Rusting 还是没有什么难度的,多看看文档,轻松拿下不是问题。

第二阶段

第二阶段就需要了解操作系统原理了,各类教科书都停留在原理阶段,学完了也不知道自己学会了没有。
lab1 比较简单,原理是需要了解特权级和系统调用,实践上懂得在哪里记录数据和返回即可。
lab2 难度提升不小,原理上需要了解页表机制,多级页表极其抽象,算是一个难点。实践上要注意内核态拿到的是用户态的虚拟地址,要注意转换而不是直接访问。
lab3 难度不大,原理上就是进程那些事,不算难。实践上因为基础代码都已经提供了,依样画葫芦很容易就能通过。
lab4 感觉也是不算难的,原理上就是各种数据结构的管理。实践上因为现在的文件系统比较简易,很多可以不过多考虑直接取巧实现。最大的问题是我的机子性能太差,跑全部测试必定超时,我误以为是自己代码问题。
lab5 感觉是个巨坑,原理上实验说明写的模模糊糊,完全看不懂。实践上也是感觉测例不完善,虽然顺利通过了,但是没把握实现是正确的。

紧赶慢赶,终于是在交上了最后的实验,带着许多想法,写下了这篇总结。同样作为清朝老兵,我又回来了。

一阶段:Rust 110 道算法题

在第一阶段,主要通过编写 110 道算法题,掌握 Rust 编程的基本和高级特性。Rust 语言强调安全性和高效性,
这在算法编程中尤其重要,是的,单个语法题目还是比较轻松的,查阅资料什么的也都可以直接解决。当真正开始算法部分的
题目的时候,才开始慢慢体会到rust的折磨——借用检查器(Borrow Checker)、所有权(Ownership)以及生命周期(Lifetime)等概念
统统在处理复杂数据结构时痛击我,Rust 的强类型系统帮助我减少了运行时错误,经常是我的逻辑天衣无缝,但还是borrowed error。
没事,慢慢折磨吧,折磨多了就会了。。。

实际上,作为清朝老兵,一阶段倒是没花多少时间,翻出以前的仓库,看看填填也就过去了,但是当时熬夜做rustlings的日子仍然是我挥之不去的记忆。酸爽!

二阶段:实现 rCore 简单内核

是的,一拳打爆rcore。第二阶段则是操作系统开发,说白了就是补全各种系统调用。我也慢慢感受到书上说的种种在我看起来毫不起眼的东西,真正实现起来是真的困难,(这里点名虚拟内存和文件系统,东西是真的很多),每个ch都是一脸懵到嘎嘎乱写,然后对着panic疯狂调,期间甚至一度动摇额我的计算机世界观——计算机是真的有玄学啊。不过还好,总是能在某些神奇的地方调调代码就神奇地通过了测试,也是给我留下了不少的未解之谜。

在二阶段确实收获了不少,我现在对于一些较大的工程项目已经没有感觉了,也确实是慢慢熟悉rust了,好玩,爱玩!期待后面的考核。

祝训练营越办越好!希望能在虚拟内存和文件管理那多讲一点,确实很抽象。

最后,感觉你读完我的碎碎念,不管怎样,还是磕磕绊绊弄完了这两个阶段,也算是给上半年自己中间跑路一个交代吧,行文至此,拜拜。

前言

  • 一个大三学生, 偶然看到这个学习活动就来玩一下了. 之前学校操作系统课学了个寂寞, 正好来学多点补全知识, 并且练练rust.

rustling

  • 这个还好, 以前有点点rust基础. 做着没什么困难, 顺便查漏补缺了一下.

rcore

  • 二阶段开始, 内容一下多了起来, 文档看着也挺枯燥的, 有些地方感觉写得对小菜菜不太友好QAQ.
  • lab1
    • 翻翻文档, 翻到了ch3. 懒得再细看文档了, 直接写代码做实验!看了一下要求, 直接加点东西就一次过了, 开心.
  • lab2
    • ch4这个卡了我两周, 内存虚拟化这块真的很不会, 做着不明不白. 有点想摆烂了, 看了看群里聊天记录(光是看群聊信息都能学很多), 有点思路, 往大方向试了一试, 通过.(可能通过了还是不太明白)
  • lab3
    • ch5不是很难, stride调度算法出了点小错误稍微卡了一下, 很快就过了.
  • lab4
    • 听很多人说ch6最难. 文件系统我也不太会,但我认真看了文档和代码, 试了几次也过了, 没感觉太难.
  • lab5
    • 做得最痛苦的实验. 这个实验主要实现死锁检测, 本身不是很难. 但我遇到了一些玄学问题, 有sleep_blocking的测例会在sleep卡住超时过不了, 找来找去找不到原因, 加上DDL到了, 心态小崩. 最后垂死挣扎, 卸了qemu9, 把它换成qemu7, 不怎么抱期望地跑了一下, 通过了!(据说有一些人用qemu9也可以通过, 但不知道为什么我不行.)

总的来说, 第二阶段学到了很多东西, 以前漏掉没学的操作系统知识被补上很多, 但更感觉有更多的东西要学. 同时有时做实验遇到困难怎么尝试都通过不了, 最后再坚持一下通过了, 很有成就感.(感觉实验测例有点简单..)

专业阶段

这个阶段给我最大的感受是学习,因为很多函数都被事先书写好了,这几个实验基本都是在已有的框架上进行相关的函数调用即可,在明白每个函数的实现逻辑以及每个lab实验要实现的功能即可完成实验。同时这个阶段给我的感受是有许多深层次的知识仍需要学习,有许多一知半解的知识需要实践,看到学习群里面大家对于实验的讨论的诸多见解,受益匪浅,完完全全的拓宽了我的眼界,许多从未听过的名词出现在我的面前,只感到纸上得来终觉浅,绝知此事要躬行。可惜时间所迫,没办法对于每个lab实验进行进一步的深究。
第二阶段我重点理会到了操作系统的一步步发展以及实现的功能,在进行每个实验的时候,感受到学校里面的讲解十分片面,并且浅薄(也是我没有认真学习的缘故,学校里面的课检验的只有期末突击而已)。
非常重要的一点是这次的理论和实践一起进行,让我极大的认识到了抽象和具体之间的联系,有时候理论上很复杂并且难以理解的东西,转化到实践上面竟然可能只是一个数组,一个函数栈而已,这让欠缺实践的我大开眼界。
非常期待第三阶段带来的挑战,也非常希望可以通过第三阶段(哈哈哈)。

Rustlings总结

心路历程

这个训练营我也算是“老兵”了,不断地入门再入门,到终于下定决心,投入大量的时间来完成这个训练营,真的经历了很多很多,有着太多的心路历程,从想着三天写完,慢
慢的查漏补缺,温习之前的知识点,慢慢的开始一道题一道题手写rustlings…
我总是说我基础很差,并拿这句话当做挡箭牌,最终一事无成。未来会更好吗?但是我已经有点厌倦痛惜过去了。
说起来这也是我第三次的写rustlings了,开始越来越熟练了,这当然是开心的,不过后面有这更多的更艰难的挑战,阶段二,阶段三,都是未知的大山,更艰难的挑战。

学习由来

对于这个训练营,我是大二机缘巧合开始接触到的,那时候听学长说,特别有含金量,可以“咸鱼翻身”,那可是清华啊,多么具有神话幻想意味的大学,那时的我突然想着,
我一定能做出一番令人羡慕的成就。
但是那时的我用两个字来概括是“摆烂”,在羡慕同学取得的成就和奖项,和自己什么事也不做只打游戏的情况下还希望着未来自己能有一番大成就,如今看来竟全是一片混
浊,在孤独和幻想中,我度过了大学的大半时光,我的性格是卑劣的。
但是未来的路仍旧一片黑暗,载着家人的希望,我在原地自顾自的打转,跳不出自己的镣铐,做不到的仍然做不到,幻想的事物越发离谱。
慢慢得走来,我只剩了这个训练营了,又是另一个未完待续。
我可以做到吗?我问过了很多人,很多未曾谋面的人,陌生的人对我的态度竟大都是积极的,他们不了解我。我也正在逐步了解自己。忘记了太多的事情,记忆也越来越差,但
是从哪个角度来说,我这次真的想完成这次训练营,即使我每次都这么说…
祝武运隆昌。

第一阶段:

比较简单非常方便新手入门,可以很好的对rust的学习曲线难度经行拟合.

第二阶段:

由于研究生的忙碌确实让我有感觉到力不从心,

但是课程比较有用 我之前是干java的对于设计模式和代码规范有一定的追求所以希望在第二阶段对这方面补充

实操教学真的很好 不仅可以检验知识点也可以检验代码能力两不耽误

感想

在接触这门课程之前,我正处于第 N 次入门 Rust 的途中,在之前其实就一直有过多次学习 Rust 的经历,我博客上关于 Rust 的学习笔记最早甚至是 18 年的,回顾我最早学习 Rust 的理由是我希望掌握不同的编程语言范式拓展自己的思维模型。

「学习过程更多的是知识迁移,如果是学习新的编程范式则会稍有难度,这不仅仅是学习新的概念,还需要在思维模式上做出改变,对于编程语言的学习,应当多去学习不同范式的语言,尽管现代编程语言都支持多范式,但各语言擅长的范式还是不一样的。」

但前几次入门 Rust 多因为工作没能用上而浅尝辄止,最近一次我是深刻意识到不实践是无法学会的,所以我一边用 Anki 制作学习笔记卡片,一边尝试用 Rust 写一些小工具,又在 Rust 微信群里看到了 Rust 操作系统的课程,这一下死去的记忆突然开始攻击我了,这不正是我想要的实践吗?
回想起大学自己自学过王爽的《汇编语言》还有于渊《一个操作系统的实现》,但止于汇编编写内核引导后输出 Hello World 的阶段,我被开篇的复杂 C 语言应用劝退了,上面两本书的作者和名字我时隔十年居然都能清晰回忆起来,这或许与我当时的热情有关吧,毕竟操作系统是三大浪漫之一。
于是我立了个 FLAG 想要弥补过去没学好的内容,另外也是为了真正知道自己在做什么,当我拿起自己习以为常且熟练的编程语言和计算机工具进行各种创作的时候,真正发生了什么,学完二阶段或许我不能说掌握了多少底层的细节,但是我有了一种清晰地感知,当我写下的代码在计算机中流动的时候发生了什么。

控制流

由代码编写的指令在编译成汇编代码后会形成一个执行序列,这个序列就是一个控制流,在正常没有中断和异常情况下,整个控制流都由代码生成的汇编控制,这个控制流是普通控制流。
但是一旦进行系统调用,程序就会脱离原本的控制流,进入到操作系统内核代码的控制流中了,比如接受 IO 中断响应,处理用户输入等。这种突变的控制流称之异常控制流。(处理中断、异常、陷入)
这个过程中伴随着执行环境的变化,以及上下文的切换,在应用程序基于操作系统抽象(进程、线程等)的执行环境中,突然切换到操作系统内核基于硬件的执行环境中,同时上下文发生了变化(执行环境的相关参数)。
操作系统和应用程序需要协同硬件一起来保存和恢复这些上下文,使得程序能够正常运行。

异常控制流的保存和恢复由操作系统和 CPU 负责。(手动编写在栈上保存与恢复寄存器的指令)

对于函数转移这内控制流转移,由编译器负责。(编译器会自动生成栈上保存与恢复上下文的指令)

异常控制流

外设中断(Device Interrput):由外部设备引起的外部 I/O 设备事件。
异常(Exception):程序发生除零错误,内存访问越界等。
陷入(Trap):系统调用进入操作系统工作流。

三种东西都是一回事,都是应用程序的工作流被中断了,跑去执行别的地方的代码了,不过对三种中断的方式做了命名区分。

目标平台与目标三元组

通常一个 C 程序编译器的工作流程如下:

  1. 源代码 -> 预处理器进行宏展开
  2. 宏展开的源代码 -> 编译器编译生成汇编代码
  3. 汇编程序 -> 汇编器编译为目标机器代码
  4. 目标代码 -> 链接器链接为可执行文件

Rust 通过一个三元组来描述软件运行的目标平台,即 CPU、操作系统、运行时库等。

`$ rustc –version –verbose

1
2
3
4
5
6
7
rustc 1.82.0 (f6e511eec 2024-10-15)
binary: rustc
commit-hash: f6e511eec7342f59a25f7c0534f1dbea00d01b14
commit-date: 2024-10-15
host: aarch64-apple-darwin
release: 1.82.0
LLVM version: 19.1.1

从 host 可以看出我们的 CPU 是 aarch64, apple 系统, darwin 运行时。

Rust std 库和 core 库区别?

std 库依赖操作系统,提供线程等能力,core 库不依赖操作系统。

elf 格式分析
rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os

实现第一个裸机程序我们做了什么?

Rust 编程:剥离标准库,使用 core 库提供的 trait 实现 panic_handle

1. 去掉 Rust 的标准库依赖,使得程序能够编译成功。

  1. 在 Rust 项目中使用 #[no_std] 标识不使用标准库,这样一来我们 println!() 也将无法使用需要自己实现。
  2. panic 处理是必须的,但是 panic!宏也标准库实现,因此需要导入 core 库手动实现 panic_handle 方法
  3. 但程序需要一个的启动需要一个_start 语义项,语言的标准库作为程序的执行环境,在启动前也会做一些初始化工作才会跳转到由用户编写的程序入口处,但此时我们已经没有标准库依赖了,因此#[no_main]声明没有通常意义的入口函数。

完成上面三项我们就可以编译通过了,但此时编译出来的是一个空壳应用,我们希望执行点什么。

2. 编译出能对接 Qemu 与 RustSBI 的应用。

源代码编译出的来程序字节码可以分为两部分即数据与代码,实际上我们还可以对这两部分进一步划分为更小的单位段(Section),整个这部分就构成了程序内存布局。

.bss: Block Started By Symbol 存储未初始化的全局变量和静态变量。
.data: 已经初始化的全局变量与静态变量。
.rodata: 存储只读的常量与字符串等。

  1. Qemu 在加电启动后会从 0x80200000 开始执行,因此我们需要调整编译出的程序的内存布局,这将通过为编译器指定 linker.ld 配置文件来实现,但提交给 Qemu 的文件还要剥离元数据才能被 Qemu 正确寻址,这通过 rust-objcopy --strip-all 来实现。

  2. 使用#[no_mangle]自定义的入口函数

  3. 编写 linker.ld 定义内存布局

  4. 编写汇编代码配置栈空间布局,设置栈顶指针然后 call 入口函数

1
2
3
4
5
6
7
8
9
10
11
12
13
 # os/src/entry.asm
.section .text.entry
.globl _start
_start:
la sp, boot_stack_top
call rust_main

.section .bss.stack
.globl boot_stack
boot_stack:
.space 4096 * 16
.globl boot_stack_top
boot_stack_top:

3. 实现关键系统调用,实现 Println! 宏等。

通过系统调用实现退出机制,通过系统调用实现 print 方法以及宏。

如何实现批处理操作系统?

批处理操作系统主要解决几个问题:

  1. 加载所有应用程序
  2. 在执行应用程序前初始化,比如用户态栈的初始化,特权级别切换
  3. 处理应用程序的系统调用
  4. 处理应用程序的错误,在出错时执行下一个程序

因此首先要实现用户态的程序,剥离元数据转为 bin 格式,以静态绑定的方式载入内存,在使用时以动态加载的方式来加载。
具体实现方法是通过代码生成汇编码,将应用程序写入数据段,并标识每个程序的起始结束地址。

1
2
3
4
5
6
7
# 例子
.section .data
.global app_4_start
.global app_4_end
app_4_start:
.incbin "../user/target/riscv64gc-unknown-none-elf/release/04priv_csr.bin"
app_4_end:

在内核中,实现应用程序的加载器、实现 Trap 的处理和特权级别切换,以便于在用户态程序进行系统调用时从用户态切换到内核态,并且在这种切换过程中实现上下文的保存和恢复。

CSR 是什么?

在计算机体系结构中,CSR 通常指的是 “Control and Status Register”(控制和状态寄存器)。这些寄存器用于存储处理器的控制信息和状态信息,允许操作系统和应用程序控制处理器的行为和获取处理器的状态。

多道任务与分时多任务

在这里我们主要为了隔离 CPU 资源的使用,让每个应用在特定时间内都能获得 CPU 的全部使用权,同时也可以主动让出 CPU 的使用。
为了让内核同时调度多个应用程序的执行,我们需要实现应用程序的加载机制和任务管理系统,实现任务的切换、暂停管理等能力。

协作式多任务执行

通过实现 yield 系统调用 我们可以让任务主动让出 CPU 使用权,从而让内核进行任务切换执行别的任务,这种由任务自己让出 CPU 使用权的系统叫做协作式操作系统,但是任务下次获得 CPU 使用权的时间与内核调度策略以及其它任务相关,所以缺点也很明显,对于需要及时得到响应的任务而言这样协作式的方式会严重影响使用体验,协作式操作系统适用于所有应用都是可信的情况下。

分时多任务执行

通过时钟中断机制,我们可以实现时间片轮转调度机制,让 CPU 在每个时间片段内周而复始的轮转执行任务,这样可以确保每个任务都能公平的得到 CPU 的使用时间,这种机制叫做抢占式调度策略。

地址空间 - 计算机空间的抽象

现在,我们学会了如何将如何将应用运行在裸机上所必要的知识与概念,即特权级别的切换与任务栈的切换,切换过程中伴随的寄存器的存储与恢复,接下来是操作系统最重要的事物,即构造各种抽象,提供环境约束与管理。
第一要构造的抽象是「地址空间」,它给应用程序创造一个拥有全部内存的幻觉,隔离应用以及操作系统之间的内存访问,保证安全。

SV39 多级页表机制

RISC-V 中通过将 Satp 寄存器的高 8 位 Mode 设置为 8 则开启 SV39 页表机制,此时 U/S 模式下的地址访问都会被视作 39 位虚拟地址需要经过 MMU 转换为 56 位的物理地址,SV39 即特权模式下的 39 位虚拟地址,即在 SV39 模式下,64 位宽只有低 39 位是有意义的。

地址格式:

因为我们的页表是 4K 大小,因此需要 12 位来寻址,因此低 12 位为页内偏移,高 27 位则为页码,MMU 的转换就是扎到虚拟页码到物理页的映射,Offset 不变,拼接成 56 位物理地址。

页表格式:

页表存储:
如果按下面线性表的方式存储页表,即知道一个应用页表的 base*addr,对应的虚拟页页表 = base_addr + 8 * 虚拟页码,即可得到页表项从而找到物理地址。
考虑到虚拟地址有 27 位用于页表索引,因此如果完全存储 2^27 页页表需要 2^27 _ 8(64 位) = 1GB 的空间,这显然不现实。

因此我们需要按需分配的机制,即保存真正有效的虚拟页表。

我们可以通过类似前缀树的方式采用三级页表的方式来索引页表项目 PTE,将 39 位虚拟地址的高 27 位分为 3 段,每段 9 位,剩下 12 位为偏移,如下图所示,通过三级索引我们拿到最终的物理页表页码,加上 Offset 就可以得到最终的物理地址。
每一级页表的大小刚好 2^9 = 512 个 PTE,每个 PTE 大小 8Byte 刚好 4K 一个物理页帧,这样一来,12K 就足以存储页表了。

在地址空间下运行的应用

在地址空间下应用的程序与之前最大的区别在于,此时所有地址指令都需要经过多重的转换,期间有 TLB 加速了这个过程。除了之前提到的特权级别切换,我们还要切换相应的地址空间。
为了这种切换能够顺畅,我们需要构造跳板以及陷入上下文等数据结构,在应用的高位地址存储用户态陷入上下文以及映射陷入内核的跳板代码,这部分代码尽管在用户地址空间,但是 S 态才有的权限。

进程 - 计算器力量的抽象

计算是一种强大力量,但是在操作系统上可能同时运行着多个不同的应用程序,每个应用都可能需要持有计算的权柄,前面提到我们可以让程序自己出让计算权利的协作式多任务模式,也有由操作系统根据时间片切换的抢占式模式。
如何更好的管理这些任务的计算,我们需要更高阶的抽象,即进程,可以理解为比任务更高阶的概念,它不光包括了正在运行的任务,还包括了他拥有的资源,如地址空间等,下面这张图很好的描述了进程概念和任务的执行过程。

文件系统 - IO 的抽象

关于 Linux 系统有一句非常著名的话,那就是一切接「文件」,这个概念如此的强大,以至于可以容纳一切外部设备,仔细想想,因为它只包含了最小的内涵,即「读、写」两个操作,只要能读写?那就是文件,包括我们的硬盘。

准确的说,文件系统是建立在块设备上的一种抽象,下面这张图描述了块设备的内部布局,类似于页表,不过用于管理索引的结构叫做位图。

操作系统上的应用不应关注块设备的内部结构,而是需要关心它提供什么能力,如果每次数据的读写都需要从索引一路找到数据块岂不是很麻烦。
因此我们要为其实现目录结构的管理,这是一个内存中的数据对象,它完成了对块设备布局中存储的真正数据内容的映射。

并发 - 万物迸发

死锁检测

算法过程就是判断某一未结束线程 Finish[i] == false
它的需求是否小于操作系统所能提供的 Need[i, j] <= Work[j]

如果小于那就执行到完成, 然后更新操作系统能分配的资源
Work[j] = Work[j] + Allocation[i, j]
Finish[i] = true

如果 Finish[0..n-1] 都为 true 即所有线程都有足够资源,否则就是不够,有死锁。