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)`.