RT-Thread smart 混内核操作系统启动流程分析一
下面以 qemu-Vexpress-a9 该虚拟平台为例,该工程,启动引导文件为
rt-smart\kernel\libcpu\arm\cortex-a\start_gcc.S
芯片跑起来的,第一步,是初始化mmu,对应代码如下
ldr r5, =PV_OFFSET
mov r7, #0x100000
sub r7, #1
mvn r8, r7
ldr r9, =KERNEL_VADDR_START
ldr r6, =__bss_end
add r6, r7
and r6, r8 //r6 end vaddr align up to 1M
sub r6, r9 //r6 is size
ldr sp, =stack_top
add sp, r5 //use paddr
ldr r0, =init_mtbl
add r0, r5
mov r1, r6
mov r2, r5
bl init_mm_setup
ldr lr, =after_enable_mmu
ldr r0, =init_mtbl
add r0, r5
b enable_mmu
在这个初始化mmu的过程中做了什么呢,下面咱们一步步分析
ldr r5, =PV_OFFSET //将PV_OFFSET这个立即数放入r5, PV_OFFSET 由宏定义得知其值为 0xa000 0000
mov r7, #0x100000 //将0x10 0000 立即数放入r7,
sub r7, #1 //0x10 0000 减1 为0xF FFFF
mvn r8, r7 //将r7的值赋给r8,此时 r8的值为 0xF FFFF
ldr r9, =KERNEL_VADDR_START //将KERNEL_VADDR_START 这个立即数放入r9, KERNEL_VADDR_START由宏定义得知其为 0xC000 0000
ldr r6, =__bss_end //将代码区 bss 区的结束的地址值放入 r6
add r6, r7 // r6+r7 放入r6 此时r6的值为 __bss_end + 0xF FFFF
and r6, r8 //r6 end vaddr align up to 1M r6 | r8 放入 r6 将r6的值1 M对其
sub r6, r9 //r6 is size r6 - r9 为 整个内核镜像大小 + 1MB + 64KB 为何为这个值呢,首先看镜像的链接脚本,该文件在 rt-smart\kernel\bsp\qemu-vexpress-a9\link.lds,在该链接脚本的开头,定义了镜像的代码段开始位置为 . = 0xc0010000
经过上述步骤,我们计算出了整个内核镜像大小,同时多加出了一部分区域
OK,接着看第二段
ldr sp, =stack_top //将栈顶地址赋值给sp,这个是为后续执行C代码做准备,
add sp, r5 //use paddr // sp的值为 stack_top + PV_OFFSET stack_top的地址为 0xC001 0000 + 实际链接的偏移,这个可以在.map文件中找到,例如我的为 0xC015 5804 ,那个这个值 + PV_OFFSET是多少呢?通过计算,因为这个平台为32位平台,则最终值是 0x6015 5804
那么 0x6015 5804 这个值有什么意义呢?看下面这个平台的地址map图
可以看到物理RAM对应的地址有两个一个由 0x6000 0000 ~ 0x8000 0000 第二个域为 0x8400 0000 ~ 0xA000 0000
因为此时mmu还未使能,则此时,CPU访问地址均为实际物理地址,所以,在此,SP + PV_OFFSET是将SP加载的地址转换为实际的物理内存的地址
好了,至此,系统已可以调用C语言的接口,下面咱们接着分析
ldr r0, =init_mtbl //将init_mtbl数据的地址赋值给r0
add r0, r5 //将 r0 + r5, 联系上文,r5的值为PV_OFFSET, 则在此处,则计算出了init_mtbl在物理内存中地址
mov r1, r6 //将r6 赋值给 r1, r6的值是什么呢,r6中当前存储的值 完整的内核镜像大小+ 1MB + 64KB
mov r2, r5 //将 PV_OFFSET 赋值给r2
bl init_mm_setup //在此处调用C函数 init_mm_setup 上述的对r0 r1 r2的赋值相当与对C函数传参
下面是init_mm_setup函数的代码
void init_mm_setup(unsigned int *mtbl, unsigned int size, unsigned int pv_off) {
unsigned int va;
for (va = 0; va < 0x1000; va++) { //vaddr range 10 0000 ~ 1 0000 0000
unsigned int vaddr = (va << 20);
if (vaddr >= KERNEL_VADDR_START && vaddr - KERNEL_VADDR_START < size) { /*将内核地址开始之上的地址,映射到 0x6000 0000 之后*/
mtbl[va] = ((va << 20) + pv_off) | NORMAL_MEM;
} else if (vaddr >= (KERNEL_VADDR_START + pv_off) && vaddr - (KERNEL_VADDR_START + pv_off) < size) { /*虚拟地址与物理内存重合的地映射为和物理地址一样*/
mtbl[va] = (va << 20) | NORMAL_MEM;
} else {
mtbl[va] = 0; /*赋值为0,未映射*/
}
}
}
继续分析init_mm_setup 函数的代码
va 取的值 是0x00 ~0x1000 这个范围是 0~4096
vaddr 的取值范围是 0x00 ~ (0x1000 << 20) 这个范围是 0~4G的空间,数值全部以 1MB 对齐,为何在此取1MB呢,下节继续