TinyEMU源码分析之启动流程
- 1 始于0x1000
- 2 确定BBL入口点
- 3 mentry.S执行过程
- 4 启动流程小结
本文属于《 TinyEMU模拟器基础系列教程》之一,欢迎查看其它文章。
本文中使用的代码,均为伪代码,删除了部分源码。
1 始于0x1000
我们沿着TinyEMU的启动流程,依次讲解,以便理解其启动原理。
整个虚拟机,到底从哪里,开始执行第一条指令呢?
我们可以在glue函数的,s->pc = GET_PC()
位置处,打上断点,查看第一条指令的PC。
static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s, int n_cycles1)
{
for(;;) {
// 获取PC
s->pc = GET_PC();
addr = s->pc;
ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend +
(uintptr_t)addr);
code_ptr = ptr;
//根据PC获取一条指令机器码
insn = get_insn32(code_ptr);
}
}
可以发现,第一条指令的PC为0x1000。
我们回顾一下,《TinyEMU源码分析之虚拟机初始化》中介绍的5条指令。
auipc t0, jump_addr // t0 = 0x80000000
auipc a1, dtb // a1 = PC
addi a1, a1, dtb // a1 = a1 + 0x3c
csrr a0, mhartid // a0 = mhartid
jalr zero, t0, jump_addr // PC = t0
这5条指令,就是从0x1000内存地址,开始存放的。
因此,虚拟机可以确定:
- 第1条指令,从
auipc t0, jump_addr
开始执行; - 第5条指令
jalr zero, t0, jump_addr
,将PC设置为0x80000000; - 因此,第6条指令,必然跳转到0x80000000处执行。
而0x80000000处,我们存放的是BIOS/Bootloader(这里是bbl64.bin);因此0x80000000处为bbl64.bin的第一条指令(入口点),我们需要分析bbl64.bin的源码,找到其入口点。
2 确定BBL入口点
bbl64.bin的源码,包括2部分,汇编文件与C文件。
汇编文件为riscv-pk\machine\mentry.S,一般叫启动文件。
Bootloader的.s启动文件,是汇编语言源代码文件,通常用于嵌入式系统的启动过程。
这个文件包含了系统启动时的底层硬件初始化代码,是用汇编语言编写的,以便直接操作硬件寄存器,完成必要的初始化工作。
在链接器的链接脚本.lds文件内,会定义程序的入口点,即程序的第一条可执行指令。
关于链接器脚本,可参考《【裸机开发】链接脚本(.lds文件)的基本语法》。
因此,编译器对.s和.c文件,执行编译与链接后,程序就可以从该入口点开始执行。
通常入口点,就在汇编文件中,这里就是mentry.S。
我们看,链接脚本riscv-pk\bbl\bbl.lds,如下:
ENTRY(symbol),这里的symbol指的是符号表中的符号。汇编阶段会生成符号表,符号表中的符号,包括静态变量、全局变量、函数名等。这是将某一个符号symbol的值,设为入口地址。
我们再看,riscv-pk\machine\mentry.S,如下:
因此bbl.lds指定的入口点,就是mentry.S中的reset_vector处。
即0x80000000处,指令为j do_reset
。
3 mentry.S执行过程
执行指令j do_reset
,跳转到do_reset
地址后,后续执行过程,如下所示:
汇编代码添加了注释,比较简单,主要有以下操作:
- 主要是初始化x系列寄存器,以及根据misa寄存器判断;
- 此外,前面讲过的a0与a1寄存器值,作为init_first_hart函数的入参;
- 保存异常向量表基址,到mtvec寄存器;
- 准备栈指针sp;
- 最后,跳转到riscv-pk\machine\minit.c中的init_first_hart函数,进入到C代码阶段。
4 启动流程小结
在bbl64.bin执行完毕后,进入运行OS(kernel-riscv64.bin)阶段。
上述启动流程,可用下图,来简要描述: