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

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

@ -81,11 +81,11 @@ pub fn into_input(self) -> Gpio<Input> {
引脚的上拉/下拉状态有 3 种:上拉(`10`)、下拉(`01`)与不拉(`00`)。设置该状态的流程如下:
1. 向 GPPUD 寄存器写入状态代码
2. 等待 150 个时钟周期
3. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 1
4. 等待 150 个时钟周期
5. 向 GPPUD 寄存器写入 0
1. 向 GPPUD 寄存器写入状态代码
2. 等待 150 个时钟周期
3. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 1
4. 等待 150 个时钟周期
5. 向 GPPUD 寄存器写入 0
6. 根据引脚编号向相应的 GPPUDCLK0/1 寄存器的相应位写入 0。
```rust
@ -189,15 +189,15 @@ RustOS 中 mini UART 的驱动主要实现在 crate [bcm2837](../../../crate/bcm
初始化 mini UART 的流程如下:
1. 向 AUX_ENABLES 寄存器写 1启用 mini UART
2. 将 GPIO 的 14/15 引脚都设为 alternative function ALT5 (TXD1/RXD1) 模式,并都设为不拉状态
1. 向 AUX_ENABLES 寄存器写 1启用 mini UART
2. 将 GPIO 的 14/15 引脚都设为 alternative function ALT5 (TXD1/RXD1) 模式,并都设为不拉状态
3. 配置 mini UART 参数:
1. 暂时禁用接收器与发送器
2. 启用接收中断,禁用发送中断
3. 设置数据大小为 8 bit
4. 设置 RTS line 为 high
5. 设置波特率为 115200
1. 暂时禁用接收器与发送器
2. 启用接收中断,禁用发送中断
3. 设置数据大小为 8 bit
4. 设置 RTS line 为 high
5. 设置波特率为 115200
6. 重新启用接收器与发送器。
```rust
@ -243,8 +243,8 @@ pub fn write_byte(&mut self, byte: u8) {
BCM283x 系列可用下列三种不同的时钟:
* System TimerBCM2837 ARM Peripherals 第 12 章IO 基地址为 `0x3F003000`,最常用的时钟,但是在 QEMU 中不可用
* ARM TimerBCM2837 ARM Peripherals 第 14 章IO 基地址为 `0x3F00B400`,在 QEMU 中也不可用RustOS 并未实现
* System TimerBCM2837 ARM Peripherals 第 12 章IO 基地址为 `0x3F003000`,最常用的时钟,但是在 QEMU 中不可用
* ARM TimerBCM2837 ARM Peripherals 第 14 章IO 基地址为 `0x3F00B400`,在 QEMU 中也不可用RustOS 并未实现
* 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`,具体的时钟驱动需要实现这些函数:
@ -288,9 +288,9 @@ if controller.is_pending() {
System Timer 通过 CS、CLO、CHI 等 IO 地址访问时钟,通过上文 Interrupt 节描述的 IRQ 控制器提供中断(IRQ 编号为 system timer 1)。实现方式如下:
* 初始化:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L68) 模块的 `enable()` 函数启用 system timer 1 IRQ
* 当前时刻:分别读取时钟计数器的高、低 32 位(CLO、CHI),再拼接起来得到 64 位计数器值(单位微秒)
* 设置下一次中断的时刻:向 System Timer Compare 1 (C1) 寄存器写入当前计数器值加上时间间隔,同时向 System Timer Control/Status (CS) 寄存器的第 1 位写入 1 表示当前的中断已被处理好
* 初始化:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L68) 模块的 `enable()` 函数启用 system timer 1 IRQ
* 当前时刻:分别读取时钟计数器的高、低 32 位(CLO、CHI),再拼接起来得到 64 位计数器值(单位微秒)
* 设置下一次中断的时刻:向 System Timer Compare 1 (C1) 寄存器写入当前计数器值加上时间间隔,同时向 System Timer Control/Status (CS) 寄存器的第 1 位写入 1 表示当前的中断已被处理好
* 判断是否有时钟中断:使用 [interrupt](../../../crate/bcm2837/src/interrupt.rs#L78) 模块的 `is_pending()` 函数。
```rust
@ -334,9 +334,9 @@ RustOS 实现的 Generic Timer 是 CPU 在 EL1 下的 Physical Timer可通过
而 Generic Timer 的中断控制器需要通过 `0x40000000` 开始的那些 IO 地址访问。Generic Timer 实现方式如下:
* 初始化:将 `CNTP_CTL_EL0` 寄存器的 ENABLE 位置为 1启用 CPU Physical Timer将 Core0 timers Interrupt control 的 CNTPNSIRQ 位置为 1开启中断
* 当前时刻:读取 `CNTPCT_EL0` 寄存器获得当前时钟计数器的值,再与时钟频率 `CNTFRQ_EL0` 经过简单的换算即能得到以微秒为单位的当前时刻
* 设置下一次中断的时刻:向 `CNTP_TVAL_EL0` 寄存器写入时间间隔对应的时钟周期数
* 初始化:将 `CNTP_CTL_EL0` 寄存器的 ENABLE 位置为 1启用 CPU Physical Timer将 Core0 timers Interrupt control 的 CNTPNSIRQ 位置为 1开启中断
* 当前时刻:读取 `CNTPCT_EL0` 寄存器获得当前时钟计数器的值,再与时钟频率 `CNTFRQ_EL0` 经过简单的换算即能得到以微秒为单位的当前时刻
* 设置下一次中断的时刻:向 `CNTP_TVAL_EL0` 寄存器写入时间间隔对应的时钟周期数
* 判断是否有时钟中断:判断 Core0 IRQ Source 的 CNTPNSIRQ 位是否为 1。
```rust
@ -378,9 +378,9 @@ Mailbox 有若干通道(channels),不同通道提供不同种类的功能。
读的流程如下:
1. 读状态寄存器 MAIL0_STA直到 empty 位没有被设置
2. 从 MAIL0_RD 寄存器读取数据
3. 如果数据的最低 4 位不与要读的通道匹配,则回到 1
1. 读状态寄存器 MAIL0_STA直到 empty 位没有被设置
2. 从 MAIL0_RD 寄存器读取数据
3. 如果数据的最低 4 位不与要读的通道匹配,则回到 1
4. 否则返回数据的高 28 位。
```rust
@ -397,8 +397,8 @@ pub fn read(&self, channel: MailboxChannel) -> u32 {
写的流程如下:
1. 读状态寄存器 MAIL1_STA直到 full 位没有被设置
3. 将数据(高 28 位)与通道(低 4 位)拼接,写入 MAIL1_WRT 寄存器。
1. 读状态寄存器 MAIL1_STA直到 full 位没有被设置
2. 将数据(高 28 位)与通道(低 4 位)拼接,写入 MAIL1_WRT 寄存器。
```rust
pub fn write(&mut self, channel: MailboxChannel, data: u32) {
@ -476,12 +476,12 @@ Framebuffer 是一块内存缓存区,树莓派的 GPU 会将其中的数据转
+ GPU 总线地址 `bus_addr`
+ 大小 `screen_size`
* `ColorDepth`:表示颜色深度的枚举值,目前支持 16 位和 32 位颜色深度
* `ColorDepth`:表示颜色深度的枚举值,目前支持 16 位和 32 位颜色深度
* `ColorBuffer`:一个 union 类型,可将同一个 framebuffer 基址解析为下列三种类型:
+ 一个 32 位无符号整数,表示 framebuffer 基址的虚拟地址
+ 一个类型为 16 位整数,大小为 framebuffer 分辨率的数组,表示 16 位颜色深度下的每个像素点
+ 一个类型为 32 位整数,大小为 framebuffer 分辨率的数组,表示 32 位颜色深度下的每个像素点
+ 一个 32 位无符号整数,表示 framebuffer 基址的虚拟地址
+ 一个类型为 16 位整数,大小为 framebuffer 分辨率的数组,表示 16 位颜色深度下的每个像素点
+ 一个类型为 32 位整数,大小为 framebuffer 分辨率的数组,表示 32 位颜色深度下的每个像素点
```rust
union ColorBuffer {
@ -491,7 +491,7 @@ Framebuffer 是一块内存缓存区,树莓派的 GPU 会将其中的数据转
}
```
该 union 还提供了 `read16()`、`write16()`、`read32()`、`write32()` 等函数用于直接读写不同颜色深度下的 framebuffer
该 union 还提供了 `read16()`、`write16()`、`read32()`、`write32()` 等函数用于直接读写不同颜色深度下的 framebuffer
* `Framebuffer`:具体的 framebuffer 结构体:
@ -507,9 +507,9 @@ Framebuffer 是一块内存缓存区,树莓派的 GPU 会将其中的数据转
Framebuffer 在函数 `Framebuffer::new()` 中初始化。流程如下:
1. 通过 mailbox property interface获取 framebuffer 物理分辨率、颜色深度等信息。也可以不获取,而是手动设置
2. 设置好相关参数,调用 `mailbox::framebuffer_alloc()` 由 GPU 分配 framebuffer构造出 `FramebufferInfo` 结构体
3. 将 framebuffer GPU 总线地址转换为物理内存地址,然后调用 `memory::ioremap()` 将这段内存做对等映射,内存属性为 NormalNonCacheable
1. 通过 mailbox property interface获取 framebuffer 物理分辨率、颜色深度等信息。也可以不获取,而是手动设置
2. 设置好相关参数,调用 `mailbox::framebuffer_alloc()` 由 GPU 分配 framebuffer构造出 `FramebufferInfo` 结构体
3. 将 framebuffer GPU 总线地址转换为物理内存地址,然后调用 `memory::ioremap()` 将这段内存做对等映射,内存属性为 NormalNonCacheable
4. 构造出 `Framebuffer` 结构体并返回。
### 读写
@ -521,3 +521,84 @@ Framebuffer 在函数 `Framebuffer::new()` 中初始化。流程如下:
`Framebuffer::clear()` 函数用于将屏幕清空(黑屏)。
## 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`
2. 然后构造函数参数 `x0`、`x1`、`x2`,分别表示异常类型、异常症状 ESR、`TrapFrame`,并调用 Rust 异常处理函数 `rust_trap()`
3. 当该函数返回时,通过宏 `RESTORE_ALL``TrapFrame` 中恢复各寄存器
1. 首先通过宏 `SAVE_ALL` 保存各寄存器,构成 `TrapFrame`
2. 然后构造函数参数 `x0`、`x1`、`x2`,分别表示异常类型、异常症状 ESR、`TrapFrame`,并调用 Rust 异常处理函数 `rust_trap()`
3. 当该函数返回时,通过宏 `RESTORE_ALL``TrapFrame` 中恢复各寄存器
4. 最后通过 `eret` 指令进行异常返回。
`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) 中)
* 将系统调用号保存在寄存器 `x8`,将 6 参数分别保存在寄存器 `x0~x5`
* 执行系统调用指令 `svc 0`
* 系统调用返回值保存在寄存器 `x0` 中。
1. 将系统调用号保存在寄存器 `x8`,将 6 参数分别保存在寄存器 `x0~x5`
2. 执行系统调用指令 `svc 0`
3. 系统调用返回值保存在寄存器 `x0` 中。
`handle_syscall()` 函数中,会从 `TrapFrame` 保存的寄存器中恢复系统调用参数,并调用 `crate::syscall::syscall()` 进行具体的系统调用。

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

@ -35,10 +35,10 @@
AArch64 有 31 个 64 位**通用寄存器**(General-purpose registers) `X0~X30`,每个 64 位寄存器都有一个 32 位的版本 `W0~W30`。寄存器的使用规范一般如下:
* 参数寄存器 (Argument registers, `X0~X7`):作为函数调用时的参数,返回值保存在 `X0`
* 调用者保存寄存器(Caller-saved temporary registers, `X9~X15`):在函数调用前,如果调用者需要保护这些寄存器中的值直到函数调用之后,则调用者需要将它们保存到当前栈帧上,而被调用者可直接使用而不必保存与恢复
* 参数寄存器 (Argument registers, `X0~X7`):作为函数调用时的参数,返回值保存在 `X0`
* 调用者保存寄存器(Caller-saved temporary registers, `X9~X15`):在函数调用前,如果调用者需要保护这些寄存器中的值直到函数调用之后,则调用者需要将它们保存到当前栈帧上,而被调用者可直接使用而不必保存与恢复
* 被调用者保存寄存器(Callee-saved registers, `X19~X29`):在函数调用中,如果该函数需要修改这些寄存器,则需要在函数开始执行前将它们保存到当前栈帧上,并在返回时恢复它们。
* 帧指针寄存器(Frame point register, `FP``X29`):用于保存当前函数的栈帧指针
* 帧指针寄存器(Frame point register, `FP``X29`):用于保存当前函数的栈帧指针
* 链接寄存器(Link register, `LR``X30`):用于保存函数返回的地址,执行 `ret` 指令会跳转到 `LR`
![](img/general-register.png)
@ -47,10 +47,10 @@ AArch64 有 31 个 64 位**通用寄存器**(General-purpose registers) `X0~X30`
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
* 零寄存器(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 恢复进程状态。
#### 进程状态
@ -71,7 +71,7 @@ AArch64 有下列**特殊寄存器**(Special-purpose registers)
+ `I`: IRQ 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`
当异常发生时,当前的 PSTATE 会根据进入哪个异常级别保存到相应的 `SPSR_ELx` 中。

@ -8,7 +8,9 @@ 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)`.

Loading…
Cancel
Save