Linux2.6內核啟動流程

ARM 213瀏覽

Linux內核構成

1 arch/arm/boot/compressed/Makefile???arch/arm/boot/compressed/vmlinux.lds

2. arch/arm/kernel/vmlinux.lds

?

?

Linux內核啟動流程

arch/arm/boot/compressed/start.S

?

Start:

????????????????.type???start,#function

????????????????.rept???8

????????????????mov?????r0, r0

????????????????.endr

?

????????????????b???????1f

????????????????.word???[email protected] Magic numbers to help the loader

????????????????[email protected] absolute load/run zImage address

????????????????[email protected] zImage end address

1:??????????????mov?????r7, [email protected] save architecture ID

????????????????mov?????r8, [email protected] save atags pointer

這也標志著u-boot將系統完全的交給了OSbootloader生命終止。之后代碼在133行會讀取cpsr并判斷是否處理器處于supervisor模式——u-boot進入kernel,系統已經處于SVC32模式;而利用angel進入則處于user模式,還需要額外兩條指令。之后是再次確認中斷關閉,并完成cpsr寫入

?

????????????????mrs?????r2, [email protected] get current mode

????????????????tst?????r2, #[email protected] not user?

????????????????bne?????not_angel

????????????????mov?????r0, #[email protected] angel_SWIreason_EnterSVC

????????????????[email protected] angel_SWI_ARM

not_angel:

????????????????mrs?????r2, [email protected] turn off interrupts to

????????????????orr?????r2, r2, #[email protected] prevent angel from running

????????????????msr?????cpsr_c, r2

?然后在LC0地址處將分段信息導入r0-r6ipsp等寄存器,并檢查代碼是否運行在與鏈接時相同的目標地址,以決定是否進行處理。由于現在很少有人不使用loadertags,將zImage燒寫到rom直接從0x0位置執行,所以這個處理是必須的(但是zImage的頭現在也保留了不用loader也可啟動的能力)。arm架構下自解壓頭一般是鏈接在0x0地址而被加載到0x30008000運行,所以要修正這個變化。涉及到

?

r5寄存器存放的zImage基地址

r6r12(即ip寄存器)存放的gotglobal
offset table

r2r3存放的bss段起止地址

sp棧指針地址

很簡單,這些寄存器統統被加上一個你也能猜到的偏移地址?0x30008000。該地址是s3c2410相關的,其他的ARM處理器可以參考下表

?

PXA2xx0xa0008000

IXP2x00IXP4xx0x00008000

Freescale i.MX31/370x80008000

TI davinci DM64xx0x80008000

TI omap系列是0x80008000

AT91RM/SAM92xx系列是0x20008000

Cirrus EP93xx0x00008000

這些操作發生在代碼172行開始的地方,下面只粘貼一部分

?

????????????????add?????r5, r5, r0

????????????????add?????r6, r6, r0

????????????????add?????ip, ip, r0

后面在211行進行bss段的清零工作

?

not_relocated:?????mov?????r0, #0

1:????????????????str?????r0, [r2], #[email protected] clear bss

????????????????str?????r0, [r2], #4

????????????????str?????r0, [r2], #4

????????????????str?????r0, [r2], #4

????????????????cmp?????r2, r3

????????????????blo?????1b

?然后224行,打開cache,并為后面解壓縮設置64KB的臨時malloc空間

?

????????????????bl??????cache_on

?

????????????????mov?????r1, [email protected] malloc space above stack

????????????????add?????r2, sp, #[email protected] 64k max??接下來238行進行檢查,確定內核解壓縮后的Image目標地址是否會覆蓋到zImage頭,如果是則準備將zImage頭轉移到解壓出來的內核后面

?

????????????????cmp?????r4, r2

????????????????bhs?????wont_overwrite

????????????????sub?????r3, sp, [email protected] > compressed kernel size

????????????????add?????r0, r4, r3, lsl #[email protected] allow for 4x expansion

????????????????cmp?????r0, r5

????????????????bls?????wont_overwrite

?

????????????????mov?????r5, [email protected] decompress after malloc space

????????????????mov?????r0, r5

????????????????mov?????r3, r7

????????????????bl??????decompress_kernel

真實情況——在大多數的應用中,內核編譯都會把壓縮的zImage和非壓縮的Image鏈接到同樣的地址,s3c2410平臺下即是0x30008000。這樣做的好處是,人們不用關心內核是Image還是zImage,放到這個位置執行就OK,所以在解壓縮后zImage頭必須為真正的內核讓路。

?

250行解壓完畢,內核長度返回值存放在r0寄存器里。在內核末尾空出128字節的棧空間用,并且使其長度128字節對齊。

?

????????????????add?????r0, r0, #127 + [email protected] alignment + stack

????????????????bic?????r0, r0, #[email protected] align the kernel length

算出搬移代碼的參數:計算內核末尾地址并存放于r1寄存器,需要搬移代碼原來地址放在r2,需要搬移的長度放在r3。然后執行搬移,并設置好sp指針指向新的棧(原來的棧也會被內核覆蓋掉)

?

????????????????add?????r1, r5, [email protected] end of decompressed kernel

????????????????adr?????r2, reloc_start

????????????????ldr?????r3, LC1

????????????????add?????r3, r2, r3

1:??????????????ldmia???r2!, {r9 - r14}[email protected] copy relocation code

????????????????stmia???r1!, {r9 - r14}

????????????????ldmia???r2!, {r9 - r14}

????????????????stmia???r1!, {r9 - r14}

????????????????cmp?????r2, r3

????????????????blo?????1b

????????????????add?????sp, r1, #[email protected] relocate the stack

搬移完成后刷新cache,因為代碼地址變化了不能讓cache再命中被內核覆蓋的老地址。然后跳轉到新的地址繼續執行

?

????????????????bl??????cache_clean_flush

????????????????add?????pc, r5, [email protected] call relocation code

注意——zImage在解壓后的搬移和跳轉會給gdb調試內核帶來麻煩。因為用來調試的符號表是在編譯是生成的,并不知道以后會被搬移到何處去,只有在內核解壓縮完成之后,根據計算出來的參數告訴調試器這個變化。以撰寫本文時使用的zImage為例,內核自解壓頭重定向后,reloc_start地址由0x30008360變為0x30533e60。故我們要把vmlinux的符號表也相應的從0x30008000后移到0x30533b00開始,這樣gdb就可以正確的對應源代碼和機器指令。

?

隨著頭部代碼移動到新的位置,不會再和內核的目標地址沖突,可以開始內核自身的搬移了。此時r0寄存器存放的是內核長度(嚴格的說是長度外加128Byte的棧),r4存放的是內核的目的地址0x30008000r5是目前內核存放地址,r6CPU
ID
r7machine IDr8atags地址。代碼從501行開始

?

reloc_start:????add?????r9, r5, r0

????????????????sub?????r9, r9, #[email protected] do not copy the stack

????????????????debug_reloc_start

????????????????mov?????r1, r4

1:

????????????????.rept???4

????????????????ldmia???r5!, {r0, r2, r3, r10 - r14}[email protected] relocate kernel

????????????????stmia???r1!, {r0, r2, r3, r10 - r14}

????????????????.endr

?

????????????????cmp?????r5, r9

????????????????blo?????1b

????????????????add?????sp, r1, #[email protected] relocate the stack

接下來在516行清除并關閉cache,清零r0,將machine
ID
存入r1atags指針存入r2,再跳入0x30008000執行真正的內核Image

?

call_kernel:????bl??????cache_clean_flush

????????????????bl??????cache_off

????????????????mov?????r0, #[email protected] must be zero

????????????????mov?????r1, [email protected] restore architecture number

????????????????mov?????r2, [email protected] restore atags pointer

????????????????mov?????pc, [email protected] call kernel

?

?

?

?

?

內核代碼入口在arch/arm/kernel/head.S文件的83行。首先進入SVC32模式,并查詢CPU ID,檢查合法性

?

????????msr?????cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode

[email protected] and irqs disabled

????????mrc?????p15, 0, r9, c0, [email protected] get processor id

[email protected] r5=procinfo r9=cpuid

????????movs????r10, [email protected] invalid processor (r5=0)?

[email protected] yes, error 'p'

接著在87行進一步查詢machine ID并檢查合法性

?

[email protected] r5=machinfo

????????movs????r8, [email protected] invalid machine (r5=0)?

[email protected] yes, error 'a'

其中__lookup_processor_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的149行,該函數首將標號3的實際地址加載到r3,然后將編譯時生成的__proc_info_begin虛擬地址載入到r5,__proc_info_end虛擬地址載入到r6,標號3的虛擬地址載入到r7。由于adr偽指令和標號3的使用,以及__proc_info_begin等符號在linux-2.6.24-moko-linuxbj/arch/arm/kernel/vmlinux.lds而不是代碼中被定義,此處代碼不是非常直觀,想弄清楚代碼緣由的讀者請耐心閱讀這兩個文件和adr偽指令的說明。

?

r3和r7分別存儲的是同一位置標號3的物理地址(由于沒有啟用mmu,所以當前肯定是物理地址)和虛擬地址,所以兒者相減即得到虛擬地址和物理地址之間的offset。利用此offset,將r5和r6中保存的虛擬地址轉變為物理地址

?

__lookup_processor_type:

????adr????r3, 3f

????ldmda????r3, {r5 - r7}

????sub????r3, r3, [email protected] get offset between virt&phys

????add????r5, r5, [email protected] convert virt addresses to

????add????r6, r6, [email protected] physical address space

