前面一篇的early_init(見 http://www.linuxidc.com/Linux/2011-11/46581.htm )執行完成後,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_offset了。
辛辛苦苦跳了這麼久,終於到了執行內核代碼的時候了!!這個函數叫start_here,代碼比較長,分兩段來分析,先看第一段:
- start_here:
- /* ptr to current */
- lis r2,init_task@h
- ori r2,r2,init_task@l /*默認初始化的task_struct結構體*/
- /* Set up for using our exception vectors */
- tophys(r4,r2) /*獲取物理地址*/
- addi r4,r4,THREAD /* 初始化線程的CPU相關的狀態,THREAD為thread在task_struct中的偏移 */
- CLR_TOP32(r4) /*空的??*/
- mtspr SPRN_SPRG_THREAD,r4 /*將當前線程信息寫入SPRG3*/
- li r3,0
- mtspr SPRN_SPRG_RTAS,r3 /* 寫SPRG2為0,使其不在RTAS中 */
-
- /* 堆棧初始化 */
- lis r1,init_thread_union@ha
- addi r1,r1,init_thread_union@l
- li r0,0
- stwu r0,THREAD_SIZE-STACK_FRAME_OVERHEAD(r1)
- /* 平台相關的初始化操作和配置MMU */
- mr r3,r31
- mr r4,r30
- bl machine_init
- bl __save_cpu_setup
- bl MMU_init