简介
文档链接:Lab1: 机器启动 - IPADS OS Course Lab Manual
本实验作为 ChCore 操作系统课程实验的第一个实验,分为三个部分。
- RTFSC: 代码导读,由于是Lab1,我们主要注重于Chcore的构建系统,这部分没有习题。
- 机器启动:介绍aarch64结构启动时的关键寄存器以及关键的启动函数。
- 页表配置:介绍aarch64页表结构,以及针对树莓派3平台的内存布局编写页表配置。
调试指北
在开始实验之前,请务必读完调试指北,以帮助你快速上手调试。
思考题 1
阅读 _start
函数的开头,尝试说明 ChCore 是如何让其中一个核首先进入初始化流程,并让其他核暂停执行的。
|
|
从中我们可以知道,在函数的开头会从
mpidr_el1
寄存器中读取cpu的编号,当cpu的编号为0时(即当前核心是主核心时)就会跳转到primary
进行初始化
练习2
在 arm64_elX_to_el1
函数的 LAB 1 TODO 1
处填写一行汇编代码,获取 CPU 当前异常级别。
arm64_elX_to_el1
,顾名思义,从特权级elX
降级到el1
,其中X
代表任意特权级
根据ChCore文档中的提示,我们可以从CurrentEL
寄存器中获取当前的特权级,又根据Arm文档中的介绍:
我们可以很简单的知道,应该把CurrentEL
中的值获取到x9
寄存器里,即
|
|
练习题 3
eret
指令可用于从高异常级别跳到更低的异常级别,在执行它之前我们需要设置 设置 elr_elx
(异常链接寄存器)和 spsr_elx
(保存的程序状态寄存器),分别控制eret
执行后的指令地址(PC)和程序状态(包括异常返回后的异常级别)。
在 arm64_elX_to_el1
函数的 LAB 1 TODO 2
处填写大约 4 行汇编代码,设置从 EL3 跳转到 EL1 所需的 elr_el3
和 spsr_el3
寄存器值。
elr_el3
的正确设置应使得控制流在eret
后从arm64_elX_to_el1
返回到_start
继续执行初始化。spsr_el3
的正确设置应正确屏蔽 DAIF 四类中断,并且将 SP 正确设置为EL1h
. 在设置好这两个系统寄存器后,不需要立即eret
.
-
elr_el3
,参考el1时的操作,从el3返回el1时同样应该回到.Ltarget
处来返回可以通过指令
adr
,获取标签的地址并存入寄存器中
-
spsr_el3
,根据下图并且结合arm文档中的解释:Inject Undefined Instruction exception. Set to 0 on taking an exception to EL3, and copied to PSTATE.UINJ on executing an exception return operation in EL3.(注入未定义的指令异常。将异常带入 EL3 时设置为 0,在 EL3 中执行异常返回操作时复制到 PSTATE.UINJ。)
DAIF四类中断的位数是 [9:6],而M[3:0]指定返回特权级的sp指针
由于lab1贴心的定义好了相应的变量,我们直接使用变量就好了
|
|
思考题 4
说明为什么要在进入 C 函数之前设置启动栈。如果不设置,会发生什么?
因为调用函数需要使用到栈来保存状态,如果不启用栈,会导致函数调用失败
思考题 5
在实验 1 中,其实不调用 clear_bss
也不影响内核的执行,请思考不清理 .bss
段在之后的何种情况下会导致内核无法工作
当内核中的某个函数依赖全局变量的初始值为0时,不清理
.bss
可能会导致出现无法预料的异常
练习题6
在 kernel/arch/aarch64/boot/raspi3/peripherals/uart.c
中 LAB 1 TODO 3
处实现通过 UART 输出字符串的逻辑。
|
|
练习题7
在 kernel/arch/aarch64/boot/raspi3/init/tools.S
中 LAB 1 TODO 4
处填写一行汇编代码,以启用 MMU。
我们可以在kernel/include/arch/aarch64/arch/machine/registers.h
中找到相应的变量定义
很容易知道应该答案:
|
|
这里关闭了对齐检查,启用了指令和数据缓存
变量SCTLR_EL1_M表示启动MMU
思考题 8
请思考多级页表相比单级页表带来的优势和劣势(如果有的话),并计算在 AArch64 页表中分别以 4KB 粒度和 2MB 粒度映射 0~4GB 地址范围所需的物理内存大小(或页表页数量)。
多级页表优势是不需要一次性地将所有空间都映射,尽管在映射大空间时占用内存多,但一般场景下需要映射地空间都很小,减少了内存的浪费。劣势是翻译需要多次访问内存,性能较差。
4KB粒度需要1024*1024个条目,则需要2048个3级页表,其中这2048个3级页表又需要4个2级页表项,总共需要2048+3个页表(考虑L0~L2)。
2MB粒度下需要2048个条目,需要4个2级页表,总共需要4+2个页表(考虑L0~L1)。
init_kernel_pt
为内核配置从0x00000000
到0x80000000
(0x40000000
后的 1G,ChCore 只需使用这部分地址中的本地外设)的映射,其中0x00000000
到0x3f000000
映射为 normal memory,0x3f000000
到0x80000000
映射为 device memory,其中0x00000000
到0x40000000
以 2MB 块粒度映射,0x40000000
到0x80000000
以 1GB 块粒度映射。
思考题 9
请结合上述地址翻译规则,计算在练习题 10 中,你需要映射几个 L2 页表条目,几个 L1 页表条目,几个 L0 页表条目。页表页需要占用多少物理内存?
0x0000_0000到0x4000_0000总共1024G,以2MB为粒度,需要524288个L2条目,1024个L1条目,2个L0条目
0x4000_0000到0x8000_0000总共1024G,以1G为粒度,需要1024个L1条目,需要2个L0条目
总共1028*4KB内存(不考虑L0)
练习题 10
在 init_kernel_pt
函数的 LAB 1 TODO 5
处配置内核高地址页表(boot_ttbr1_l0
、boot_ttbr1_l1
和 boot_ttbr1_l2
),以 2MB 粒度映射。
这里低位和高位的映射都是一样的,我们可以直接copy低位的配置来直接修改
|
|
- 将
boot_ttbr0_lx
改成boot_ttbr1_lx
vaddr = KERNEL_VADDR + 物理地址
在进行映射时记得把vaddr - KERNEL_VADDR
- 高地址只有内核执行,不需要通过ASID来区分,所以把所有的
NG
删掉
思考题11
请思考在 init_kernel_pt
函数中为什么还要为低地址配置页表,并尝试验证自己的解释。
由于启动mmu之后的指令仍然在低地址中执行,如果不为低地址配置页表的话,就会翻译出错而trap在小于0x8000的地址段。直到运行到
start_kernel
函数时才会进入高地址运行
思考题12
在一开始我们暂停了三个其他核心的执行,根据现有代码简要说明它们什么时候会恢复执行。思考为什么一开始只让 0 号核心执行初始化流程?
|
|
根据
init_c
函数我们知道在初始化完串口之后,其它的核心就会被唤醒。使用一个核心有利于简化初始化过程,在串行的初始化中我们也容易知道出错的位置,从而更好地找到问题所在。并且在启动过程中,一些操作通常需要按特定顺序执行,包括时钟、调度器、锁等,使用单个核心可以更容易地控制这一过程。