ChinOS
我们在GN-base上添加一个chinos分支来开始我们的微内核操作系统之旅
https://github.com/jingjin666/GN-base/tree/chinos
QEMU-virt启动
linux-kernel boot
qemu-virt支持Linux kernel启动协议,DTB信息通过R2/X0寄存器携带传给客户机
qemu-system-aarch64 -M virt -cpu cortex-a53 -m 1024 -nographic -kernel zImage -dtb xx.dtb
bare-metal boot
qemu-virt下的裸机(bare-metal)启动支持binary和elf两种镜像加载
- binary文件启动,起始代码(__start)地址必须放在为.boot节(最开始位置)否则程序无法正常启动,ELF启动则可以不用,因为内部有程序会解析ELF中的ENTRY符号(__start),并进行重定向启动
- elf文件启动,需要重定位的支持
binary-load
binary load,默认直接把binary镜像加载到RAM(0x4008_0000)地址处开始运行,0x4000_0000处存放DTB信息
qemu-system-aarch64 -M virt -cpu cortex-a53 -m 1024 -nographic -kernel ./out/image/os.bin
输出qemu默认dtb信息
qemu-system-aarch64 -cpu cortex-a53 -m 512 -smp 4 -M virt,gic-version=3,dumpdtb=dump.dtb
dtc -o dump.dts -O dts -I dtb dump.dtb
elf-load
elf load,需要在链接脚本(linker.lds)中指定AT属性,不然qemu无法从ELF中把段LOAD到内存中去
qemu-system-aarch64 -M virt -cpu cortex-a53 -m 1024 -nographic -kernel ./out/image/os.elf
ENTRY(_start);
SECTIONS
{
. = 0x40000000;
.boot : AT(ADDR(.boot))
{
*(.boot)
}
.text :
{
*(.text*)
}
.rodata :
{
*(.rodata*)
}
.data :
{
*(.data*)
}
.bss :
{
*(.bss*)
}
. = ALIGN(4096);
. += 4096;
boot_stack = .;
/DISCARD/ :
{
*(.note.gnu.build-id)
*(.comment)
}
}
链接脚本中,通过AT我们告诉qemu把ELF加载到RAM(0x4000_0000)处,当然这个内存地址存放的是DTB信息,对于裸机系统我们不需要DTB信息,可以直接从这个地址开始运行。如果你确实需要用到DTB信息那么从RAM(0x4008_0000)开始AT即可
ARMv8启动
ARMv8异常等级
ARMv8异常等级分为EL3,EL2,EL1,EL0
EL0:用户态应用程序
EL1:操作系统
EL2:Hypervisor虚拟机
EL3:Secure Monitor
一般来说,一个软件,如应用程序、操作系统的内核或管理程序,只占用一个异常级别。这一规则的一个例外是内核内的管理程序,如KVM,它在EL2和EL1之间运行。
ARMv8-A提供两种安全状态:安全和非安全。非安全状态也被称为 “正常世界”。这使得操作系统(OS)可以在同一硬件上与受信任的操作系统并行运行,并提供对某些软件攻击和硬件攻击的保护。
ARM TrustZone技术使系统可以在正常世界和安全世界之间进行划分。与ARMv7-A架构一样,安全监视器作为网关在正常世界和安全世界之间移动。
ARMv8异常等级切换
异常级别之间的奇幻遵循以下规则:
- 移动到更高的异常级别,例如从EL0到EL1,表示软件执行权限的增加。
- 一个异常不能被带到较低的异常级别。
- 在EL0层没有异常处理,异常必须在更高的异常层处理。
- 一个异常会导致程序流程的改变。异常处理程序的执行在高于EL0的异常级别开始,从一个定义的向量开始,与所发生的异常有关。异常包括:中断,如IRQ和FIQ。内存系统中止。未定义的指令。系统调用。这些允许无特权的软件对操作系统进行系统调用。 操作系统的系统调用。
- 安全监控器或管理程序陷阱。
- 结束异常处理并返回到上一个异常级别是通过执行ERET指令进行的。
- 从异常中返回可以停留在同一个异常级别或进入一个较低的异常级别。它不能移动到更高的异常级别。
- 安全状态会随着异常级别的改变而改变,除了从EL3返回到非安全状态时。
获取异常等级并切换到EL1
从uboot或ATF启动后,AArch64需要切换到EL1(内核态)
_start:
// 当前异常等级
mrs tmp, CurrentEL
and tmp, tmp, #0b1100
cmp tmp, #(1 << 2)
// 跳转到EL1处理
beq el1_entry
cmp tmp, #(2 << 2)
// 跳转到EL2处理
beq switch_to_el1_from_el2
cmp tmp, #(3 << 2)
// 跳转到EL3处理
beq switch_to_el2_from_el3
switch_to_el2_from_el3:
// 关闭EL3的指令和数据缓存和MMU
mrs tmp, sctlr_el3
bic tmp, tmp, #SCTLR_ELx_I
bic tmp, tmp, #SCTLR_ELx_C
bic tmp, tmp, #SCTLR_ELx_M
msr sctlr_el3, tmp
// 从EL3切换到EL2
/* Return to the EL2_SP2 mode from EL3 */
ldr tmp, =(SPSR_EL_DEBUG_MASK | SPSR_EL_SERR_MASK |\
SPSR_EL_IRQ_MASK | SPSR_EL_FIQ_MASK |\
SPSR_EL_M_AARCH64 | SPSR_EL_M_EL2H)
msr spsr_el3, tmp
adr tmp, switch_to_el1_from_el2
msr elr_el3, tmp
eret
switch_to_el1_from_el2:
// 关闭EL2的指令和数据缓存和MMU
mrs tmp, sctlr_el2
bic tmp, tmp, #SCTLR_ELx_I
bic tmp, tmp, #SCTLR_ELx_C
bic tmp, tmp, #SCTLR_ELx_M
msr sctlr_el2, tmp
// 从EL2切换到EL1
/* Initialize HCR_EL2 */
ldr tmp, =(HCR_EL2_RW_AARCH64 | HCR_EL2_HCD_DIS)
msr hcr_el2, tmp
/* Return to the EL1_SP1 mode from EL2 */
ldr tmp, =(SPSR_EL_DEBUG_MASK | SPSR_EL_SERR_MASK |\
SPSR_EL_IRQ_MASK | SPSR_EL_FIQ_MASK |\
SPSR_EL_M_AARCH64 | SPSR_EL_M_EL1H)
msr spsr_el2, tmp
adr tmp, el1_entry
msr elr_el2, tmp
eret
el1_entry:
// 关闭EL1的指令和数据缓存和MMU
mrs tmp, sctlr_el1
bic tmp, tmp, #SCTLR_ELx_I
bic tmp, tmp, #SCTLR_ELx_C
bic tmp, tmp, #SCTLR_ELx_M
msr sctlr_el1, tmp
cache_disable_done:
dsb sy
isb
EL1等级的切换需要逐级降级EL3->EL2->EL1,QEMU-virt默认启动后异常等级为EL1,无需进行异常等级切换
关闭MMU和缓存
在系统启动的初始阶段,我们一般都会关闭CACHE包括指令缓存和数据缓存,同时关闭MMU,此时系统工作在实地址(物理地址)模式
进入C语言
设置好堆栈就可以正常进入C语言的世界了
adr x1, boot_stack
mov sp, x1
boot_stack定义在linker.lds中,注意ARM堆栈的增长方向是从高地址到低地址,所以栈顶指针(SP)的地址为 += STACK_SIZE,在栈的初始阶段,栈顶和栈底相同,入栈,栈顶指针向低地址移动,出栈,栈顶指针向高地址移动
.bss :
{
*(.bss*)
}
. = ALIGN(4096);
. += 4096;
boot_stack = .;
/DISCARD/ :
{
*(.note.gnu.build-id)
*(.comment)
}
相对跳转还是绝对跳转
bl boot_setup_mmu
直到此刻,系统一直处于地址无关状态,也就是代码和数据的访问都是通过相对位置来计算,和链接脚本中的链接地址没有关系,但是寻址范围是受限的。此刻我们只能使用相对跳转b/bl
无条件跳转(立即数)
立即数跳转,支持±128MiB的立即偏移范围
- B lable 跳转:无条件跳转到PC-relative lable
- BL lable 跳转:无条件跳转到PC-relative lable,并将下一条指令的地址写入LR(X30)寄存器
无条件跳转(寄存器)
- BR Xm:无条件跳转到Xm中的地址
- BLR Xm:无条件跳转到Xm中的地址,并将下一条指令的地址写入LR(X30)寄存器
- RET {Xm}:跳转到寄存器Xm,提示CPU这是一个子程序返回
详细armv8汇编,请参考https://blog.csdn.net/weixin_42135087/article/details/123664540