diff --git a/docs/2_OSLab/g2/boot.md b/docs/2_OSLab/g2/boot.md
index c71d8f7..ee1d3be 100644
--- a/docs/2_OSLab/g2/boot.md
+++ b/docs/2_OSLab/g2/boot.md
@@ -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()`,按调度器轮流执行创建的线程。
diff --git a/docs/2_OSLab/g2/context.md b/docs/2_OSLab/g2/context.md
index 4390675..1372c65 100644
--- a/docs/2_OSLab/g2/context.md
+++ b/docs/2_OSLab/g2/context.md
@@ -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` 结构。
diff --git a/docs/2_OSLab/g2/drivers.md b/docs/2_OSLab/g2/drivers.md
index 200f199..eac26b4 100644
--- a/docs/2_OSLab/g2/drivers.md
+++ b/docs/2_OSLab/g2/drivers.md
@@ -81,11 +81,11 @@ pub fn into_input(self) -> Gpio {
引脚的上拉/下拉状态有 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 Timer:BCM2837 ARM Peripherals 第 12 章,IO 基地址为 `0x3F003000`,最常用的时钟,但是在 QEMU 中不可用;
-* ARM Timer:BCM2837 ARM Peripherals 第 14 章,IO 基地址为 `0x3F00B400`,在 QEMU 中也不可用,RustOS 并未实现;
+* System Timer:BCM2837 ARM Peripherals 第 12 章,IO 基地址为 `0x3F003000`,最常用的时钟,但是在 QEMU 中不可用。
+* ARM Timer:BCM2837 ARM Peripherals 第 14 章,IO 基地址为 `0x3F00B400`,在 QEMU 中也不可用,RustOS 并未实现。
* Generic Timer:ARMv8 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`:具体的控制台结构体,通过传入的字体泛型 `` 构造,包含当前光标位置 `(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)
diff --git a/docs/2_OSLab/g2/img/fantastic-text.png b/docs/2_OSLab/g2/img/fantastic-text.png
new file mode 100644
index 0000000..15c3f19
Binary files /dev/null and b/docs/2_OSLab/g2/img/fantastic-text.png differ
diff --git a/docs/2_OSLab/g2/interrupt.md b/docs/2_OSLab/g2/interrupt.md
index 62eef02..9903130 100644
--- a/docs/2_OSLab/g2/interrupt.md
+++ b/docs/2_OSLab/g2/interrupt.md
@@ -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()` 进行具体的系统调用。
diff --git a/docs/2_OSLab/g2/memory.md b/docs/2_OSLab/g2/memory.md
index 678d852..e3f76c8 100644
--- a/docs/2_OSLab/g2/memory.md
+++ b/docs/2_OSLab/g2/memory.md
@@ -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. Device:Device-nGnRE 类型的内存,不可缓存,外部共享;
+ 1. Normal:普通可缓存内存,cache 属性为 Write-Back Non-transient Read-Allocate Write-Allocate,内部共享。
+ 2. Device:Device-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, frame: PhysFrame, flags: PageTableFlags, attr: PageTableAttribute, allocator: FrameAllocator)`:将虚拟地址表示的**页** `page`,映射为物理地址表示的**帧** `frame`,并设置标志位 `flags` 和属性 `attr`,如果需要新分配页表就用 `allocator` 分配。页与帧的大小都是 4KB;
+* `new(table: PageTable)`:将虚拟地址表示的 `table` 作为 4 级页表,新建 `RecursivePageTable` 对象。
+* `map_to(self, page: Page, frame: PhysFrame, flags: PageTableFlags, attr: PageTableAttribute, allocator: FrameAllocator)`:将虚拟地址表示的**页** `page`,映射为物理地址表示的**帧** `frame`,并设置标志位 `flags` 和属性 `attr`,如果需要新分配页表就用 `allocator` 分配。页与帧的大小都是 4KB。
* `unmap(self, page: Page)`:取消虚拟地址表示的页 `page` 的映射。
### 实现内存管理
diff --git a/docs/2_OSLab/g2/overview.md b/docs/2_OSLab/g2/overview.md
index 37f21ca..8409300 100644
--- a/docs/2_OSLab/g2/overview.md
+++ b/docs/2_OSLab/g2/overview.md
@@ -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` 中。
diff --git a/kernel/src/arch/aarch64/driver/console/fonts/mod.rs b/kernel/src/arch/aarch64/driver/console/fonts/mod.rs
index fde1f9a..5a5bd9c 100644
--- a/kernel/src/arch/aarch64/driver/console/fonts/mod.rs
+++ b/kernel/src/arch/aarch64/driver/console/fonts/mod.rs
@@ -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)`.