mirror of
https://github.com/rcore-os/rCore.git
synced 2024-11-21 23:56:18 +04:00
aarch64/doc: add file links
This commit is contained in:
parent
1523e7ea5f
commit
9bc0a89a94
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
## linker.ld
|
## linker.ld
|
||||||
|
|
||||||
链接脚本位于 `kernel/src/arch/aarch64/boot/linker.ld`,主要内容如下:
|
链接脚本位于 [kernel/src/arch/aarch64/boot/linker.ld](../../../kernel/src/arch/aarch64/boot/linker.ld),主要内容如下:
|
||||||
|
|
||||||
```
|
```
|
||||||
SECTIONS {
|
SECTIONS {
|
||||||
@ -41,14 +41,14 @@ SECTIONS {
|
|||||||
几个要点:
|
几个要点:
|
||||||
|
|
||||||
* CPU 最先从 `.text.boot (0x80000)` 处开始执行。
|
* CPU 最先从 `.text.boot (0x80000)` 处开始执行。
|
||||||
* 在 `boot.S` 中做好了必要的初始化后,将跳转到 `.text.entry/_start (0x100000)`,再从这里跳转到 Rust 代码 `rust_main()`。
|
* 在 [boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 中做好了必要的初始化后,将跳转到 `_start (0x100000)`,再从这里跳转到 Rust 代码 `rust_main()`。
|
||||||
* `boot.S` 的偏移为 `0x80000`,Rust 代码的偏移为 `0x100000`。
|
* [boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 的偏移为 `0x80000`,Rust 代码的偏移为 `0x100000`。
|
||||||
* 跳转到 `rust_main()` 后,`0x0~0x100000` 这段内存将被作为内核栈,大小为 1MB,栈顶即 `bootstacktop (0x100000)`。
|
* 跳转到 `rust_main()` 后,`0x0~0x100000` 这段内存将被作为内核栈,大小为 1MB,栈顶即 `bootstacktop (0x100000)`。
|
||||||
* `boot.S` 结束后还未启用 MMU,可直接访问物理地址。
|
* [boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 结束后还未启用 MMU,可直接访问物理地址。
|
||||||
|
|
||||||
## boot.S
|
## boot.S
|
||||||
|
|
||||||
CPU 启动代码位于 `kernel/src/arch/aarch64/boot/boot.S`,负责初始化一些系统寄存器,并将当前异常级别(exception level)切换到 EL1。
|
CPU 启动代码位于 [kernel/src/arch/aarch64/boot/boot.S](../../../kernel/src/arch/aarch64/boot/boot.S),负责初始化一些系统寄存器,并将当前异常级别(exception level)切换到 EL1。
|
||||||
|
|
||||||
AArch64 有 4 个异常级别,相当于 x86 的特权级,分别为:
|
AArch64 有 4 个异常级别,相当于 x86 的特权级,分别为:
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ AArch64 有 4 个异常级别,相当于 x86 的特权级,分别为:
|
|||||||
|
|
||||||
在 RustOS 中,内核将运行在 EL1 上,用户程序将运行在 EL0 上。
|
在 RustOS 中,内核将运行在 EL1 上,用户程序将运行在 EL0 上。
|
||||||
|
|
||||||
`boot.S` 的主要流程如下:
|
[boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 的主要流程如下:
|
||||||
|
|
||||||
1. 获取核的编号,目前只使用 0 号核,其余核将被闲置:
|
1. 获取核的编号,目前只使用 0 号核,其余核将被闲置:
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ AArch64 有 4 个异常级别,相当于 x86 的特权级,分别为:
|
|||||||
|
|
||||||
## rust_main
|
## rust_main
|
||||||
|
|
||||||
在 `boot.S` 初始化完毕后,会进入 Rust 函数 `rust_main()`:
|
在 [boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 初始化完毕后,会进入 [kernel/src/arch/aarch64/mod.rs](../../../kernel/src/arch/aarch64/mod.rs#L19) 的 Rust 函数 `rust_main()`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/// The entry point of kernel
|
/// The entry point of kernel
|
||||||
@ -222,4 +222,4 @@ pub extern "C" fn rust_main() -> ! {
|
|||||||
5. 初始化内存管理,包括物理页帧分配器与内核堆分配器,最后会建立一个新的页表重新映射内核;
|
5. 初始化内存管理,包括物理页帧分配器与内核堆分配器,最后会建立一个新的页表重新映射内核;
|
||||||
6. 初始化其他设备驱动,包括 Frambuffer、Console、Timer;
|
6. 初始化其他设备驱动,包括 Frambuffer、Console、Timer;
|
||||||
7. 初始化进程管理,包括线程调度器、进程管理器,并为每个核建立一个 idle 线程,最后会加载 SFS 文件系统加入用户态 shell 进程;
|
7. 初始化进程管理,包括线程调度器、进程管理器,并为每个核建立一个 idle 线程,最后会加载 SFS 文件系统加入用户态 shell 进程;
|
||||||
8. 最后调用 `crate::kmain()`,按调度器轮流运行创建的线程。
|
8. 最后调用 `crate::kmain()`,按调度器轮流执行创建的线程。
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# 上下文切换
|
# 上下文切换
|
||||||
|
|
||||||
平台无关的代码位于 `kernel/src/process/context.rs` 中,而平台相关(aarch64)的代码位于 `kernel/src/arch/aarch64/interrupt/context.rs` 中。
|
平台无关的代码位于 [kernel/src/process/context.rs](../../../kernel/src/process/context.rs) 中,而平台相关(aarch64)的代码位于 [kernel/src/arch/aarch64/interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs) 中。
|
||||||
|
|
||||||
## 相关数据结构
|
## 相关数据结构
|
||||||
|
|
||||||
在 `kernel/src/arch/aarch64/interrupt/context.rs` 中定义了下列数据结构:
|
在 [kernel/src/arch/aarch64/interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs) 中定义了下列数据结构:
|
||||||
|
|
||||||
1. `TrapFrame`:
|
1. `TrapFrame`:
|
||||||
|
|
||||||
@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在陷入异常时向栈中压入的内容,由 `trap.S` 的 `__alltraps` 构建。详见“中断与异常”章节。
|
在陷入异常时向栈中压入的内容,由 [trap.S](../../../kernel/src/arch/aarch64/interrupt/trap.S#L92) 的 `__alltraps` 构建。详见“中断与异常”相关章节。
|
||||||
|
|
||||||
2. `ContextData`:
|
2. `ContextData`:
|
||||||
|
|
||||||
@ -56,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
每个进程控制块 `Process` (`kernel/src/process/context.rs`) 都会维护一个平台相关的 `Context` 对象,在 aarch64 中包含下列信息:
|
每个进程控制块 `Process` ([kernel/src/process/context.rs](../../../kernel/src/process/context.rs#L13)) 都会维护一个平台相关的 `Context` 对象,在 aarch64 中包含下列信息:
|
||||||
|
|
||||||
1. `stack_top`:内核栈顶地址;
|
1. `stack_top`:内核栈顶地址;
|
||||||
2. `ttbr`:页表基址;
|
2. `ttbr`:页表基址;
|
||||||
@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
## 切换流程
|
## 切换流程
|
||||||
|
|
||||||
在 `kernel/src/process/context.rs` 里,`switch_to()` 是平台无关的切换函数,最终会调用 `kernel/src/arch/aarch64/interrupt/context.rs` 里平台相关的切换函数 `Context::switch()`:
|
在 [kernel/src/process/context.rs](../../../kernel/src/process/context.rs#L22) 里,`switch_to()` 是平台无关的切换函数,最终会调用 [kernel/src/arch/aarch64/interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L129) 里平台相关的切换函数 `Context::switch()`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub unsafe fn switch(&mut self, target: &mut Self) {
|
pub unsafe fn switch(&mut self, target: &mut Self) {
|
||||||
@ -190,7 +190,7 @@ ret
|
|||||||
2. 创建新的**用户线程**:解析 ELF 文件;
|
2. 创建新的**用户线程**:解析 ELF 文件;
|
||||||
3. 从一个线程 **fork** 出一个新线程:通过 `fork` 系统调用。
|
3. 从一个线程 **fork** 出一个新线程:通过 `fork` 系统调用。
|
||||||
|
|
||||||
三种线程的平台无关创建流程实现在 `kernel/src/process/context.rs` 里,最终会分别调用 `kernel/src/arch/aarch64/interrupt/context.rs` 里的 `new_kernel_thread()`、`new_user_thread()` 和 `new_fork()` 这三个函数创建平台相关的 `Context` 结构。
|
三种线程的平台无关创建流程实现在 [kernel/src/process/context.rs](../../../kernel/src/process/context.rs#L40) 里,最终会分别调用 [kernel/src/arch/aarch64/interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L146) 里的 `new_kernel_thread()`、`new_user_thread()` 和 `new_fork()` 这三个函数创建平台相关的 `Context` 结构。
|
||||||
|
|
||||||
在这三个函数里,会构造 `ContextData` 与 `TrapFrame` 结构,构成一个 `InitStack`,并向新线程的内核栈压入 `InitStack` 结构,最后将新内核栈顶地址、页表基址等信息构成 `Context` 结构返回。这两个结构的构造方式如下:
|
在这三个函数里,会构造 `ContextData` 与 `TrapFrame` 结构,构成一个 `InitStack`,并向新线程的内核栈压入 `InitStack` 结构,最后将新内核栈顶地址、页表基址等信息构成 `Context` 结构返回。这两个结构的构造方式如下:
|
||||||
|
|
||||||
|
@ -62,14 +62,14 @@
|
|||||||
* `mode=debug|release`:指定 `debug` 还是 `release` 模式。默认 `debug`。
|
* `mode=debug|release`:指定 `debug` 还是 `release` 模式。默认 `debug`。
|
||||||
* `graphic=on|off`:是否启用图形输出。默认 `on`。
|
* `graphic=on|off`:是否启用图形输出。默认 `on`。
|
||||||
* `smp=1|2|3|4|...`:指定 SMP 的核数。目前 aarch64 的 SMP 未实现,该选项无效。
|
* `smp=1|2|3|4|...`:指定 SMP 的核数。目前 aarch64 的 SMP 未实现,该选项无效。
|
||||||
* `raspi3_timer=system/generic`:使用 Raspberry Pi 的 System Timer 还是 Generic Timer。默认 `generic`,且在 QEMU 中只能使用 Generic Timer。
|
* `raspi3_timer=system|generic`:使用 Raspberry Pi 的 System Timer 还是 Generic Timer。默认 `generic`,且在 QEMU 中只能使用 Generic Timer。
|
||||||
* `prefix=<prefix>`:指定 aarch64 工具链前缀。默认 `aarch64-none-elf-`,某些 Linux 中的工具链前缀为 `aarch64-linux-gnu-`。
|
* `prefix=<prefix>`:指定 aarch64 工具链前缀。默认 `aarch64-none-elf-`,某些 Linux 中的工具链前缀为 `aarch64-linux-gnu-`。
|
||||||
* `LOG=off|error|warn|info|debug|trace`:指定输出日志的级别。默认 `warn`。
|
* `LOG=off|error|warn|info|debug|trace`:指定输出日志的级别。默认 `warn`。
|
||||||
* `SFSIMG=<sfsimg>`:用户程序 SFS 镜像路径。默认 `../user/img/ucore-aarch64.img`,即用 C 语言编写的直接从原 uCore 中移植过来的用户程序。如欲使用 Rust 编写的用户程序可将其设为 `../user/build/user-aarch64.img`。
|
* `SFSIMG=<sfsimg>`:用户程序 SFS 镜像路径。默认 `../user/img/ucore-aarch64.img`,即用 C 语言编写的直接从原 uCore 中移植过来的用户程序。如欲使用 Rust 编写的用户程序可将其设为 `../user/build/user-aarch64.img`。
|
||||||
|
|
||||||
## 在真机上运行
|
## 在真机上运行
|
||||||
|
|
||||||
1. 往 SD 卡中写入 [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) 原版系统镜像:直接看 <https://www.raspberrypi.org/documentation/installation/installing-images>。然后需要将原版的 `config.txt` 替换为 `tools/raspi-firmware/config.txt`。
|
1. 往 SD 卡中写入 [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) 原版系统镜像:直接看 <https://www.raspberrypi.org/documentation/installation/installing-images>。然后需要将原版的 `config.txt` 替换为 [tools/raspi-firmware/config.txt](../../../tools/raspi-firmware/config.txt)。
|
||||||
|
|
||||||
2. 写入 RustOS 内核镜像:
|
2. 写入 RustOS 内核镜像:
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
> 参考:ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile, capture D1.1, D1.7, D1.10, D1.11, D1.13, D1.14, D1.16.
|
> 参考:ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile, capture D1.1, D1.7, D1.10, D1.11, D1.13, D1.14, D1.16.
|
||||||
|
|
||||||
在 AArch64 中,各种中断被统称为异常(exception),包括:
|
在 AArch64 中,各种中断被统称为**异常**(exception),包括:
|
||||||
|
|
||||||
* Reset.
|
* Reset.
|
||||||
* Interrupts.
|
* Interrupts.
|
||||||
@ -13,16 +13,16 @@
|
|||||||
* Supervisor calls (SVCs), Secure Monitor calls (SMCs), and hypervisor calls (HVCs).
|
* Supervisor calls (SVCs), Secure Monitor calls (SMCs), and hypervisor calls (HVCs).
|
||||||
* Debug exceptions.
|
* Debug exceptions.
|
||||||
|
|
||||||
这些异常可分为同步异常(synchronous exception)与异步异常(asynchronous exception)两大类:
|
这些异常可分为**同步异常**(synchronous exception)与**异步异常**(asynchronous exception)两大类:
|
||||||
|
|
||||||
* 同步异常:发生在执行一条特定的指令时,包括执行系统调用指令(`svc`、`hvc`)、断点指令(debug exceptions)、Instruction Aborts、Data Aborts 等。
|
* 同步异常:发生在执行一条特定的指令时,包括执行系统调用指令(`svc`、`hvc`)、断点指令(debug exceptions)、Instruction Aborts、Data Aborts 等。
|
||||||
* 异步异常:发生的时机不确定,又被称为中断(interrupt),是发送给处理机的信号,包括 SError、IRQ、FIQ 等。
|
* 异步异常:发生的时机不确定,又被称为**中断**(interrupt),是发送给处理机的信号,包括 SError、IRQ、FIQ 等。
|
||||||
|
|
||||||
### 异常处理
|
### 异常处理
|
||||||
|
|
||||||
当发生异常时,CPU 会根据异常的类型,跳转到特定的地址执行,该地址被称为异常向量(exception vector)。
|
当发生异常时,CPU 会根据异常的类型,跳转到特定的地址执行,该地址被称为**异常向量**(exception vector)。
|
||||||
|
|
||||||
不同类型异常的异常向量通过统一的向量基地址寄存器(Vector Base Address Register, VBAR)加上不同的偏移得到。在 EL1、EL2、EL3 下各有一个 VBAR 寄存器 `VBAR_ELx`。此时异常被分为 4 大类,每一类根据异常来源的不同又可分为 4 类,于是共有 16 个异常向量。
|
不同类型异常的异常向量通过统一的**向量基地址寄存器**(Vector Base Address Register, VBAR)加上不同的偏移得到。在 EL1、EL2、EL3 下各有一个 VBAR 寄存器 `VBAR_ELx`。此时异常被分为 4 大类,每一类根据异常来源的不同又可分为 4 类,于是共有 16 个异常向量。
|
||||||
|
|
||||||
4 种类型的异常分别为:
|
4 种类型的异常分别为:
|
||||||
|
|
||||||
@ -47,11 +47,11 @@
|
|||||||
| LowerAArch64 | `0x400` | `0x480` | `0x500` | `0x580` |
|
| LowerAArch64 | `0x400` | `0x480` | `0x500` | `0x580` |
|
||||||
| LowerAArch32 | `0x600` | `0x680` | `0x700` | `0x780` |
|
| LowerAArch32 | `0x600` | `0x680` | `0x700` | `0x780` |
|
||||||
|
|
||||||
如果该异常是 Synchronous 或 SError,异常症状寄存器(Exception Syndrome Register, ESR)将被设置,用于记录具体的异常类别 EC (exception class) 与 ISS (Instruction Specific Syndrome)。在 EL1、EL2、EL3 下各有一个 ESR 寄存器 `ESR_ELx`。具体的 EC、ISS 编码见官网文档 ARMv8 Reference Manual D1.10.4 节。
|
如果该异常是 Synchronous 或 SError,**异常症状寄存器**(Exception Syndrome Register, ESR)将被设置,用于记录具体的异常类别 EC (exception class) 与 ISS (Instruction Specific Syndrome)。在 EL1、EL2、EL3 下各有一个 ESR 寄存器 `ESR_ELx`。具体的 EC、ISS 编码见官网文档 ARMv8 Reference Manual D1.10.4 节。
|
||||||
|
|
||||||
### 异常屏蔽
|
### 异常屏蔽
|
||||||
|
|
||||||
某些异常可以被屏蔽(mask),即发生时不跳转到相应的异常向量。可被屏蔽的异常包括所有异步异常与调试时的同步异常(debug exceptions),共 4 种,分别由 `DAIF` 4 个位控制:
|
某些异常可以被**屏蔽**(mask),即发生时不跳转到相应的异常向量。可被屏蔽的异常包括所有异步异常与调试时的同步异常(debug exceptions),共 4 种,分别由 `DAIF` 4 个位控制:
|
||||||
|
|
||||||
1. `D`: Debug exception
|
1. `D`: Debug exception
|
||||||
2. `A`: SError interrupt
|
2. `A`: SError interrupt
|
||||||
@ -62,21 +62,21 @@
|
|||||||
|
|
||||||
### 异常返回
|
### 异常返回
|
||||||
|
|
||||||
当发生异常时,异常返回地址会被设置,保存在异常链接寄存器(Exception Link Register, ELR) `ELR_ELx` 中。
|
当发生异常时,异常返回地址会被设置,保存在**异常链接寄存器**(Exception Link Register, ELR) `ELR_ELx` 中。
|
||||||
|
|
||||||
异常返回使用 `eret` 指令完成。当异常返回时,`PC` 会根据当前特权级被恢复为 `ELR_ELx` 中的,`PSTATE` (process state)也会被恢复为 `SPSR_ELx` 中的。`PSTATE` 是当前进程状态信息的抽象,保存在 `SPSR_ELx` 中,包含条件标志位 `NZCV`、异常屏蔽位 `DAIF`、当前特权级等信息。如果修改了 `SPSR_ELx` 中相应的位并进行异常返回,就能实现异常级别切换、异常开启/屏蔽等功能。
|
异常返回使用 **`eret`** 指令完成。当异常返回时,`PC` 会根据当前特权级被恢复为 `ELR_ELx` 中的,**进程状态** `PSTATE` (process state)也会被恢复为 `SPSR_ELx` 中的。`PSTATE` 是当前进程状态信息的抽象,保存在 `SPSR_ELx` 中,包含条件标志位 `NZCV`、异常屏蔽位 `DAIF`、当前特权级等信息。如果修改了 `SPSR_ELx` 中相应的位并进行异常返回,就能实现异常级别切换、异常开启/屏蔽等功能。
|
||||||
|
|
||||||
### 系统调用
|
### 系统调用
|
||||||
|
|
||||||
一般使用 `svc` 指令(supervisor call)完成,将触发一个同步异常。
|
一般使用 **`svc`** 指令(supervisor call)完成,将触发一个同步异常。
|
||||||
|
|
||||||
## RustOS 中的实现
|
## RustOS 中的实现
|
||||||
|
|
||||||
中断与异常部分的代码主要位于 `kernel/src/arch/aarch64/interrupt/` 中。
|
中断与异常部分的代码主要位于模块 [kernel/src/arch/aarch64/interrupt](../../../kernel/src/arch/aarch64/interrupt/) 中。
|
||||||
|
|
||||||
### 异常启用与屏蔽
|
### 异常启用与屏蔽
|
||||||
|
|
||||||
在 `interrupt/mod.rs` 中使用了 `DAIFClr` 与 `DAIFSet` 特殊目的寄存器分别实现了异常的启用与屏蔽(仅针对 IRQ),分别为 `enable()` 与 `disable()` 函数,代码如下:
|
在 [interrupt/mod.rs](../../../kernel/src/arch/aarch64/interrupt/mod.rs#L24) 中使用了 `DAIFClr` 与 `DAIFSet` 特殊目的寄存器分别实现了异常的启用与屏蔽(仅针对 IRQ),分别为 `enable()` 与 `disable()` 函数,代码如下:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/// Enable the interrupt (only IRQ).
|
/// Enable the interrupt (only IRQ).
|
||||||
@ -92,13 +92,13 @@ pub unsafe fn disable() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
此外,也可在异常返回前通过修改保存的 `SPSR` 寄存器启用或屏蔽异常,详见 `interrupt/context.rs` 中的 `TrapFrame::new_kernel_thread()` 与 `TrapFrame::new_user_thread()` 函数。
|
此外,也可在异常返回前通过修改保存的 `SPSR` 寄存器启用或屏蔽异常,详见 [interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L26) 中的 `TrapFrame::new_kernel_thread()` 与 `TrapFrame::new_user_thread()` 函数。
|
||||||
|
|
||||||
### 异常向量
|
### 异常向量
|
||||||
|
|
||||||
全局符号 `__vectors` 定义了异常向量基址,并在 `interrupt/mod.rs` 的 `init()` 函数中通过 `msr vbar_el1, x0` 指令,将 `VBAR_EL1` 设为了 `__vectors`。
|
全局符号 `__vectors` 定义了异常向量基址,并在 [interrupt/mod.rs](../../../kernel/src/arch/aarch64/interrupt/mod.rs#L13) 的 `init()` 函数中通过 `msr vbar_el1, x0` 指令,将 `VBAR_EL1` 设为了 `__vectors`。
|
||||||
|
|
||||||
16 个异常向量分别通过宏 `HANDLER source kind` 定义在 `interrupt/vector.S` 中,代码如下:
|
16 个异常向量分别通过宏 `HANDLER source kind` 定义在 [interrupt/vector.S](../../../kernel/src/arch/aarch64/interrupt/vector.S) 中,代码如下:
|
||||||
|
|
||||||
```armasm
|
```armasm
|
||||||
.macro HANDLER source kind
|
.macro HANDLER source kind
|
||||||
@ -112,7 +112,7 @@ pub unsafe fn disable() {
|
|||||||
|
|
||||||
不同的异常向量对应的异常处理例程结构相同,仅有 `source` 和 `kind` 不同。`source` 与 `kind` 将会被合并成一个整数并存到寄存器 `x0` 中,以便作为参数传给 Rust 编写的异常处理函数。
|
不同的异常向量对应的异常处理例程结构相同,仅有 `source` 和 `kind` 不同。`source` 与 `kind` 将会被合并成一个整数并存到寄存器 `x0` 中,以便作为参数传给 Rust 编写的异常处理函数。
|
||||||
|
|
||||||
由于不同异常向量的间距较少(仅为 `0x80` 字节),所以不在 `HANDLER` 中做细致的处理,而是统一跳转到 `__alltraps` 中进行处理。
|
由于不同异常向量的间距较少(仅为 `0x80` 字节),所以不在 `HANDLER` 中做细致的处理,而是统一跳转到 [trap.S](../../../kernel/src/arch/aarch64/interrupt/trap.S#L92) 的 `__alltraps` 中进行处理。
|
||||||
|
|
||||||
### 异常处理
|
### 异常处理
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ __trapret:
|
|||||||
3. 当该函数返回时,通过宏 `RESTORE_ALL` 从 `TrapFrame` 中恢复各寄存器;
|
3. 当该函数返回时,通过宏 `RESTORE_ALL` 从 `TrapFrame` 中恢复各寄存器;
|
||||||
4. 最后通过 `eret` 指令进行异常返回。
|
4. 最后通过 `eret` 指令进行异常返回。
|
||||||
|
|
||||||
`TrapFrame` 定义在 `interrupt/context.rs` 中,结构如下:
|
`TrapFrame` 定义在 [interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L12)中,结构如下:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub struct TrapFrame {
|
pub struct TrapFrame {
|
||||||
@ -159,9 +159,9 @@ pub struct TrapFrame {
|
|||||||
|
|
||||||
目前保存的寄存器包括:通用寄存器 `x0~x30`、异常返回地址 `ELR`、栈指针 `SP`、进程状态 `SPSR`。由于在 `aarch64-blog_os.json` 中禁用了 `NEON` 指令,不需要保存 `q0~q31` 这些 SIMD/FP 寄存器。
|
目前保存的寄存器包括:通用寄存器 `x0~x30`、异常返回地址 `ELR`、栈指针 `SP`、进程状态 `SPSR`。由于在 `aarch64-blog_os.json` 中禁用了 `NEON` 指令,不需要保存 `q0~q31` 这些 SIMD/FP 寄存器。
|
||||||
|
|
||||||
`rust_trap()` 函数定义在 `interrupt/handler.rs` 中。首先判断传入的 `kind`:
|
`rust_trap()` 函数定义在 [interrupt/handler.rs](../../../kernel/src/arch/aarch64/interrupt/handler.rs#L43) 中。首先判断传入的 `kind`:
|
||||||
|
|
||||||
* 如果是 `Synchronous`:在 `interrupt/syndrome.rs` 中解析 `ESR`,根据具体的异常类别分别处理断点指令、系统调用、缺页异常等。
|
* 如果是 `Synchronous`:在 [interrupt/syndrome.rs](../../../kernel/src/arch/aarch64/interrupt/syndrome.rs) 中解析 `ESR`,根据具体的异常类别分别处理断点指令、系统调用、缺页异常等。
|
||||||
* 如果是 `IRQ`:调用 `handle_irq()` 函数处理 IRQ。
|
* 如果是 `IRQ`:调用 `handle_irq()` 函数处理 IRQ。
|
||||||
* 其他类型的异常(SError interrupt、Debug exception)暂不做处理,直接调用 `crate::trap::error()`。
|
* 其他类型的异常(SError interrupt、Debug exception)暂不做处理,直接调用 `crate::trap::error()`。
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ pub struct TrapFrame {
|
|||||||
|
|
||||||
如果 `ESR` 的异常类别是 `SVC`,则说明该异常由系统调用指令 `svc` 触发,紧接着会调用 `handle_syscall()` 函数。
|
如果 `ESR` 的异常类别是 `SVC`,则说明该异常由系统调用指令 `svc` 触发,紧接着会调用 `handle_syscall()` 函数。
|
||||||
|
|
||||||
RustOS 的系统调用方式如下(实现在 `user/ucore-lib/src/syscall.rs` 中):
|
RustOS 的系统调用方式如下(实现在 [user/ucore-ulib/src/syscall.rs](../../../user/ucore-ulib/src/syscall.rs#L47) 中):
|
||||||
|
|
||||||
* 将系统调用号保存在寄存器 `x8`,将 6 参数分别保存在寄存器 `x0~x5` 中;
|
* 将系统调用号保存在寄存器 `x8`,将 6 参数分别保存在寄存器 `x0~x5` 中;
|
||||||
* 执行系统调用指令 `svc 0`;
|
* 执行系统调用指令 `svc 0`;
|
||||||
@ -189,13 +189,13 @@ RustOS 的系统调用方式如下(实现在 `user/ucore-lib/src/syscall.rs` 中
|
|||||||
* TLB conflict abort.
|
* TLB conflict abort.
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
其中 level 表示在第几级页表产生异常。当状态码是 translation fault、access flag fault、permission fault 时,将被判断为是缺页异常,并调用 `handle_page_fault()` 处理缺页异常。
|
其中 level 表示在第几级翻译表产生异常。当状态码是 translation fault、access flag fault、permission fault 时,将被判断为是缺页异常,并调用 `handle_page_fault()` 处理缺页异常。
|
||||||
|
|
||||||
发生 Instruction Abort 与 Data Abort 的虚拟地址将会被保存到 `FAR_ELx` 系统寄存器中。此时再调用 `crate::memory::page_fault_handler(addr)` 来做具体的缺页处理。
|
发生 Instruction Abort 与 Data Abort 的虚拟地址将会被保存到 `FAR_ELx` 系统寄存器中。此时再调用 `crate::memory::page_fault_handler(addr)` 来做具体的缺页处理。
|
||||||
|
|
||||||
#### IRQ
|
#### IRQ
|
||||||
|
|
||||||
如果该异常是 IRQ,则会调用 `kernel/src/arch/aarch64/board/raspi3/irq.rs` 中的 `handle_irq()` 函数。该函数与 board 相关,即使都是在 aarch64 下,不同 board 的 IRQ 处理方式也不一样,所以放到了 `kernel/src/arch/board/raspi3` 中,表示是 RPi3 特有的 IRQ 处理方式。
|
如果该异常是 IRQ,则会调用 [kernel/src/arch/aarch64/board/raspi3/irq.rs](../../../kernel/src/arch/aarch64/board/raspi3/irq.rs#L8) 中的 `handle_irq()` 函数。该函数与 board 相关,即使都是在 aarch64 下,不同 board 的 IRQ 处理方式也不一样,所以放到了模块 [kernel/src/arch/aarch64/board/raspi3](../../../kernel/src/arch/aarch64/board/raspi3/) 中,表示是 RPi3 特有的 IRQ 处理方式。
|
||||||
|
|
||||||
该函数首先会判断是否有时钟中断,如果有就先处理时钟中断:
|
该函数首先会判断是否有时钟中断,如果有就先处理时钟中断:
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ if controller.is_pending() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
其中使用了 crate bcm2837,位于 `crate/bcm2837` 中,是一个封装良好的访问 RPi3 底层外围设备的库。
|
其中使用了 crate bcm2837,位于 [crate/bcm2837](../../../crate/bcm2837/) 中,是一个封装良好的访问 RPi3 底层外围设备的库。
|
||||||
|
|
||||||
然后会遍历所有其他未处理的 IRQ,如果该 IRQ 已注册,就调用它的处理函数:
|
然后会遍历所有其他未处理的 IRQ,如果该 IRQ 已注册,就调用它的处理函数:
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ AArch64 拥有 64 位地址,支持两段虚拟内存地址空间,分别为
|
|||||||
|
|
||||||
#### Cache 操作 (C5.3)
|
#### Cache 操作 (C5.3)
|
||||||
|
|
||||||
Cache 是主存的缓存,如果一个物理地址在 cache 中命中,将不会访问主存,而是直接从 cache 中得到数据。Cache 又分为指令 cache 与 数据 cache,分别作用在取指与普通访存时。
|
**Cache** 是主存的缓存,如果一个物理地址在 cache 中命中,将不会访问主存,而是直接从 cache 中得到数据。Cache 又分为指令 cache 与 数据 cache,分别作用在取指与普通访存时。
|
||||||
|
|
||||||
当通过普通访存的形式修改了代码段的数据,并跳转到了这里运行,此时需要刷新指令 cache,以防取指时从指令 cache 中取到旧的数据。指令 cache 可使用下列代码一次性全部清空:
|
当通过普通访存的形式修改了代码段的数据,并跳转到了这里运行,此时需要刷新指令 cache,以防取指时从指令 cache 中取到旧的数据。指令 cache 可使用下列代码一次性全部清空:
|
||||||
|
|
||||||
@ -144,11 +144,11 @@ Cache line 的大小可通过 `CTR_EL0` 寄存器读取,一般为 16 个 WORD
|
|||||||
|
|
||||||
在上下文切换时,需要修改 TTBR1_EL1 寄存器,如果不刷新 TLB 也不使用 ASID,TLB 中将会保留旧进程的虚拟——物理地址映射关系,导致进程访问错误的物理地址。不过如果每次上下文切换都刷新 TLB,开销又较大。
|
在上下文切换时,需要修改 TTBR1_EL1 寄存器,如果不刷新 TLB 也不使用 ASID,TLB 中将会保留旧进程的虚拟——物理地址映射关系,导致进程访问错误的物理地址。不过如果每次上下文切换都刷新 TLB,开销又较大。
|
||||||
|
|
||||||
ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下文切换时,每个进程会被分配一个唯一的 ASID,并将其保存到 `TTBR1_EL1` 的高 16 位中。此时 TLB 中会同时记录一个虚拟地址及其进程的 ASID 对应的物理地址,使得不同进程的相同虚拟地址在 TLB 中也会被映射为不同的物理地址。
|
**ASID** (Address Space ID) 的引入就是为了避免在上下文切换过程中刷新 TLB。详见“上下文切换”相关章节。
|
||||||
|
|
||||||
## RustOS 中的实现
|
## RustOS 中的实现
|
||||||
|
|
||||||
在 RustOS 中,aarch64 平台相关的内存管理主要实现在 `kernel/src/arch/aarch64/memory.rs` 与 `kernel/src/arch/aarch64/paging.rs` 中。此外,crate [aarch64](https://github.com/equation314/aarch64) 类似其他平台下的 x86_64、riscv 库,封装了对 aarch64 底层系统寄存器、翻译表的访问。
|
在 RustOS 中,aarch64 平台相关的内存管理主要实现在 [kernel/src/arch/aarch64/memory.rs](../../../kernel/src/arch/aarch64/memory.rs) 与 [kernel/src/arch/aarch64/paging.rs](../../../kernel/src/arch/aarch64/paging.rs) 中。此外,crate [aarch64](https://github.com/equation314/aarch64) 类似其他平台下的 x86_64、riscv 库,封装了对 aarch64 底层系统寄存器、翻译表的访问。
|
||||||
|
|
||||||
(注:为了保持与其他平台的一致性,下文使用“页表”指代“翻译表”,并且下文中页表的级数 1, 2, 3, 4 分别指官方文档中翻译表的级数 3, 2, 1, 0)
|
(注:为了保持与其他平台的一致性,下文使用“页表”指代“翻译表”,并且下文中页表的级数 1, 2, 3, 4 分别指官方文档中翻译表的级数 3, 2, 1, 0)
|
||||||
|
|
||||||
@ -156,11 +156,11 @@ ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下
|
|||||||
|
|
||||||
### aarch64 库中的页表
|
### aarch64 库中的页表
|
||||||
|
|
||||||
与其他平台一样,页表实现在内核代码 `paging.rs` 中,其实只是套了层壳,诸如 map 等复杂操作的实现位于 aarch64 库中。
|
与其他平台一样,页表实现在内核代码 [paging.rs](../../../kernel/src/arch/aarch64/paging.rs) 中,其实只是套了层壳,诸如 map 等复杂操作的实现位于 aarch64 库中。
|
||||||
|
|
||||||
#### 页表描述符
|
#### 页表描述符
|
||||||
|
|
||||||
在 `aarch64/src/paging/page_table.rs` 中实现了页表(`PageTable`)与页表项(`PageTableEntry`)。页表有 512 个项,每个页表项是一个 64 位描述符。一个页表项描述符由下面 3 部分字段构成:
|
在 [aarch64/src/paging/page_table.rs](https://github.com/equation314/aarch64/blob/master/src/paging/page_table.rs) 中实现了页表(`PageTable`)与页表项(`PageTableEntry`)。页表有 512 个项,每个页表项是一个 64 位描述符。一个页表项描述符由下面 3 部分字段构成:
|
||||||
|
|
||||||
1. **地址**(address):位于描述符的第 12 到 47 位。根据描述符的 `TABLE_OR_PAGE` 位,分别指向下列 3 种地址:
|
1. **地址**(address):位于描述符的第 12 到 47 位。根据描述符的 `TABLE_OR_PAGE` 位,分别指向下列 3 种地址:
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下
|
|||||||
| 59 | PXNTable | 该页表所映射的所有内存都是特权级下不可执行的 |
|
| 59 | PXNTable | 该页表所映射的所有内存都是特权级下不可执行的 |
|
||||||
| 60 | XNTable | 该页表所映射的所有内存都是非特权级下不可执行的 |
|
| 60 | XNTable | 该页表所映射的所有内存都是非特权级下不可执行的 |
|
||||||
|
|
||||||
3. **属性**(attribute):属性字段指明了这段内存的内存属性,包括内存类型(位 2、3、4)与可共享性(位 8、9)。在 `aarch64/src/paging/memory_attribute.rs` 中预定义了 3 中内存属性,分别为:
|
3. **属性**(attribute):属性字段指明了这段内存的内存属性,包括内存类型(位 2、3、4)与可共享性(位 8、9)。在 [aarch64/src/paging/memory_attribute.rs](https://github.com/equation314/aarch64/blob/master/src/paging/memory_attribute.rs) 中预定义了 3 中内存属性,分别为:
|
||||||
|
|
||||||
1. Normal:普通可缓存内存,cache 属性为 Write-Back Non-transient Read-Allocate Write-Allocate,内部共享;
|
1. Normal:普通可缓存内存,cache 属性为 Write-Back Non-transient Read-Allocate Write-Allocate,内部共享;
|
||||||
2. Device:Device-nGnRE 类型的内存,不可缓存,外部共享;
|
2. Device:Device-nGnRE 类型的内存,不可缓存,外部共享;
|
||||||
@ -216,7 +216,7 @@ ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下
|
|||||||
* 2 级页表:`R || R || IA[47..39] || IA[38..30]`;
|
* 2 级页表:`R || R || IA[47..39] || IA[38..30]`;
|
||||||
* 1 级页表:`R || IA[47..39] || IA[38..30] || IA[29..21]`;
|
* 1 级页表:`R || IA[47..39] || IA[38..30] || IA[29..21]`;
|
||||||
|
|
||||||
在 `aarch64/src/paging/recursive.rs` 中,为结构体 `RecursivePageTable` 实现了一系列函数,主要几个如下:
|
在 [aarch64/src/paging/recursive.rs](https://github.com/equation314/aarch64/blob/master/src/paging/recursive.rs) 中,为结构体 `RecursivePageTable` 实现了一系列函数,主要的几个如下:
|
||||||
|
|
||||||
* `new(table: PageTable)`:将虚拟地址表示的 `table` 作为 4 级页表,新建 `RecursivePageTable` 对象;
|
* `new(table: PageTable)`:将虚拟地址表示的 `table` 作为 4 级页表,新建 `RecursivePageTable` 对象;
|
||||||
* `map_to(self, page: Page<Size4KiB>, frame: PhysFrame<Size4KiB>, flags: PageTableFlags, attr: PageTableAttribute, allocator: FrameAllocator<Size4KiB>)`:将虚拟地址表示的**页** `page`,映射为物理地址表示的**帧** `frame`,并设置标志位 `flags` 和属性 `attr`,如果需要新分配页表就用 `allocator` 分配。页与帧的大小都是 4KB;
|
* `map_to(self, page: Page<Size4KiB>, frame: PhysFrame<Size4KiB>, flags: PageTableFlags, attr: PageTableAttribute, allocator: FrameAllocator<Size4KiB>)`:将虚拟地址表示的**页** `page`,映射为物理地址表示的**帧** `frame`,并设置标志位 `flags` 和属性 `attr`,如果需要新分配页表就用 `allocator` 分配。页与帧的大小都是 4KB;
|
||||||
@ -224,17 +224,17 @@ ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下
|
|||||||
|
|
||||||
### 实现内存管理
|
### 实现内存管理
|
||||||
|
|
||||||
下面是 `memory.rs` 与 `paging.rs` 中对内存管理的实现。
|
下面是 [memory.rs](../../../kernel/src/arch/aarch64/memory.rs) 与 [paging.rs](../../../kernel/src/arch/aarch64/paging.rs) 中对内存管理的实现。
|
||||||
|
|
||||||
#### 启用 MMU
|
#### 启用 MMU
|
||||||
|
|
||||||
由于在真机上使用原子指令需要 MMU 的支持,应该尽量早地启用 MMU,而 `boot.S` 中并没有建立页表与启用 MMU,所以启动完毕一进入 `rust_main()` 函数,就会调用 `memory.rs` 里的 `init_serial_early()` 函数来启用 MMU。
|
由于在真机上使用原子指令需要 MMU 的支持,应该尽量早地启用 MMU,而 [boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 中并没有建立页表与启用 MMU,所以启动完毕一进入 `rust_main()` 函数,就会调用 [memory.rs](../../../kernel/src/arch/aarch64/memory.rs#L20) 里的 `init_mmu_early()` 函数来启用 MMU。
|
||||||
|
|
||||||
该函数主要做了下面三件事:
|
该函数主要做了下面三件事:
|
||||||
|
|
||||||
1. 建立一个临时的地址翻译系统。
|
1. 建立一个临时的地址翻译系统。
|
||||||
|
|
||||||
这部分的代码主要位于 `paging.rs` 里的 `setup_temp_page_table()` 函数。这个地址翻译系统只是临时的,未进行细致的权限控制,更细致的地址翻译系统将在下节“重新映射内核地址空间”中建立。
|
这部分的代码主要位于 [paging.rs](../../../kernel/src/arch/aarch64/paging.rs#L14) 里的 `setup_temp_page_table()` 函数。这个地址翻译系统只是临时的,未进行细致的权限控制,更细致的地址翻译系统将在下节“重新映射内核地址空间”中建立。
|
||||||
|
|
||||||
该过程共需要三个 4KB 大小的页面,分别用于第 2~4 级页表。然后按下图方式映射:
|
该过程共需要三个 4KB 大小的页面,分别用于第 2~4 级页表。然后按下图方式映射:
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下
|
|||||||
|
|
||||||
#### 初始化内存管理
|
#### 初始化内存管理
|
||||||
|
|
||||||
这部分实现在 `memory.rs` 里的 `init()` 函数中。包含下列三部分:
|
这部分实现在 [memory.rs](../../../kernel/src/arch/aarch64/memory.rs#L12) 里的 `init()` 函数中。包含下列三部分:
|
||||||
|
|
||||||
1. 探测物理内存,初始化物理页帧分配器:由函数 `init_frame_allocator()`,先用 atags 库探测出可用物理内存的区间 `[start, end]`,然后以 4KB 大小的页为单位插入 `FRAME_ALLOCATOR` 中。由于 Raspberry Pi 3 拥有 1GB 内存,物理页帧数多达 256K,所以需要使用 `bit_allocator::BitAlloc1M`。
|
1. 探测物理内存,初始化物理页帧分配器:由函数 `init_frame_allocator()`,先用 atags 库探测出可用物理内存的区间 `[start, end]`,然后以 4KB 大小的页为单位插入 `FRAME_ALLOCATOR` 中。由于 Raspberry Pi 3 拥有 1GB 内存,物理页帧数多达 256K,所以需要使用 `bit_allocator::BitAlloc1M`。
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ ASID 的引入就是为了避免在上下文切换过程中刷新 TLB。上下
|
|||||||
|
|
||||||
`InactivePageTable` 定义了一个**非活跃页表**,准确地说是由第 4 级页表基址指定的一套地址翻译系统,一般随着 `MemorySet` 的建立而建立。除了在 `remap_the_kernel()` 中建立的是内核新页表,其他时候都是用户页表。
|
`InactivePageTable` 定义了一个**非活跃页表**,准确地说是由第 4 级页表基址指定的一套地址翻译系统,一般随着 `MemorySet` 的建立而建立。除了在 `remap_the_kernel()` 中建立的是内核新页表,其他时候都是用户页表。
|
||||||
|
|
||||||
在不同的平台下需要各自实现以下函数(`paging.rs` 中):
|
在不同的平台下需要各自实现以下函数([paging.rs](../../../kernel/src/arch/aarch64/paging.rs#L183) 中):
|
||||||
|
|
||||||
* `new_bare()`:新建一套地址翻译系统。首先调用 `alloc_frame()` 分配一个物理页帧用于存放唯一的 4 级页表,然后修改该物理页帧,使得其最后一项映射到自身,即建立自映射页表。通过调用 `active_table().with_temporary_map()` 让当前活跃的页表新建一个临时的映射,可在页表被激活后也能修改任意物理页帧。
|
* `new_bare()`:新建一套地址翻译系统。首先调用 `alloc_frame()` 分配一个物理页帧用于存放唯一的 4 级页表,然后修改该物理页帧,使得其最后一项映射到自身,即建立自映射页表。通过调用 `active_table().with_temporary_map()` 让当前活跃的页表新建一个临时的映射,可在页表被激活后也能修改任意物理页帧。
|
||||||
* `token()`:获取该翻译系统的页表基址,即第 4 级页表的物理地址。
|
* `token()`:获取该翻译系统的页表基址,即第 4 级页表的物理地址。
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
| 片上系统(SoC) | Broadcom BCM2837B0 |
|
| 片上系统(SoC) | Broadcom BCM2837B0 |
|
||||||
| 处理器(CPU) | 4 x Cortex-A53 1.4Ghz
|
| 处理器(CPU) | 4 x Cortex-A53 1.4Ghz
|
||||||
| 图形处理器(GPU) | Broadcom VideoCore IV |
|
| 图形处理器(GPU) | Broadcom VideoCore IV |
|
||||||
| 内存 | 1GB(与 GPU 共享) |
|
| 内存 | 1GB (与 GPU 共享) |
|
||||||
|
|
||||||
|
## AArch64 简介
|
||||||
|
|
||||||
## 官方文档
|
## 官方文档
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user