《从0开始写一个微内核操作系统》2-系统启动

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,在栈的初始阶段,栈顶和栈底相同,入栈,栈顶指针向低地址移动,出栈,栈顶指针向高地址移动

关于栈的描述参考https://www.cnblogs.com/dongry/p/10491031.html

    .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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值