前面一篇的early_init执行完成后,CPU启动早期的基本初始化工作算是做完了,这时内核会开始重定向并复制运行,代码如下:
bl reloc_offset
mr r26,r3
addis r4,r3,KERNELBASE@h /* current address of _start */
lis r5,PHYSICAL_START@h
cmplw 0,r4,r5 /* already running at PHYSICAL_START? */
bne relocate_kernel /*Juan内核重定向,经典启动必备*/
这里的第一句mr是将当前偏移量保存在r26中,后面relocate_kernel会使用。之后内核会判断是否需要重定向,KERNELBASE为内核的虚拟起始地址,PHYSICAL_START为内核的实际起始地址,而内核则必须要从物理地址运行start函数。下面是relocate_kernel的详细代码:
relocate_kernel:
addis r9,r26,klimit@ha /* fetch klimit */
lwz r25,klimit@l(r9) /*r25 = kilmit + offset*/
addis r25,r25,-KERNELBASE@h /*最后得到的r25为内核大小*/
lis r3,PHYSICAL_START@h /* 拷贝目标基地址 */
li r6,0 /* 实际地址,不偏移 */
li r5,0x4000 /* 先拷贝 16K字节*/
bl copy_and_flush
addi r0,r3,4f@l /* 跳到4f */
mtctr r0 /* in copy and do the rest. */
bctr /* jump to the copy */
4: mr r5,r25
bl copy_and_flush /* copy the rest */
b turn_on_mmu /*打开MMU*/
机制很简单,就是获取内核大小后,先拷16K,再把剩下的拷过去,然后打开MMU,打开MMU的代码和关闭的类似,这里就不再列举了,看一下拷贝函数copy_and_flush,实现的是拷贝内核到内存物理起始处,并关闭cache。代码如下:
_ENTRY(copy_and_flush)
addi r5,r5,-4
addi r6,r6,-4
4: li r0,L1_CACHE_BYTES/4 /*L1_CACHE_BYTES:0b10000=16*/
mtctr r0
3: addi r6,r6,4 /* copy a cache line */
lwzx r0,r6,r4 /*读单字(4Byte),通过Cache*/
stwx r0,r6,r3 /*写单字,从r4加载,存在r3*/
bdnz 3b /*递减计数器,循环每次拷4个字*/
dcbst r6,r3 /*Data Cache Block Store,再将r3的值写到内存*/
sync
icbi r6,r3 /*Instruction Cache Block Invalidate,强制清空指令Cache */
cmplw 0,r6,r5
blt 4b /*循环写内存,直到写完(r6>=r5)*/
sync /* additional sync needed on g4 */
isync
addi r5,r5,4
addi r6,r6,4
blr
这里的r4是在上面调用relocate_kernel的时候赋的值,为虚拟起始地址-偏移量(偏移量是负的,remember?),即拷贝的源地址。执行完拷贝后,内核会跳转到trun_on_mmu中,该函数在SRR0中写入了start_here的地址,执行完使能MMU后,中断返回指令自动将SRR1更新为MSR,并在新的MSR控制下将SRR0更新为PC指针,实现绝对跳转,处理器即正式跳到start_here中。在此之后,就不再有前面说的链接地址与实际运行地址不同的事情了,即访问变量时也不用加上reloc