aarch64/doc: add overview of AArch64

master
equation314 6 years ago
parent 9bc0a89a94
commit e1d0d2a020

@ -17,7 +17,7 @@
``` ```
SECTIONS { SECTIONS {
. = 0x80000; /* Raspbery Pi 3 Aarch64 (kernel8.img) load address */ . = 0x80000; /* Raspbery Pi 3 AArch64 (kernel8.img) load address */
.boot : { .boot : {
KEEP(*(.text.boot)) /* from boot.S */ KEEP(*(.text.boot)) /* from boot.S */
@ -48,17 +48,10 @@ SECTIONS {
## boot.S ## boot.S
CPU 启动代码位于 [kernel/src/arch/aarch64/boot/boot.S](../../../kernel/src/arch/aarch64/boot/boot.S),负责初始化一些系统寄存器,并将当前异常级别(exception level)切换到 EL1。
AArch64 有 4 个异常级别,相当于 x86 的特权级,分别为:
* EL0: Applications.
* EL1: OS kernel and associated functions that are typically described as privileged.
* EL2: Hypervisor.
* EL3: Secure monitor.
在 RustOS 中,内核将运行在 EL1 上,用户程序将运行在 EL0 上。 在 RustOS 中,内核将运行在 EL1 上,用户程序将运行在 EL0 上。
CPU 启动代码位于 [kernel/src/arch/aarch64/boot/boot.S](../../../kernel/src/arch/aarch64/boot/boot.S),负责初始化一些系统寄存器,并将当前异常级别切换到 EL1。
[boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 的主要流程如下: [boot.S](../../../kernel/src/arch/aarch64/boot/boot.S) 的主要流程如下:
1. 获取核的编号,目前只使用 0 号核,其余核将被闲置: 1. 获取核的编号,目前只使用 0 号核,其余核将被闲置:

@ -56,7 +56,7 @@
} }
``` ```
每个进程控制块 `Process` ([kernel/src/process/context.rs](../../../kernel/src/process/context.rs#L13)) 都会维护一个平台相关的 `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`:页表基址;
@ -79,13 +79,13 @@ pub unsafe fn switch(&mut self, target: &mut Self) {
### 页表切换与 ASID 机制 ### 页表切换与 ASID 机制
首先进行的是页表的切换,即向 `TTBR1_EL1` 寄存器写入目标线程页表基址 `target.ttbr`。一般来说,切换页表后需要刷新 TLB不过 aarch64 引入了 ASID (Address Space ID) 机制来避免频繁刷新 TLB。 首先进行的是页表的切换,即向 `TTBR1_EL1` 寄存器写入目标线程页表基址 `target.ttbr`。一般来说,切换页表后需要刷新 TLB不过 ARMv8 引入了 ASID (Address Space ID) 机制来避免频繁刷新 TLB。
#### ASID 机制 #### ASID 机制
在页表项描述符中,有一个 `nG` 位,如果该位为 0表示这页内存是全局可访问的(用于内核空间);如果该位为 1表示这页内存不是全局可访问的只有特定线程可访问。具体地如果页表项中该位为 1当访问相应虚拟地址更新 TLB 时,会有额外的信息被写入 TLB该信息即 ASID由操作系统分配下次在 TLB 中查找该虚拟地址时就会检查 TLB 表项中的 ASID 是否与当前 ASID 匹配。相当于为不同的 ASID 各自创建了一个页表。 在页表项描述符中,有一个 nG 位,如果该位为 0表示这页内存是全局可访问的(用于内核空间);如果该位为 1表示这页内存不是全局可访问的只有特定线程可访问。具体地如果页表项中该位为 1当访问相应虚拟地址更新 TLB 时,会有额外的信息被写入 TLB该信息即 ASID由操作系统分配下次在 TLB 中查找该虚拟地址时就会检查 TLB 表项中的 ASID 是否与当前 ASID 匹配。相当于为不同的 ASID 各自创建了一个页表。
ASID 的大小可以为 8 位或 16 位,由 `TCR_EL1``AS` 字段指定,当前的 ASID 保存在 TTBR 的高位中,也可以由 `TCR_EL1``A1` 字段指定是 `TTBR0_EL1` 还是 `TTBR1_EL1`。在 RustOS 中ASID 大小为 16 位,当前 ASID 保存在 `TTBR1_EL1` 的高 16 位。 ASID 的大小可以为 8 位或 16 位,由 `TCR_EL1` 的 AS 字段指定,当前的 ASID 保存在 TTBR 的高位中,也可以由 `TCR_EL1``A1` 字段指定是 `TTBR0_EL1` 还是 `TTBR1_EL1`。在 RustOS 中ASID 大小为 16 位,当前 ASID 保存在 `TTBR1_EL1` 的高 16 位。
`switch()` 函数里,首先会为目标线程分配一个 ASID然后同时将该 ASID 与 `target.ttbr` 写入 `TTBR1_EL1` 即可,无需进行 TLB 刷新。 `switch()` 函数里,首先会为目标线程分配一个 ASID然后同时将该 ASID 与 `target.ttbr` 写入 `TTBR1_EL1` 即可,无需进行 TLB 刷新。
@ -178,9 +178,9 @@ ret
为什么只保存了 `sp` 与 callee-saved 寄存器,而不是所有寄存器?因为执行上下文切换就是在调用一个函数,在调用前后编译器会自动保存并恢复 caller-saved 寄存器(调用者保存,即 `x0~x18`)。 为什么只保存了 `sp` 与 callee-saved 寄存器,而不是所有寄存器?因为执行上下文切换就是在调用一个函数,在调用前后编译器会自动保存并恢复 caller-saved 寄存器(调用者保存,即 `x0~x18`)。
### 特权级切换 ### 异常级别切换
特权级保存在 `TrapFrame``spsr` 中,在中断返回后即能自动进行特权级切换。通过构造特定的 `spsr` 可让新线程运行在指定的特权级 异常发生前的异常级别保存在 `TrapFrame``spsr` 的相应位,在异常返回后会恢复给 PSTATE实现异常级别切换。通过构造特定的 `spsr` 可让新线程运行在指定的异常级别
## 创建新线程 ## 创建新线程

@ -61,9 +61,9 @@
* `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`

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

@ -51,20 +51,18 @@
### 异常屏蔽 ### 异常屏蔽
某些异常可以被**屏蔽**(mask),即发生时不跳转到相应的异常向量。可被屏蔽的异常包括所有异步异常与调试时的同步异常(debug exceptions),共 4 种,分别由 `DAIF` 4 个位控制: 某些异常可以被**屏蔽**(mask),即发生时不跳转到相应的异常向量。可被屏蔽的异常包括所有异步异常与调试时的同步异常(debug exceptions),共 4 种,分别由 PSTATE 中 `DAIF` 字段的 4 个位控制:
1. `D`: Debug exception 1. `D`: Debug exception
2. `A`: SError interrupt 2. `A`: SError interrupt
3. `I`: IRQ interrupt 3. `I`: IRQ interrupt
4. `F`: FIQ interrupt 4. `F`: FIQ interrupt
可通过设置 `SPSR_ELx`、`DAIF`、`DAIFSet` 、`DAIFClr` 等特殊目的寄存器(special-purpose registers)中相应的位来选择屏蔽或取消屏蔽相应的异常。
### 异常返回 ### 异常返回
当发生异常时,异常返回地址会被设置,保存在**异常链接寄存器**(Exception Link Register, ELR) `ELR_ELx` 中。 当发生异常时,异常返回地址会被设置,保存在**异常链接寄存器**(Exception Link Register, ELR) `ELR_ELx`;当前的**进程状态 PSTATE** 会保存在**保存的进程状态寄存器**(Saved Process Status Register, SPSR) `SPSR_ELx`
异常返回使用 **`eret`** 指令完成。当异常返回时,`PC` 会根据当前特权级被恢复为 `ELR_ELx` 中的,**进程状态** `PSTATE` (process state)也会被恢复为 `SPSR_ELx` 中的。`PSTATE` 是当前进程状态信息的抽象,保存在 `SPSR_ELx` 中,包含条件标志位 `NZCV`、异常屏蔽位 `DAIF`、当前特权级等信息。如果修改了 `SPSR_ELx` 中相应的位并进行异常返回,就能实现异常级别切换、异常开启/屏蔽等功能。 异常返回使用 **`eret`** 指令完成。当异常返回时,`pc` 会根据当前特权级被恢复为 `ELR_ELx` 中的PSTATE 也会被恢复为 `SPSR_ELx` 中的。通过修改 `SPSR_ELx` 中相应的位并进行异常返回,就能使 PSTATE 被修改,从而实现异常级别切换、异常开启/屏蔽等功能。
### 系统调用 ### 系统调用
@ -76,7 +74,7 @@
### 异常启用与屏蔽 ### 异常启用与屏蔽
在 [interrupt/mod.rs](../../../kernel/src/arch/aarch64/interrupt/mod.rs#L24) 中使用了 `DAIFClr``DAIFSet` 特殊目的寄存器分别实现了异常的启用与屏蔽(仅针对 IRQ),分别为 `enable()``disable()` 函数,代码如下: 在 [interrupt/mod.rs](../../../kernel/src/arch/aarch64/interrupt/mod.rs#L24) 中,通过写入 `DAIFClr``DAIFSet` 特殊寄存器修改 PSTATE分别实现了异常的启用与屏蔽(仅针对 IRQ),代码如下:
```rust ```rust
/// Enable the interrupt (only IRQ). /// Enable the interrupt (only IRQ).
@ -92,7 +90,7 @@ pub unsafe fn disable() {
} }
``` ```
此外,也可在异常返回前通过修改保存的 `SPSR` 寄存器启用或屏蔽异常,详见 [interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L26) 中的 `TrapFrame::new_kernel_thread()``TrapFrame::new_user_thread()` 函数。 此外,也可在异常返回前修改保存的 `SPSR_EL1` 寄存器,使得异常返回时 PSTATE 改变,从而实现启用或屏蔽异常,详见 [interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L26) 中的 `TrapFrame::new_kernel_thread()``TrapFrame::new_user_thread()` 函数。
### 异常向量 ### 异常向量
@ -137,7 +135,7 @@ __trapret:
流程如下: 流程如下:
1. 首先通过宏 `SAVE_ALL` 保存各寄存器,构成 `TrapFrame` 1. 首先通过宏 `SAVE_ALL` 保存各寄存器,构成 `TrapFrame`
2. 然后构造函数参数 `x0`、`x1`、`x2`,分别表示异常类型、异常症状 `ESR`、`TrapFrame`,并调用 Rust 异常处理函数 `rust_trap()` 2. 然后构造函数参数 `x0`、`x1`、`x2`,分别表示异常类型、异常症状 ESR、`TrapFrame`,并调用 Rust 异常处理函数 `rust_trap()`
3. 当该函数返回时,通过宏 `RESTORE_ALL``TrapFrame` 中恢复各寄存器; 3. 当该函数返回时,通过宏 `RESTORE_ALL``TrapFrame` 中恢复各寄存器;
4. 最后通过 `eret` 指令进行异常返回。 4. 最后通过 `eret` 指令进行异常返回。
@ -157,17 +155,17 @@ pub struct TrapFrame {
} }
``` ```
目前保存的寄存器包括:通用寄存器 `x0~x30`、异常返回地址 `ELR`、栈指针 `SP`、进程状态 `SPSR`。由于在 `aarch64-blog_os.json` 中禁用了 `NEON` 指令,不需要保存 `q0~q31` 这些 SIMD/FP 寄存器。 目前保存的寄存器包括:通用寄存器 `x0~x30`、异常返回地址 `elr_el1`、用户栈指针 `sp_el0`、进程状态 `spsr_el1`。由于在 `aarch64-blog_os.json` 中禁用了 NEON 指令,不需要保存 `q0~q31` 这些 SIMD/FP 寄存器。
`rust_trap()` 函数定义在 [interrupt/handler.rs](../../../kernel/src/arch/aarch64/interrupt/handler.rs#L43) 中。首先判断传入的 `kind` `rust_trap()` 函数定义在 [interrupt/handler.rs](../../../kernel/src/arch/aarch64/interrupt/handler.rs#L43) 中。首先判断传入的 `kind`
* 如果是 `Synchronous`:在 [interrupt/syndrome.rs](../../../kernel/src/arch/aarch64/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()`
#### 系统调用 #### 系统调用
如果 `ESR` 的异常类别是 `SVC`,则说明该异常由系统调用指令 `svc` 触发,紧接着会调用 `handle_syscall()` 函数。 如果 ESR 的异常类别是 SVC则说明该异常由系统调用指令 `svc` 触发,紧接着会调用 `handle_syscall()` 函数。
RustOS 的系统调用方式如下(实现在 [user/ucore-ulib/src/syscall.rs](../../../user/ucore-ulib/src/syscall.rs#L47) 中) RustOS 的系统调用方式如下(实现在 [user/ucore-ulib/src/syscall.rs](../../../user/ucore-ulib/src/syscall.rs#L47) 中)
@ -195,7 +193,7 @@ RustOS 的系统调用方式如下(实现在 [user/ucore-ulib/src/syscall.rs](..
#### IRQ #### 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 处理方式。 如果该异常是 IRQ则会调用 [kernel/src/arch/aarch64/board/raspi3/irq.rs](../../../kernel/src/arch/aarch64/board/raspi3/irq.rs#L8) 中的 `handle_irq()` 函数。该函数与具体的硬件板子相关,即使都是在 AArch64 下,不同 board 的 IRQ 处理方式也不一样,所以放到了模块 [kernel/src/arch/aarch64/board/raspi3](../../../kernel/src/arch/aarch64/board/raspi3/) 中,表示是 RPi3 特有的 IRQ 处理方式。
该函数首先会判断是否有时钟中断,如果有就先处理时钟中断: 该函数首先会判断是否有时钟中断,如果有就先处理时钟中断:

@ -4,7 +4,7 @@
> 参考ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile, capture D5: The AArch64 Virtual Memory System Architecture. > 参考ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile, capture D5: The AArch64 Virtual Memory System Architecture.
(注:AArch64 可能拥有一些可选的配置,如页大小、页表级数等,以下描述都是指在 RustOS 中的实现,不代表只有这一种实现方式) (注:完整的 AArch64 文档中描述了许多可选的配置,如页大小、翻译表级数等,以下描述都是指在 RustOS 中的实现,不代表只有这一种实现方式)
### 地址空间 (D5.1.3) ### 地址空间 (D5.1.3)
@ -148,7 +148,7 @@ Cache line 的大小可通过 `CTR_EL0` 寄存器读取,一般为 16 个 WORD
## RustOS 中的实现 ## RustOS 中的实现
在 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 底层系统寄存器、翻译表的访问。 在 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)
@ -333,8 +333,8 @@ Cache line 的大小可通过 `CTR_EL0` 寄存器读取,一般为 16 个 WORD
* `active_token()`获取当前活跃页表的页表基址寄存器内容。RustOS 中是读取 `TTBR1_EL1` * `active_token()`获取当前活跃页表的页表基址寄存器内容。RustOS 中是读取 `TTBR1_EL1`
* `flush_tlb()`:刷新 TLB 以立即生效对地址翻译系统的修改。 * `flush_tlb()`:刷新 TLB 以立即生效对地址翻译系统的修改。
* `edit()`:让该翻译系统变得可编辑。本质上是将当前活跃页表(`TTBR0_EL1` 指向的页表)的递归索引指向该翻译系统的第 4 级页表,使得所有对该翻译系统第 1~4 级页表的访问,都可直接通过自映射机制构造相应的虚拟地址来实现。同时由于不修改当前活跃页表的其他表项,对内核地址空间可正常访问。 * `edit()`:让该翻译系统变得可编辑。本质上是将当前活跃页表(`TTBR0_EL1` 指向的页表)的递归索引指向该翻译系统的第 4 级页表,使得所有对该翻译系统第 1~4 级页表的访问,都可直接通过自映射机制构造相应的虚拟地址来实现。同时由于不修改当前活跃页表的其他表项,对内核地址空间可正常访问。
* `new()`:建立用户页表时调用,会同时调用 `new_bare()``map_kernel()`。不过在 aarch64 下不需要 `map_kernel()` * `new()`:建立用户页表时调用,会同时调用 `new_bare()``map_kernel()`。不过在 AArch64 下不需要 `map_kernel()`
* `map_kernel()`:为了让用户程序陷入中断后能访问内核地址空间,需要在用户页表中映射内核的地址空间,通过 `edit()` 函数修改自身来实现。使用 `new()` 建立用户页表时会额外调用该函数。不过在 aarch64 下可使用 `TTBR0_EL1``TTBR1_EL1` 区分内核页表与用户页表,所以 aarch64 下该函数无效。 * `map_kernel()`:为了让用户程序陷入中断后能访问内核地址空间,需要在用户页表中映射内核的地址空间,通过 `edit()` 函数修改自身来实现。使用 `new()` 建立用户页表时会额外调用该函数。不过在 AArch64 下可使用 `TTBR0_EL1``TTBR1_EL1` 区分内核页表与用户页表,所以 AArch64 下该函数无效。
#### 分离内核页表与用户页表 #### 分离内核页表与用户页表

@ -2,17 +2,118 @@
## Raspberry Pi 简介 ## Raspberry Pi 简介
本实验的目标是将 Rust OS 移植到 Raspberry Pi 3 Model B+ 上。Raspberry Pi 3B+ 的主要硬件参数如下: 本实验的目标是将 RustOS 移植到 Raspberry Pi 3 Model B+ 上。其主要硬件参数如下:
| Raspberry Pi 3B+ | | | Raspberry Pi 3B+ | |
|-------|---------| |-------|---------|
| 指令集 | ARMv8-A 64 bit | | 指令集 | ARMv8-A 32/64 bit |
| 片上系统(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 简介 ## ARMv8 架构简介
### 运行状态
根据寄存器位数的不同ARMv8 架构定义了两种**运行状态**(Execution States),分别为 **AArch64****AArch32**。两种运行状态使用的指令集也不相同。RustOS 实现的是 AArch64。
### 异常级别
在 AArch64 下,可在 4 个**异常级别**(Exception level)中运行,分别为:
* EL0: Normal user applications.
* EL1: Operating system kernel typically described as *privileged*.
* EL2: Hypervisor.
* EL3: Secure monitor.
级别越高,特权(privilege)越高。一般称 EL0 为非特权级(unprivileged),其他的为特权级(unprivileged)。
### 寄存器
#### 通用寄存器
AArch64 有 31 个 64 位**通用寄存器**(General-purpose registers) `X0~X30`,每个 64 位寄存器都有一个 32 位的版本 `W0~W30`。寄存器的使用规范一般如下:
* 参数寄存器 (Argument registers, `X0~X7`):作为函数调用时的参数,返回值保存在 `X0`
* 调用者保存寄存器(Caller-saved temporary registers, `X9~X15`):在函数调用前,如果调用者需要保护这些寄存器中的值直到函数调用之后,则调用者需要将它们保存到当前栈帧上,而被调用者可直接使用而不必保存与恢复;
* 被调用者保存寄存器(Callee-saved registers, `X19~X29`):在函数调用中,如果该函数需要修改这些寄存器,则需要在函数开始执行前将它们保存到当前栈帧上,并在返回时恢复它们。
* 帧指针寄存器(Frame point register, `FP``X29`):用于保存当前函数的栈帧指针;
* 链接寄存器(Link register, `LR``X30`):用于保存函数返回的地址,执行 `ret` 指令会跳转到 `LR`
![](img/general-register.png)
#### 特殊寄存器
AArch64 有下列**特殊寄存器**(Special-purpose registers)
* 零寄存器(Zero register, ZR):被映射为立即数 0可分别用 `WZR/XZR` 访问 32/64 位版本;
* 程序计数器(Program counter, `PC`)当前指令的地址64 位;
* 栈指针(Stack pointer, `SP`)当前栈顶地址64 位。在每个异常级别下都有一个栈指针,分别为 `SP_EL0`、`SP_EL1`、`SP_EL2`、`SP_EL3`,直接访问 `SP` 时会根据当前异常级别自动选择对应的(如果 `SPSel = 0`,则在任何异常级别下都使用 `SP_EL0`)
* 异常链接寄存器(Exception Link Register, ELR):用于保存异常返回的地址,在异常级别 1~3 下分别为 `ELR_ELx`,执行 `eret` 指令进行异常返回时会根据当前异常级别跳转到相应的 ELR
* 保存的进程状态寄存器(Saved Process Status Register, SPSR):用于保存异常发生时的进程状态(PSTATE),在异常级别 1~3 下分别为 `SPSR_ELx`。执行 `eret` 指令进行异常返回时会根据当前异常级别,从相应的 SPSR 恢复进程状态。
#### 进程状态
**进程状态**(Process state, PSTATE)是当前进程状态信息的抽象。AArch64 提供了一系列特殊寄存器来独立访问 PSTATE 中的每个字段,常用的几个如下(完整的字段描述参见 ARMv8 Reference Manual D1.7 节)
* 条件标志位(Condition flags, `NZCV`)
+ `N`: Negative Condition flag
+ `Z`: Zero Condition flag
+ `C`: Carry Condition flag
+ `V`: oVerflow Condition flag
* 异常屏蔽位(exception masking bits, `DAIF`)
+ `D`: Debug exception mask bit
+ `A`: SError interrupt mask bit
+ `I`: IRQ interrupt mask bit
+ `F`: FIQ interrupt mask bit
* 当前异常级别(Current Exception level, `CurrentEL`):获取当前的异常级别;
* 栈指针选择(Stack Pointer Select, `SPSel`):如果为 1则在不同异常级别下分别使用相应的 `SP_ELx` 作为 `SP`,否则任何时候都使用 `SP_EL0` 作为 `SP`
当异常发生时,当前的 PSTATE 会根据进入哪个异常级别保存到相应的 `SPSR_ELx` 中。
#### 系统寄存器
可以通过**系统寄存器**(System registers)来进行 AArch64 的系统配置。一般每种系统寄存器在不同异常级别下都有相应的版本,用后缀 `_ELx` 表示。下表列出了常用的几个系统寄存器(全部系统寄存器参见 ARMv8 Reference Manual D12.1 节)
| 系统寄存器_ELx | 名称 | 描述 |
|------------------|-----------------------------------------|------------------------------|
| CTR | Cache Type Register | 获取 cache 信息 |
| ESR | Exception Syndrome Register | 保存发生异常的原因 |
| FAR | Fault Address Register | 保存发生访存错误的虚拟地址 |
| HCR | Hypervisor Configuration Register | 配置 EL2 下的虚拟化 |
| MAIR | Memory Attribute Indirection Register | 配置内存属性 |
| MPIDR | Multiprocessor Affinity Register | 多核系统中核的编号 |
| SCTLR | System Control Register | 提供对内存系统等系统配置 |
| TCR | Translation Control Register | 配置地址翻译系统的参数 |
| TTBR0/TTBR1 | Translation Table Base Register | 设置翻译表(页表)基址 |
| VBAR | Vector Based Address Register | 设置异常向量基址 |
对系统寄存器(包括部分特殊寄存器)的访问需要使用 `mrs``msr` 指令:
* 读:
```armasm
mrs x0, TTBR0_EL1
```
* 写:
```armasm
msr TTBR0_EL1, x0
```
crate [aarch64](https://github.com/equation314/aarch64) 的 [regs](https://github.com/equation314/aarch64/tree/master/src/regs) 模块封装了对部分系统寄存器的访问。
#### SIMD/FP 寄存器
共有 32 个最高为 128 位的 **SIMD/floating-point 寄存器**,可分别通过 `Q0~Q31`、`D0~D31`、`S0~S31`、`H0~H31`、`B0~B31` 访问其 128、64、32、16、8 位版本。
使用这些寄存器需要 NEON 技术的支持。为了方便,在 RustOS 中禁用了 NEON (Cargo features `+a53,+strict-align,-neon`),这样在处理异常时无需保存这些寄存器。
## 官方文档 ## 官方文档

@ -1,7 +1,7 @@
ENTRY(_start) ENTRY(_start)
SECTIONS { SECTIONS {
. = 0x80000; /* Raspbery Pi 3 Aarch64 (kernel8.img) load address */ . = 0x80000; /* Raspbery Pi 3 AArch64 (kernel8.img) load address */
.boot : { .boot : {
KEEP(*(.text.boot)) /* from boot.S */ KEEP(*(.text.boot)) /* from boot.S */

Loading…
Cancel
Save