1
0
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:
equation314 2018-12-31 17:23:11 +08:00
parent 1523e7ea5f
commit 9bc0a89a94
6 changed files with 54 additions and 52 deletions

View File

@ -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()`,按调度器轮流行创建的线程。

View File

@ -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` 结构返回。这两个结构的构造方式如下:

View File

@ -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 内核镜像:

View File

@ -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 已注册,就调用它的处理函数:

View File

@ -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 也不使用 ASIDTLB 中将会保留旧进程的虚拟——物理地址映射关系,导致进程访问错误的物理地址。不过如果每次上下文切换都刷新 TLB开销又较大。 在上下文切换时,需要修改 TTBR1_EL1 寄存器,如果不刷新 TLB 也不使用 ASIDTLB 中将会保留旧进程的虚拟——物理地址映射关系,导致进程访问错误的物理地址。不过如果每次上下文切换都刷新 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. DeviceDevice-nGnRE 类型的内存,不可缓存,外部共享; 2. DeviceDevice-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 级页表的物理地址。

View File

@ -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 简介
## 官方文档 ## 官方文档