这个part是想详细走读一下用qemu运行kernel的最初始代码,也就是使用qemu运行kernel代码的详细逻辑,从qemu加载根目录下vmlinux镜像的逻辑,也就是运行arch/arm/kernel/head.S的整个过程,直到跳转到start_kernel,使用的kernel版本还是3.18。
初始运行状态
沿用上回《基于QEMU的vexpress-a9开发调试环境搭建》的最后一部分调试kernel汇编这一个part,我们需要将vmlinux的链接地址设置到0x60008000,设置方法是修改arch/arm/Kconfig中VMSPLIT_2G对应的值到0x60000000,并重新编译kernel,接着就可以直接b *0x60008000,并且执行到arch/arm/kernel/head.S,这样,我们就可以直接通过命令c运行到arch/arm/kernel/head.S的第一句代码(bl __hyp_stub_install),这其中跳过了compressed/vmlinux的解压内核等逻辑,后续章节再补充。
在此,我们打印一下运行到此处时的寄存器
// arch/arm/kernel/head.S主线代码
#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install // 1:如有必要,执行hyp模式的初始化配置
#endif
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9 // 2: 进入SVC模式,屏蔽中断
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid // 3.根据CPU id查找cpu执行函数
movs r10, r5 @ invalid processor (r5=0)?
1. __hyp_stub_install的执行
如果定义了CONFIG_ARM_VIRT_EXT宏,则进入arch/arm/kernel/head.S的第一行代码就是bl __hyp_stub_install,__hyp_stub_install首先执行store_primary_cpu_mode,这个方法是用来记录主cpu运行模式的, 只有主CPU在初始化时候才会调用这个方法,这个方法其实是将主cpu当前cpsr寄存器中存的cpu模式(bit0-bit4,当前值为0x13,代表SVC_MODE),存入到__boot_cpu_mode的位置。后面__syp_stub_install_secondary其实用来给其他CPU,检查当前cpu模式,如果和主CPU模式一致,并且也是HYP_MODE,则初始化hyp-stub(这个部分我们不展开讨论了,因为不会走到),由于我们主CPU是SVC_MODE,所以不满足第二个条件,所以在执行到114行代码时,就会retne lr,返回head.S的主代码逻辑。
2. safe_svcmode_maskall的执行
这部分做的是强制让cpu进入SVC模式,并屏蔽中断。
/*
* Helper macro to enter SVC mode cleanly and mask interrupts. reg is
* a scratch register for the macro to overwrite.
*
* This macro is intended for forcing the CPU into SVC mode at boot time.
* you cannot return to the original mode.
*/
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
mrs \reg , cpsr //读取cpsr到reg
eor \reg, \reg, #HYP_MODE //reg和HYP_MODE(0x14)进行异或,结果存到reg
tst \reg, #MODE_MASK // 测试是否与0x1F相同,由于cpsr中是SVC,与HYP异或后上一步结果非0,所以这里cpsr中Z位置0
bic \reg , \reg , #MODE_MASK //清除reg中模式位
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE //reg中屏蔽中断和快速中断,并设置SVC模式
THUMB( orr \reg , \reg , #PSR_T_BIT )
bne 1f //由于cpsr中Z为0,跳转到1:的位置。
orr \reg, \reg, #PSR_A_BIT //将r0,第8位,exception位清零
adr lr, BSYM(2f) //badr是设置lr到2:这个位置,也就是跳出这个函数了。
msr spsr_cxsf, \reg //将hyp模式的spsr,设置成新的svc的cpsr
__MSR_ELR_HYP(14) //这两句是thumb语句,ignore
__ERET
1: msr cpsr_c, \reg //将reg中的结果,设置到cpsr_c,也就是cpsr的低8位里
2:
......
通过上面代码分析可知,如果当前cpu模式为HYP模式,需要额外将新的cpsr设置到HYP模式的spsr中,否则,就只是屏蔽中断/快中断,并让cpu进入svc模式。下面图可以看出HYP模式有自己的spsr寄存器。
3 __lookup_processor_type的执行
这部分是在.proc.info.init数据中,查找对应cpu的私有执行方法。具体步骤为:
- 调用CP15寄存器方法(mrc p15, 0, r9, c0, c0),获得cpu id。
- 通过cpu id,到.proc.init段中查找对应的cpu的私有执行方法。
- 找到后存入r5寄存器返回
/*
* Read processor ID register (CP#15, CR0), and look up in the linker-built
* supported processor list. Note that we can't use the absolute addresses
* for the __proc_info lists since we aren't running with the MMU on
* (and therefore, we are not in the correct address space). We have to
* calculate the offset.
*
* r9 = cpuid
* Returns:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__lookup_processor_type:
adr r3, __lookup_processor_type_data // r3存放__lookup_processor_type_data的物理地址
ldmia r3, {
r4 - r6} //r4存放__lookup_processor_type_data的链接地址,r5存放__proc_info_start链接地址,r6存放__proc_info_end链接地址
sub r3, r3, r4 @ get offset between virt&phys //用r3-r4,__lookup_processor_type_data的物理地址减去链接地址,r3存放物理地址与链接地址的差值
add r5, r5, r3 @ convert virt addresses to//r5存放__proc_info_start物理地址
add r6, r6, r3 @ physical address space //r6存放__proc_info_end物理地址
1: ldmia r5, {
r3, r4} @ value, mask // r3存放proc_info的cpu id,r4存掩码
and