然后從proc_info中讀出內核編譯時寫入的processor ID和之前從cpsr中讀到的processor ID對比,查看代碼和CPU硬件是否匹配(想在arm920t上運行為cortex-a8編譯的內核?不讓!)。如果編譯了多種處理器支持,如versatile板,則會循環每種type依次檢驗,如果硬件讀出的ID在內核中找不到匹配,則r5置0返回

?

1:??ldmia?r5, {r3, r4}[email protected] value, mask

??????and????r4, r4, [email protected] mask wanted bits

??????teq?????r3, r4

??????beq????2f

??????add????r5, r5, #[email protected] sizeof(proc_info_list)

??????cmp???r5, r6

??????blo?????1b

??????mov???r5, #[email protected] unknown processor

2:??mov???pc, lr

?__lookup_machine_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的197行,編碼方法與檢查processor ID完全一樣,請參考前段

?

__lookup_machine_type:

??????adr?????r3, 3b

??????ldmia?r3, {r4, r5, r6}

??????sub????r3, r3, [email protected] get offset between virt&phys

??????add????r5, r5, [email protected] convert virt addresses to

??????add????r6, r6, [email protected] physical address space

1:??ldr?r3, [r5, #MACHINFO_TYPE][email protected] get machine type

??????teq?????r3, [email protected] matches loader number?

[email protected] found

??????add????r5, r5, #[email protected] next machine_desc

??????cmp???r5, r6

??????blo?????1b

??????mov???r5, #[email protected] unknown machine

2:??mov???pc, lr

代碼回到head.S第92行,檢查atags合法性,然后創建初始頁表

?

??????bl??__vet_atags

??????bl??__create_page_tables

?創建頁表的代碼在218行,首先將內核起始地址-0x4000到內核起始地址之間的16K存儲器清0

?

__create_page_tables:

[email protected] page table address

?

??????/*

???????* Clear the 16K level 1 swapper page table

???????*/

??????mov???r0, r4

??????mov???r3, #0

??????add????r6, r0, #0x4000

1:??str?r3, [r0], #4

??????str?r3, [r0], #4

??????str?r3, [r0], #4

??????str?r3, [r0], #4

??????teq?????r0, r6

??????bne????1b

?然后在234行將proc_info中的mmu_flags加載到r7

?

??????ldr?r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags在242行將PC指針右移20位,得到內核第一個1MB空間的段地址存入r6,在s3c2410平臺該值是0x300。接著根據此值存入映射標識

?

??????mov???r6, pc, lsr #[email protected] start of kernel section

??????orr?r3, r7, r6, lsl #[email protected] flags + kernel base

??????str?r3, [r4, r6, lsl #2][email protected] identity mapping

完成頁表設置后回到102行,為打開虛擬地址映射作準備。設置sp指針,函數返回地址lr指向__enable_mmu,并跳轉到linux-2.6.24-moko-linuxbj/arch/arm/mm/proc-arm920.S的386行,清除I-cache、D-cache、write buffer和TLB

?

__arm920_setup:

??????mov???r0, #0

??????mcr????p15, 0, r0, c7, [email protected] invalidate I,D caches on v4

??????mcr????p15, 0, r0, c7, c10, [email protected] drain write buffer on v4

#ifdef CONFIG_MMU

??????mcr????p15, 0, r0, c8, [email protected] invalidate I,D TLBs on v4

#endif然后返回head.S的158行,加載domain和頁表,跳轉到__turn_mmu_on

?

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

??????orr?r0, r0, #CR_A

#else

??????bic??????r0, r0, #CR_A

#endif

#ifdef CONFIG_CPU_DCACHE_DISABLE

??????bic??????r0, r0, #CR_C

#endif

#ifdef CONFIG_CPU_BPREDICT_DISABLE

??????bic??????r0, r0, #CR_Z

#endif

#ifdef CONFIG_CPU_ICACHE_DISABLE

??????bic??????r0, r0, #CR_I

#endif

??????mov???r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) |

?????????????????domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) |

?????????????????domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) |

?????????????????domain_val(DOMAIN_IO, DOMAIN_CLIENT))

??????mcr????p15, 0, r5, c3, c0, [email protected] load domain access register

??????mcr????p15, 0, r4, c2, c0, [email protected] load page table pointer

??????b???__turn_mmu_on在194行把mmu使能位寫入mmu,激活虛擬地址。然后將原來保存在sp中的地址載入pc,跳轉到head-common.S的__mmap_switched,至此代碼進入虛擬地址的世界

?

??????mov???r0, r0

??????mcr????p15, 0, r0, c1, c0, [email protected] write control reg

??????mrc????p15, 0, r3, c0, c0, [email protected] read id reg

??????mov???r3, r3

??????mov???r3, r3

??????mov???pc, r13

在head-common.S的37行開始清除內核bss段,processor ID保存在r9

七星彩走势图2元网官网