#### 背景知识

##### 编译的过程

1. `make testcases-img`生成文件系统镜像`fat.img`，且在`fat.img`里包含测例。
1. `make run`核心操作就是`cargo build`。
1. maturin使用了构建脚本`build.rs`，cargo会先编译并执行该脚本，再构建整个项目。
1. `build.rs`通过`println!`和cargo通信，告诉它如果`build.rs`或`../fat.img`发生变化则重新编译运行它自己。
1. `build.rs`调用`insert_fs_img()`，在`kernel`的源码里加入一个包含一小段汇编代码的文件`fs.S`，把`../fat.img`包含进去。
1. `build.rs`在输出目录里创建`linker.ld`文件，把字符串`LINKER`的内容写入这个文件。
1. `build.rs`调用`println!`告诉cargo把`-C link-arg=-Tlinker.ld`选项传给编译器，其实就是指定了链接脚本。链接脚本里比较重要的信息有：内核入口点是`_start`，内核装载位置是`0xffffffff80200000`。

##### 初始化的流程

1. 入口为`arch/riscv/mod.rs`的`entry()`函数，使用汇编代码设置栈和页表，然后跳转到`start_kernel()`开始Rust代码的执行。
2. `start_kernel()`调用相关模块实现内存、自陷、文件系统的初始化。
3. `start_kernel()`通过调用`arch::secondary_entry`初始化其它的核。
   1. 使用汇编代码设置栈和页表，然后跳转到`start_kernel_secondary()`开始Rust代码的执行。
   2. `start_kernel_secondary()`初始化当前核的页表和自陷，等待主核初始化结束后开始执行`task::run_tasks()`。
4. 主核执行`task::run_tasks()`，开始运行用户程序。
5. `task::run_tasks()`就是一个大`loop`循环，这个循环的内容是：
   1. 首先调用`fetch_task_from_scheduler()`从调度器器里取一个任务。
   1. 执行`task.vm.lock().activate()`切换到用户的页表。最终是写`satp`寄存器实现的，且要注意刷新TLB.
   2. 通过`__switch`切换到任务的上下文，用户程序开始执行。
   3. 用户程序执行完毕后，调用`enable_kernel_page_table()`切换回内核页表。
   4. 获取任务的状态，执行相应的操作。
   5. 如果没有任务了，则调用`panic!()`退出系统。

##### 第一个用户进程

1. `fetch_task_from_scheduler()`调用`load_next_testcase()`创建一个用户程序的PCB，先`push`进调度器`GLOBAL_TASK_SCHEDULER`，再`pop`出来，返回该测例的TCB.
2. `load_next_testcase()`把用户程序加载到TCB里。这最终是通过`from_app_name()`实现的。
3. `from_app_name()`从用户程序的名称生成TCB。其中，在构造内核栈的时候调用了`app_init_context()`初始化用户程序的上下文。
4. `app_init_context()`把`sstatus`寄存器的`SPP`位设置为`SPP::User`。

#### 概述

应用程序对用户是有意义的，用户不需要内核，是应用程序需要内核。

应用程序需要内存来运行自己，于是内核把内存抽象成页，再把页分配给应用程序。

应用程序需要把运行结果记录下来，于是内核把硬盘抽象成文件系统，这样应用程序就可以把结果保存为文件。

应用程序需要并行以提高效率，于是内核把CPU抽象成进程。

内核为了完成上述的三个抽象，还需要一个调度进程来调度所有的应用程序，需要一个自陷管理进程来让应用程序和外设进行交互，需要提供进程间交互的机制（比如信号）。

#### 内存

RISC-V在S态提供了把内存抽象成页的功能，和其它架构的实现方式一样，这在本质上就是“多级查表”。第一级表是4k的页，这个页的物理地址放在`satp`寄存器。

RV32的分页方式是Sv32，RV64的分页方式是Sv39和Sv48。分页方式也记录在`satp`寄存器。

- Sv32是二级查表，寻址4G的虚拟空间。
- Sv39是三级查表，寻址512G的虚拟空间。
- Sv48是四级查表，寻址256T的虚拟空间。

如果修改了页表，需要用`sfence.vma`指令刷新TLB。

addr子模块实现物理地址与虚拟地址、页与地址之间的转换。

allocator子模块实现内存分配，主要是堆分配和页桢分配。

area子模块实现物理段的trait，虚拟段的操作。

page_table子模块实现页表操作。

user子模块实现用户空间指针的检查。

vmm子模块实现虚拟段映射操作。

#### 进程

进程是拥有自己的上下文(寄存器值)的一串机器指令。它的存在形式由ELF格式控制，它在内核里表现为TCB(任务控制块)。

控制进程的状态是通过控制TCB里的状态字段实现的。

#### 文件系统

maturin的文件系统在底层是RAM块，上层是通过调用外部库[rust-fatfs](https://github.com/rafalh/rust-fatfs)实现的。

#### 调度进程

调度进程维护了一个就绪队列`ready_queue`，`push`方法把进程加到队列末尾，`pop`方法把进程从队列顶头弹出，这样就实现了轮转调度算法。

#### 自陷管理进程

自陷分为系统调用，中断和异常。

`__alltraps`是所有自陷的入口。

`kernel_trap_handler`处理内核态的自陷。

`user_trap_handler`处理用户态的自陷。

#### 信号

操作TCB里与信号相关的字段。
