aarch64/doc: add console driver

master
equation314 6 years ago
parent b4e0b38286
commit dfb2d49cd6

@ -208,11 +208,11 @@ pub extern "C" fn rust_main() -> ! {
流程如下: 流程如下:
1. 建立临时页表,启动 MMU 1. 建立临时页表,启动 MMU
2. 初始化串口输入输出,可以使用 `println!()` 等宏了 2. 初始化串口输入输出,可以使用 `println!()` 等宏了
3. 初始化 logging 模块,可以使用 `info!()`、`error!()` 等宏了 3. 初始化 logging 模块,可以使用 `info!()`、`error!()` 等宏了
4. 初始化中断,其实就是设置了异常向量基址 4. 初始化中断,其实就是设置了异常向量基址
5. 初始化内存管理,包括物理页帧分配器与内核堆分配器,最后会建立一个新的页表重新映射内核 5. 初始化内存管理,包括物理页帧分配器与内核堆分配器,最后会建立一个新的页表重新映射内核
6. 初始化其他设备驱动,包括 Frambuffer、Console、Timer 6. 初始化其他设备驱动,包括 Frambuffer、Console、Timer
7. 初始化进程管理,包括线程调度器、进程管理器,并为每个核建立一个 idle 线程,最后会加载 SFS 文件系统加入用户态 shell 进程 7. 初始化进程管理,包括线程调度器、进程管理器,并为每个核建立一个 idle 线程,最后会加载 SFS 文件系统加入用户态 shell 进程
8. 最后调用 `crate::kmain()`,按调度器轮流执行创建的线程。 8. 最后调用 `crate::kmain()`,按调度器轮流执行创建的线程。

@ -58,9 +58,9 @@
每个进程控制块 `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`:页表基址
3. `asid`Address Space ID详见下文“页表切换与 ASID 机制” 3. `asid`Address Space ID详见下文“页表切换与 ASID 机制”
## 切换流程 ## 切换流程
@ -135,8 +135,8 @@ impl AsidAllocator {
分配的流程如下: 分配的流程如下:
1. 判断 `old_asid` 是否等于 `self.0.generation`,如果相等说明这一代的 ASID 还是有效的,直接返回 `old_asid` 1. 判断 `old_asid` 是否等于 `self.0.generation`,如果相等说明这一代的 ASID 还是有效的,直接返回 `old_asid`
2. 否则,`old_asid` 已失效,如果当前代的 65535 个 ASID 没有分配完,就直接分配下一个 2. 否则,`old_asid` 已失效,如果当前代的 65535 个 ASID 没有分配完,就直接分配下一个
3. 如果当前代的 65535 个 ASID 都分配完了,就开始新的一代,同时刷新 TLB。 3. 如果当前代的 65535 个 ASID 都分配完了,就开始新的一代,同时刷新 TLB。
### 寄存器与栈的切换 ### 寄存器与栈的切换
@ -171,10 +171,10 @@ ret
流程如下: 流程如下:
1. 保存**当前栈顶** `sp``_self_stack` (`x0`),保存 **callee-saved 寄存器**到当前栈上 1. 保存**当前栈顶** `sp``_self_stack` (`x0`),保存 **callee-saved 寄存器**到当前栈上
2. 从 `_target_stack` (`x1`) 获取目标线程的**内核栈顶**,从目标线程内核栈顶恢复 **callee-saved 寄存器** 2. 从 `_target_stack` (`x1`) 获取目标线程的**内核栈顶**,从目标线程内核栈顶恢复 **callee-saved 寄存器**
4. 将 `sp` 设为目标线程内核栈顶,将 `_target_stack` (`x1`) 里的内容清空 3. 将 `sp` 设为目标线程内核栈顶,将 `_target_stack` (`x1`) 里的内容清空
5. 使用 `ret` 指令返回,这会跳转到目标线程 `lr` 寄存器中存放的地址。 4. 使用 `ret` 指令返回,这会跳转到目标线程 `lr` 寄存器中存放的地址。
为什么只保存了 `sp` 与 callee-saved 寄存器,而不是所有寄存器?因为执行上下文切换就是在调用一个函数,在调用前后编译器会自动保存并恢复 caller-saved 寄存器(调用者保存,即 `x0~x18`)。 为什么只保存了 `sp` 与 callee-saved 寄存器,而不是所有寄存器?因为执行上下文切换就是在调用一个函数,在调用前后编译器会自动保存并恢复 caller-saved 寄存器(调用者保存,即 `x0~x18`)。
@ -186,8 +186,8 @@ ret
线程可通过下列三种方式创建: 线程可通过下列三种方式创建:
1. 创建新的**内核线程**:直接给出一个内核函数 1. 创建新的**内核线程**:直接给出一个内核函数
2. 创建新的**用户线程**:解析 ELF 文件 2. 创建新的**用户线程**:解析 ELF 文件
3. 从一个线程 **fork** 出一个新线程:通过 `fork` 系统调用。 3. 从一个线程 **fork** 出一个新线程:通过 `fork` 系统调用。
三种线程的平台无关创建流程实现在 [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` 结构。 三种线程的平台无关创建流程实现在 [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` 结构。

@ -81,11 +81,11 @@ pub fn into_input(self) -> Gpio<Input> {
引脚的上拉/下拉状态有 3 种:上拉(`10`)、下拉(`01`)与不拉(`00`)。设置该状态的流程如下: 引脚的上拉/下拉状态有 3 种:上拉(`10`)、下拉(`01`)与不拉(`00`)。设置该状态的流程如下:
1. 向 GPPUD 寄存器写入状态代码 1. 向 GPPUD 寄存器写入状态代码
2. 等待 150 个时钟周期 2. 等待 150 个时钟周期
3. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 1 3. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 1
4. 等待 150 个时钟周期 4. 等待 150 个时钟周期
5. 向 GPPUD 寄存器写入 0 5. 向 GPPUD 寄存器写入 0
6. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 0。 6. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 0。
```rust ```rust
@ -189,15 +189,15 @@ RustOS 中 mini UART 的驱动主要实现在 crate [bcm2837](../../../crate/bcm
初始化 mini UART 的流程如下: 初始化 mini UART 的流程如下:
1. 向 AUX_ENABLES 寄存器写 1启用 mini UART 1. 向 AUX_ENABLES 寄存器写 1启用 mini UART
2. 将 GPIO 的 14/15 引脚都设为 alternative function ALT5 (TXD1/RXD1) 模式,并都设为不拉状态 2. 将 GPIO 的 14/15 引脚都设为 alternative function ALT5 (TXD1/RXD1) 模式,并都设为不拉状态
3. 配置 mini UART 参数: 3. 配置 mini UART 参数:
1. 暂时禁用接收器与发送器 1. 暂时禁用接收器与发送器
2. 启用接收中断,禁用发送中断 2. 启用接收中断,禁用发送中断
3. 设置数据大小为 8 bit 3. 设置数据大小为 8 bit
4. 设置 RTS line 为 high 4. 设置 RTS line 为 high
5. 设置波特率为 115200 5. 设置波特率为 115200
6. 重新启用接收器与发送器。 6. 重新启用接收器与发送器。
```rust ```rust
@ -243,8 +243,8 @@ pub fn write_byte(&mut self, byte: u8) {
BCM283x 系列可用下列三种不同的时钟: BCM283x 系列可用下列三种不同的时钟:
* System TimerBCM2837 ARM Peripherals 第 12 章IO 基地址为 `0x3F003000`,最常用的时钟,但是在 QEMU 中不可用 * System TimerBCM2837 ARM Peripherals 第 12 章IO 基地址为 `0x3F003000`,最常用的时钟,但是在 QEMU 中不可用
* ARM TimerBCM2837 ARM Peripherals 第 14 章IO 基地址为 `0x3F00B400`,在 QEMU 中也不可用RustOS 并未实现 * ARM TimerBCM2837 ARM Peripherals 第 14 章IO 基地址为 `0x3F00B400`,在 QEMU 中也不可用RustOS 并未实现
* Generic TimerARMv8 Reference Manual 第 D10 章,通过 AArch64 系统寄存器访问 CPU 的时钟,外围设备只提供了中断控制(IO 基地址为 `0x40000000`),可同时在 QEMU 与真机上使用。 * Generic TimerARMv8 Reference Manual 第 D10 章,通过 AArch64 系统寄存器访问 CPU 的时钟,外围设备只提供了中断控制(IO 基地址为 `0x40000000`),可同时在 QEMU 与真机上使用。
时钟主要实现在 crate [bcm2837](../../../crate/bcm2837/) 的 [timer](../../../crate/bcm2837/src/timer) 模块中。可以指定 crate bcm2837 的 feature `use_generic_timer` 来选择是否使用 Generic Timer。在 [mod.rs](../../../crate/bcm2837/src/timer/mod.rs#L12) 中提供了以下 `trait`,具体的时钟驱动需要实现这些函数: 时钟主要实现在 crate [bcm2837](../../../crate/bcm2837/) 的 [timer](../../../crate/bcm2837/src/timer) 模块中。可以指定 crate bcm2837 的 feature `use_generic_timer` 来选择是否使用 Generic Timer。在 [mod.rs](../../../crate/bcm2837/src/timer/mod.rs#L12) 中提供了以下 `trait`,具体的时钟驱动需要实现这些函数:
@ -288,9 +288,9 @@ if controller.is_pending() {
System Timer 通过 CS、CLO、CHI 等 IO 地址访问时钟,通过上文 Interrupt 节描述的 IRQ 控制器提供中断(IRQ 编号为 system timer 1)。实现方式如下: System Timer 通过 CS、CLO、CHI 等 IO 地址访问时钟,通过上文 Interrupt 节描述的 IRQ 控制器提供中断(IRQ 编号为 system timer 1)。实现方式如下:
* 初始化:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L68) 模块的 `enable()` 函数启用 system timer 1 IRQ * 初始化:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L68) 模块的 `enable()` 函数启用 system timer 1 IRQ
* 当前时刻:分别读取时钟计数器的高、低 32 位(CLO、CHI),再拼接起来得到 64 位计数器值(单位微秒) * 当前时刻:分别读取时钟计数器的高、低 32 位(CLO、CHI),再拼接起来得到 64 位计数器值(单位微秒)
* 设置下一次中断的时刻:向 System Timer Compare 1 (C1) 寄存器写入当前计数器值加上时间间隔,同时向 System Timer Control/Status (CS) 寄存器的第 1 位写入 1 表示当前的中断已被处理好 * 设置下一次中断的时刻:向 System Timer Compare 1 (C1) 寄存器写入当前计数器值加上时间间隔,同时向 System Timer Control/Status (CS) 寄存器的第 1 位写入 1 表示当前的中断已被处理好
* 判断是否有时钟中断:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L78) 模块的 `is_pending()` 函数。 * 判断是否有时钟中断:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L78) 模块的 `is_pending()` 函数。
```rust ```rust
@ -334,9 +334,9 @@ RustOS 实现的 Generic Timer 是 CPU 在 EL1 下的 Physical Timer可通过
而 Generic Timer 的中断控制器需要通过 `0x40000000` 开始的那些 IO 地址访问。Generic Timer 实现方式如下: 而 Generic Timer 的中断控制器需要通过 `0x40000000` 开始的那些 IO 地址访问。Generic Timer 实现方式如下:
* 初始化:将 `CNTP_CTL_EL0` 寄存器的 ENABLE 位置为 1启用 CPU Physical Timer将 Core0 timers Interrupt control 的 CNTPNSIRQ 位置为 1开启中断 * 初始化:将 `CNTP_CTL_EL0` 寄存器的 ENABLE 位置为 1启用 CPU Physical Timer将 Core0 timers Interrupt control 的 CNTPNSIRQ 位置为 1开启中断
* 当前时刻:读取 `CNTPCT_EL0` 寄存器获得当前时钟计数器的值,再与时钟频率 `CNTFRQ_EL0` 经过简单的换算即能得到以微秒为单位的当前时刻 * 当前时刻:读取 `CNTPCT_EL0` 寄存器获得当前时钟计数器的值,再与时钟频率 `CNTFRQ_EL0` 经过简单的换算即能得到以微秒为单位的当前时刻
* 设置下一次中断的时刻:向 `CNTP_TVAL_EL0` 寄存器写入时间间隔对应的时钟周期数 * 设置下一次中断的时刻:向 `CNTP_TVAL_EL0` 寄存器写入时间间隔对应的时钟周期数
* 判断是否有时钟中断:判断 Core0 IRQ Source 的 CNTPNSIRQ 位是否为 1。 * 判断是否有时钟中断:判断 Core0 IRQ Source 的 CNTPNSIRQ 位是否为 1。
```rust ```rust
@ -378,9 +378,9 @@ Mailbox 有若干通道(channels),不同通道提供不同种类的功能。
读的流程如下: 读的流程如下:
1. 读状态寄存器 MAIL0_STA直到 empty 位没有被设置 1. 读状态寄存器 MAIL0_STA直到 empty 位没有被设置
2. 从 MAIL0_RD 寄存器读取数据 2. 从 MAIL0_RD 寄存器读取数据
3. 如果数据的最低 4 位不与要读的通道匹配,则回到 1 3. 如果数据的最低 4 位不与要读的通道匹配,则回到 1
4. 否则返回数据的高 28 位。 4. 否则返回数据的高 28 位。
```rust ```rust
@ -397,8 +397,8 @@ pub fn read(&self, channel: MailboxChannel) -> u32 {
写的流程如下: 写的流程如下:
1. 读状态寄存器 MAIL1_STA直到 full 位没有被设置 1. 读状态寄存器 MAIL1_STA直到 full 位没有被设置
3. 将数据(高 28 位)与通道(低 4 位)拼接,写入 MAIL1_WRT 寄存器。 2. 将数据(高 28 位)与通道(低 4 位)拼接,写入 MAIL1_WRT 寄存器。
```rust ```rust
pub fn write(&mut self, channel: MailboxChannel, data: u32) { pub fn write(&mut self, channel: MailboxChannel, data: u32) {
@ -476,12 +476,12 @@ Framebuffer 是一块内存缓存区,树莓派的 GPU 会将其中的数据转
+ GPU 总线地址 `bus_addr` + GPU 总线地址 `bus_addr`
+ 大小 `screen_size` + 大小 `screen_size`
* `ColorDepth`:表示颜色深度的枚举值,目前支持 16 位和 32 位颜色深度 * `ColorDepth`:表示颜色深度的枚举值,目前支持 16 位和 32 位颜色深度
* `ColorBuffer`:一个 union 类型,可将同一个 framebuffer 基址解析为下列三种类型: * `ColorBuffer`:一个 union 类型,可将同一个 framebuffer 基址解析为下列三种类型:
+ 一个 32 位无符号整数,表示 framebuffer 基址的虚拟地址 + 一个 32 位无符号整数,表示 framebuffer 基址的虚拟地址
+ 一个类型为 16 位整数,大小为 framebuffer 分辨率的数组,表示 16 位颜色深度下的每个像素点 + 一个类型为 16 位整数,大小为 framebuffer 分辨率的数组,表示 16 位颜色深度下的每个像素点
+ 一个类型为 32 位整数,大小为 framebuffer 分辨率的数组,表示 32 位颜色深度下的每个像素点 + 一个类型为 32 位整数,大小为 framebuffer 分辨率的数组,表示 32 位颜色深度下的每个像素点
```rust ```rust
union ColorBuffer { union ColorBuffer {
@ -491,7 +491,7 @@ Framebuffer 是一块内存缓存区,树莓派的 GPU 会将其中的数据转
} }
``` ```
该 union 还提供了 `read16()`、`write16()`、`read32()`、`write32()` 等函数用于直接读写不同颜色深度下的 framebuffer 该 union 还提供了 `read16()`、`write16()`、`read32()`、`write32()` 等函数用于直接读写不同颜色深度下的 framebuffer
* `Framebuffer`:具体的 framebuffer 结构体: * `Framebuffer`:具体的 framebuffer 结构体:
@ -507,9 +507,9 @@ Framebuffer 是一块内存缓存区,树莓派的 GPU 会将其中的数据转
Framebuffer 在函数 `Framebuffer::new()` 中初始化。流程如下: Framebuffer 在函数 `Framebuffer::new()` 中初始化。流程如下:
1. 通过 mailbox property interface获取 framebuffer 物理分辨率、颜色深度等信息。也可以不获取,而是手动设置 1. 通过 mailbox property interface获取 framebuffer 物理分辨率、颜色深度等信息。也可以不获取,而是手动设置
2. 设置好相关参数,调用 `mailbox::framebuffer_alloc()` 由 GPU 分配 framebuffer构造出 `FramebufferInfo` 结构体 2. 设置好相关参数,调用 `mailbox::framebuffer_alloc()` 由 GPU 分配 framebuffer构造出 `FramebufferInfo` 结构体
3. 将 framebuffer GPU 总线地址转换为物理内存地址,然后调用 `memory::ioremap()` 将这段内存做对等映射,内存属性为 NormalNonCacheable 3. 将 framebuffer GPU 总线地址转换为物理内存地址,然后调用 `memory::ioremap()` 将这段内存做对等映射,内存属性为 NormalNonCacheable
4. 构造出 `Framebuffer` 结构体并返回。 4. 构造出 `Framebuffer` 结构体并返回。
### 读写 ### 读写
@ -521,3 +521,84 @@ Framebuffer 在函数 `Framebuffer::new()` 中初始化。流程如下:
`Framebuffer::clear()` 函数用于将屏幕清空(黑屏)。 `Framebuffer::clear()` 函数用于将屏幕清空(黑屏)。
## Console ## Console
有了 framebuffer就可以将显示器作为输出设备显示字符了。为此
Console (控制台) 是一个平台无关的抽象输出设备,表示屏幕上要显示的字符矩阵。该设备负责将字符转成像素点写入 framebuffer 中,以实现显示器中字符的显示,并支持颜色、字体等多种效果。
Console 驱动实现在模块 [kernel/src/arch/aarch64/driver/console](../../../kernel/src/arch/aarch64/driver/console/) 中,依赖 framebuffer包含下面几部分
1. 控制台主体([mod.rs](../../../kernel/src/arch/aarch64/driver/console/mod.rs))
2. 颜色([color.rs](../../../kernel/src/arch/aarch64/driver/console/color.rs))
3. 字体([fonts](../../../kernel/src/arch/aarch64/driver/console/fonts))
4. ANSI 转移序列解析器([escape_parser.rs](../../../kernel/src/arch/aarch64/driver/console/escape_parser.rs))
### 控制台主体
包含下列结构体:
* `ConsoleChar`:控制台中的字符,由 ASCII 码 `ascii_char` 与字符属性 `attr` (详见下节“ANSI 转移序列解析器”)构成。
* `ConsoleBuffer`:控制台字符缓冲区,是一个 `num_row``num_col` 列,元素类型是 `ConsoleChar` 的二维数组。主要包含以下函数:
+ `write()`:向 `(row, col)` 处写入一个字符 `ch`。这会根据给定的字体与字符属性,将字符转成像素点呈现在 framebuffer 上。
+ `new_line()`:向字符缓冲区的底部插入一个新行,并将原来的内容都向上平移一行。在真机上测试时,发现 framebuffer 读的速度非常慢,所以没有用 `Framebuffer::copy()` 函数直接拷贝 framebuffer 内容,而是根据新的字符缓冲区重新绘制。
+ `clear()`:清空屏幕与字符缓冲区内容。
* `Console`:具体的控制台结构体,通过传入的字体泛型 `<F: Font>` 构造,包含当前光标位置 `(row, col)`、 ANSI 转移序列解析器 `parser` 以及控制台字符缓冲区 `buffer`,主要包含以下函数:
+ `new()`:根据 `FramebufferInfo` 新建一个 `Console` 对象。当前字体下字符的高、宽与 framebuffer 分辨率将决定字符缓冲区的大小。
+ `write_byte()`:向当前光标位置处写入一个字符。这会根据具体是什么字符进行相应的操作,包括:直接显示该字符、换行、删除前一个字符、传给转移序列解析器。
+ `new_line()`:换行。如果当前光标位于字符缓冲区最底部则会调用 `ConsoleBuffer::new_line()` 移动屏幕内容。
+ `clear()`:清空并初始化。
此外,`Console` 实现了 trait `fmt::Write``write_str()` 函数,这样就可以用 `write_fmt()` 格式化输出了。
### 颜色
该模块定义了 16 种颜色(8 种标准颜色与 8 种高亮的标准颜色),并提供了将 RGB 值转换为 framebuffer 可识别的 16/32 位像素值的方法。
### 字体
该模块定义了统一的字体接口:
```rust
pub trait Font {
const HEIGHT: usize;
const WIDTH: usize;
/// the `y` coordinate of underline.
const UNDERLINE: usize;
/// the `y` coordinate of strikethrough.
const STRIKETHROUGH: usize;
/// Whether the character `byte` is visible at `(x, y)`.
fn get(byte: u8, x: usize, y: usize) -> bool;
}
```
添加一种字体只需实现该 trait 即可,支持下划线与删除线。目前内置了一种 8x16 的字体(直接从 linux 中拷贝而来,[CP437 编码](https://en.wikipedia.org/wiki/Code_page_437))。
### ANSI 转移序列解析器
> 参考https://en.wikipedia.org/wiki/ANSI_escape_code
为了在控制台上支持文字颜色等选项RustOS 实现了一个简易的 ANSI 转移序列(ANSI escape sequences)解析器可识别标准的ANSI 转移序列并呈现在屏幕上,支持下列 SGR (Select Graphic Rendition) 字符属性:
| SGR 代码 | 效果 |
|------------|------------------------|
| 0 | 重置为默认 |
| 4 | 下划线 |
| 7 | 反转前景与背景色 |
| 9 | 删除线 |
| 24 | 取消下划线 |
| 27 | 取消反转前景与背景色 |
| 29 | 取消删除线 |
| 30~37 | 设置前景色 |
| 40~47 | 设置背景色 |
| 90~97 | 设置高亮前景色 |
| 100~107 | 设置高亮背景色 |
具体实现时,结构体 `EscapeParser` 维护了一个状态机,通过 `parse()` 函数传入一个字符,转移状态,解析出 SGR 参数并更新当前字符属性;通过 `char_attribute()` 函数获取当前的字符属性。
目前显示效果与在终端下使用 `screen` 获取串口输出的效果一致,如在 QEMU 中运行 `fantastic_text` 测例的效果如下:
![](img/fantastic-text.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

@ -134,9 +134,9 @@ __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` 指令进行异常返回。
`TrapFrame` 定义在 [interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L12)中,结构如下: `TrapFrame` 定义在 [interrupt/context.rs](../../../kernel/src/arch/aarch64/interrupt/context.rs#L12)中,结构如下:
@ -169,9 +169,9 @@ pub struct TrapFrame {
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) 中)
* 将系统调用号保存在寄存器 `x8`,将 6 参数分别保存在寄存器 `x0~x5` 1. 将系统调用号保存在寄存器 `x8`,将 6 参数分别保存在寄存器 `x0~x5`
* 执行系统调用指令 `svc 0` 2. 执行系统调用指令 `svc 0`
* 系统调用返回值保存在寄存器 `x0` 中。 3. 系统调用返回值保存在寄存器 `x0` 中。
`handle_syscall()` 函数中,会从 `TrapFrame` 保存的寄存器中恢复系统调用参数,并调用 `crate::syscall::syscall()` 进行具体的系统调用。 `handle_syscall()` 函数中,会从 `TrapFrame` 保存的寄存器中恢复系统调用参数,并调用 `crate::syscall::syscall()` 进行具体的系统调用。

@ -45,8 +45,8 @@ AArch64 拥有 64 位地址,支持两段虚拟内存地址空间,分别为
翻译表描述符即翻译表项,由一段地址空间的基址与这段地址空间的属性构成。根据这段地址空间的用处不同,将描述符分为 3 类: 翻译表描述符即翻译表项,由一段地址空间的基址与这段地址空间的属性构成。根据这段地址空间的用处不同,将描述符分为 3 类:
1. **页描述符**(page descriptor):该描述符中的地址指向一个 4KB 大小的页 1. **页描述符**(page descriptor):该描述符中的地址指向一个 4KB 大小的页
2. **块描述符**(block descriptor):该描述符中的地址指向一个 1GB 或 2MB 大小的块 2. **块描述符**(block descriptor):该描述符中的地址指向一个 1GB 或 2MB 大小的块
3. **表描述符**(table descriptor):该描述符中的地址指向另一个翻译表。 3. **表描述符**(table descriptor):该描述符中的地址指向另一个翻译表。
#### 第 0, 1, 2 级翻译表描述符 #### 第 0, 1, 2 级翻译表描述符
@ -86,8 +86,8 @@ AArch64 拥有 64 位地址,支持两段虚拟内存地址空间,分别为
可共享性分为 3 种: 可共享性分为 3 种:
1. 不可共享,即每个核都不与其他核共享这段内存 1. 不可共享,即每个核都不与其他核共享这段内存
2. 内部共享,即多核之间可以共享这段内存 2. 内部共享,即多核之间可以共享这段内存
3. 外部共享即除了多核之间外CPU 与 GPU 之间也可共享这段内存。 3. 外部共享即除了多核之间外CPU 与 GPU 之间也可共享这段内存。
在块/页描述符的 SH 字段可设置内存的可共享性。 在块/页描述符的 SH 字段可设置内存的可共享性。
@ -164,8 +164,8 @@ Cache line 的大小可通过 `CTR_EL0` 寄存器读取,一般为 16 个 WORD
1. **地址**(address):位于描述符的第 12 到 47 位。根据描述符的 `TABLE_OR_PAGE` 位,分别指向下列 3 种地址: 1. **地址**(address):位于描述符的第 12 到 47 位。根据描述符的 `TABLE_OR_PAGE` 位,分别指向下列 3 种地址:
1. 页描述符(page descriptor):该地址指向一个 4KB 大小的页,该地址 4KB 对齐 1. 页描述符(page descriptor):该地址指向一个 4KB 大小的页,该地址 4KB 对齐
2. 块描述符(block descriptor):该地址指向一个 1GB 或 2MB 大小的块,该地址 1GB 或 2MB 对齐 2. 块描述符(block descriptor):该地址指向一个 1GB 或 2MB 大小的块,该地址 1GB 或 2MB 对齐
3. 表描述符(table descriptor):该地址指向另一个页表,该地址 4KB 对齐。 3. 表描述符(table descriptor):该地址指向另一个页表,该地址 4KB 对齐。
2. **标志位**(flags):仅由一个位来表示的内存属性。对于表/块描述符包含下列位: 2. **标志位**(flags):仅由一个位来表示的内存属性。对于表/块描述符包含下列位:
@ -201,8 +201,8 @@ Cache line 的大小可通过 `CTR_EL0` 寄存器读取,一般为 16 个 WORD
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 中内存属性,分别为: 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 类型的内存,不可缓存,外部共享
3. NormalNonCacheable普通不可缓存内存外部共享。 3. NormalNonCacheable普通不可缓存内存外部共享。
#### 自映射机制 #### 自映射机制
@ -211,15 +211,15 @@ Cache line 的大小可通过 `CTR_EL0` 寄存器读取,一般为 16 个 WORD
具体地,设**递归索引**为 `R` (RustOS 中为 `0o777 = 512`,即页表的最后一项),只需将第 4 级页表的第 `R` 项映射为第 4 级页表自身即可建立自映射页表。这样一来,一个虚拟地址 `IA` 的四级页表分别被以下虚拟地址所映射(`||` 表示比特串的连接) 具体地,设**递归索引**为 `R` (RustOS 中为 `0o777 = 512`,即页表的最后一项),只需将第 4 级页表的第 `R` 项映射为第 4 级页表自身即可建立自映射页表。这样一来,一个虚拟地址 `IA` 的四级页表分别被以下虚拟地址所映射(`||` 表示比特串的连接)
* 4 级页表:`R || R || R || R` * 4 级页表:`R || R || R || R`
* 3 级页表:`R || R || R || IA[47..39]` * 3 级页表:`R || R || R || IA[47..39]`
* 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](https://github.com/equation314/aarch64/blob/master/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
* `unmap(self, page: Page<Size4KiB>)`:取消虚拟地址表示的页 `page` 的映射。 * `unmap(self, page: Page<Size4KiB>)`:取消虚拟地址表示的页 `page` 的映射。
### 实现内存管理 ### 实现内存管理

@ -35,10 +35,10 @@
AArch64 有 31 个 64 位**通用寄存器**(General-purpose registers) `X0~X30`,每个 64 位寄存器都有一个 32 位的版本 `W0~W30`。寄存器的使用规范一般如下: AArch64 有 31 个 64 位**通用寄存器**(General-purpose registers) `X0~X30`,每个 64 位寄存器都有一个 32 位的版本 `W0~W30`。寄存器的使用规范一般如下:
* 参数寄存器 (Argument registers, `X0~X7`):作为函数调用时的参数,返回值保存在 `X0` * 参数寄存器 (Argument registers, `X0~X7`):作为函数调用时的参数,返回值保存在 `X0`
* 调用者保存寄存器(Caller-saved temporary registers, `X9~X15`):在函数调用前,如果调用者需要保护这些寄存器中的值直到函数调用之后,则调用者需要将它们保存到当前栈帧上,而被调用者可直接使用而不必保存与恢复 * 调用者保存寄存器(Caller-saved temporary registers, `X9~X15`):在函数调用前,如果调用者需要保护这些寄存器中的值直到函数调用之后,则调用者需要将它们保存到当前栈帧上,而被调用者可直接使用而不必保存与恢复
* 被调用者保存寄存器(Callee-saved registers, `X19~X29`):在函数调用中,如果该函数需要修改这些寄存器,则需要在函数开始执行前将它们保存到当前栈帧上,并在返回时恢复它们。 * 被调用者保存寄存器(Callee-saved registers, `X19~X29`):在函数调用中,如果该函数需要修改这些寄存器,则需要在函数开始执行前将它们保存到当前栈帧上,并在返回时恢复它们。
* 帧指针寄存器(Frame point register, `FP``X29`):用于保存当前函数的栈帧指针 * 帧指针寄存器(Frame point register, `FP``X29`):用于保存当前函数的栈帧指针
* 链接寄存器(Link register, `LR``X30`):用于保存函数返回的地址,执行 `ret` 指令会跳转到 `LR` * 链接寄存器(Link register, `LR``X30`):用于保存函数返回的地址,执行 `ret` 指令会跳转到 `LR`
![](img/general-register.png) ![](img/general-register.png)
@ -47,10 +47,10 @@ AArch64 有 31 个 64 位**通用寄存器**(General-purpose registers) `X0~X30`
AArch64 有下列**特殊寄存器**(Special-purpose registers) AArch64 有下列**特殊寄存器**(Special-purpose registers)
* 零寄存器(Zero register, ZR):被映射为立即数 0可分别用 `WZR/XZR` 访问 32/64 位版本 * 零寄存器(Zero register, ZR):被映射为立即数 0可分别用 `WZR/XZR` 访问 32/64 位版本
* 程序计数器(Program counter, `PC`)当前指令的地址64 位 * 程序计数器(Program counter, `PC`)当前指令的地址64 位
* 栈指针(Stack pointer, `SP`)当前栈顶地址64 位。在每个异常级别下都有一个栈指针,分别为 `SP_EL0`、`SP_EL1`、`SP_EL2`、`SP_EL3`,直接访问 `SP` 时会根据当前异常级别自动选择对应的(如果 `SPSel = 0`,则在任何异常级别下都使用 `SP_EL0`) * 栈指针(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 * 异常链接寄存器(Exception Link Register, ELR):用于保存异常返回的地址,在异常级别 1~3 下分别为 `ELR_ELx`,执行 `eret` 指令进行异常返回时会根据当前异常级别跳转到相应的 ELR
* 保存的进程状态寄存器(Saved Process Status Register, SPSR):用于保存异常发生时的进程状态(PSTATE),在异常级别 1~3 下分别为 `SPSR_ELx`。执行 `eret` 指令进行异常返回时会根据当前异常级别,从相应的 SPSR 恢复进程状态。 * 保存的进程状态寄存器(Saved Process Status Register, SPSR):用于保存异常发生时的进程状态(PSTATE),在异常级别 1~3 下分别为 `SPSR_ELx`。执行 `eret` 指令进行异常返回时会根据当前异常级别,从相应的 SPSR 恢复进程状态。
#### 进程状态 #### 进程状态
@ -71,7 +71,7 @@ AArch64 有下列**特殊寄存器**(Special-purpose registers)
+ `I`: IRQ interrupt mask bit + `I`: IRQ interrupt mask bit
+ `F`: FIQ interrupt mask bit + `F`: FIQ interrupt mask bit
* 当前异常级别(Current Exception level, `CurrentEL`):获取当前的异常级别 * 当前异常级别(Current Exception level, `CurrentEL`):获取当前的异常级别
* 栈指针选择(Stack Pointer Select, `SPSel`):如果为 1则在不同异常级别下分别使用相应的 `SP_ELx` 作为 `SP`,否则任何时候都使用 `SP_EL0` 作为 `SP` * 栈指针选择(Stack Pointer Select, `SPSel`):如果为 1则在不同异常级别下分别使用相应的 `SP_ELx` 作为 `SP`,否则任何时候都使用 `SP_EL0` 作为 `SP`
当异常发生时,当前的 PSTATE 会根据进入哪个异常级别保存到相应的 `SPSR_ELx` 中。 当异常发生时,当前的 PSTATE 会根据进入哪个异常级别保存到相应的 `SPSR_ELx` 中。

@ -8,7 +8,9 @@ pub trait Font {
const HEIGHT: usize; const HEIGHT: usize;
const WIDTH: usize; const WIDTH: usize;
/// The `y` coordinate of underline.
const UNDERLINE: usize; const UNDERLINE: usize;
/// The `y` coordinate of strikethrough.
const STRIKETHROUGH: usize; const STRIKETHROUGH: usize;
/// Whether the character `byte` is visible at `(x, y)`. /// Whether the character `byte` is visible at `(x, y)`.

Loading…
Cancel
Save