前言
在學習、分析之前首先要弄明白一個問題:為什么要分析啟動代碼?
因為啟動代碼絕大部分都是用匯編語言寫的,對於沒學過或者不熟悉匯編語言的同學確實有一定難度,但是如果你想真正深入地學習Linux,那么讀、分析某一個體系結構(比如ARM)的啟動代碼或者其他底層代碼是必不可少的。當分析之后會發現這是有很多好處的:分析啟動代碼可以加深對匯編語言的理解;可以學習匯編語言的使用技巧;可以學習如何編寫位置無關的代碼,可以知道從啟動到start_kernel()函數之前內核到底干了什么事情,從而為后續其他內核子系統的學習打下基礎。
廢話不多說,下面基於s3c6410,以Linux-2.6.36版本為基礎進行分析。ARM Linux的啟動代碼有兩處,一處是經過壓縮的,一處是沒有經過壓縮的,壓縮的最終還是會調用沒有壓縮的,沒有壓縮的入口在arch/arm/kernel/head.S文件中,如下所示:
00000077 __HEAD 00000078 ENTRY(stext) 00000079 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode 00000080 @ and irqs disabled 00000081 mrc p15, 0, r9, c0, c0 @ get processor id 00000082 bl __lookup_processor_type @ r5=procinfo r9=cpuid 00000083 movs r10, r5 @ invalid processor (r5=0)? 00000084 beq __error_p @ yes, error 'p' 00000085 bl __lookup_machine_type @ r5=machinfo 00000086 movs r8, r5 @ invalid machine (r5=0)? 00000087 beq __error_a @ yes, error 'a' 00000088 bl __vet_atags 00000089 bl __create_page_tables 00000090 00000091 /* 00000092 * The following calls CPU specific code in a position independent 00000093 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of 00000094 * xxx_proc_info structure selected by __lookup_machine_type 00000095 * above. On return, the CPU will be ready for the MMU to be 00000096 * turned on, and r0 will hold the CPU control register value. 00000097 */ 00000098 ldr r13, __switch_data @ address to jump to after 00000099 @ mmu has been enabled 00000100 adr lr, BSYM(__enable_mmu) @ return (PIC) address 00000101 ARM( add pc, r10, #PROCINFO_INITFUNC ) 00000102 THUMB( add r12, r10, #PROCINFO_INITFUNC ) 00000103 THUMB( mov pc, r12 ) 00000104 ENDPROC(stext)
79行就是要分析的第一行代碼,設置CPU為管理模式,這也是CPU一上電所處的模式,關閉CPU普通中斷和CPU快速中斷。
81行,讀協處理器p15獲取CPU ID,結果存在r9寄存器里,待會會用到。
82行,跳轉到__lookup_processor_type標號處,在arch/arm/kernel/head-common.S文件里定義:
00000160 __lookup_processor_type: 00000161 adr r3, 3f 00000162 ldmia r3, {r5 - r7} 00000163 add r3, r3, #8 00000164 sub r3, r3, r7 @ get offset between virt&phys 00000165 add r5, r5, r3 @ convert virt addresses to 00000166 add r6, r6, r3 @ physical address space 00000167 1: ldmia r5, {r3, r4} @ value, mask 00000168 and r4, r4, r9 @ mask wanted bits 00000169 teq r3, r4 00000170 beq 2f 00000171 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) 00000172 cmp r5, r6 00000173 blo 1b 00000174 mov r5, #0 @ unknown processor 00000175 2: mov pc, lr 00000176 ENDPROC(__lookup_processor_type) …… 00000193 .align 2 00000194 3: .long __proc_info_begin 00000195 .long __proc_info_end 00000196 4: .long . 00000197 .long __arch_info_begin 00000198 .long __arch_info_end
在匯編語言中,標號代表的是地址,准確來說是鏈接地址。adr和ldr都是偽指令,它們兩者的作用都是將標號處所代表的地址存放到寄存器中。但是adr采用基於PC值的相對地址(PC+偏移值),而ldr采用的是絕對地址(直接采用標號的值),另外adr要求指令與標號位於同一個段中。
161行,因此當前PC值是存放的是一個物理地址,為什么是物理地址?為了搞清楚這個問題,下面簡單說說上一個“年代”的bootloader是怎么引導、啟動內核的,主要的流程如下:
(1)上電
(2)必要的設置
(3)關看門狗
(4)初始化SDRAM、初始化Nand Flash
(5)把bootloader拷貝到SDRAM的高處
(6)清BSS段
(7)跳到SDRAM繼續執行
(8)把Nand Flash中的內核Image拷貝到SDRAM(0x50008000)
(9)設置啟動參數,r0、r1等寄存器,關閉MMU、cache等
(10)跳到內核Image的起始處(0x50008000)執行,此后,bootloader時代一去不復返,進入Linux新時代。
現在應該知道執行到161行時,PC的值就為0x50000000~0x58000000之間的某一個值(假定內存為128MB,s3c6410物理內存的起始地址為0x50000000),即一物理地址,因此r3的值就為194行的標號3處的物理地址。
162行,分別將r3、r3+4、r3+8地址上的內容存放到r5、r6、r7寄存器中,即r5存放的是__proc_info_begin的值(是一個鏈接地址,或者說虛擬地址),r6存放的是__proc_info_end的值(是一個鏈接地址,或者說虛擬地址),因為 . 表示的是當前的鏈接地址,所以r7存放的是標號4的鏈接地址,這跟LD鏈接腳本里的 . 表示的意思是一樣的。
163行,將r3的值加8,即現在r3的值為196行的標號4的物理地址。
164行,r3 = r3 – r7,即r3 = 標號4的物理地址 - 標號4的虛擬地址,這樣就可以計算出物理地址和虛擬地址的偏移量,顯然r3的值為一負數。
165行,結果為r5 = __proc_info_begin的物理地址。
166行,結果為r6 = __proc_info_end的物理地址。
167行,取出struct proc_info_list結構體的前兩個成員的值分別放到r3、r4。struct proc_info_list結構體的定義如下:
struct proc_info_list { unsigned int cpu_val; unsigned int cpu_mask; unsigned long __cpu_mm_mmu_flags; /* used by head.S */ unsigned long __cpu_io_mmu_flags; /* used by head.S */ unsigned long __cpu_flush; /* used by head.S */ const char *arch_name; const char *elf_name; unsigned int elf_hwcap; const char *cpu_name; struct processor *proc; struct cpu_tlb_fns *tlb; struct cpu_user_fns *user; struct cpu_cache_fns *cache; };
每一種體系結構都有一個這樣的結構體變量,對於s3c6410,來說,它屬於ARMv6體系結構,它的struct proc_info_list變量在arch/arm/mm/proc-v6.S中定義,在鏈接的時候所有這些變量都被放在__proc_info_begin和__proc_info_end之間。因此,167行執行后,r3 = cpu_val,r4 = cpu_mask。
168行,將r4的值與r9的值相與,得到的CPU ID存在r4中。
169行,比較r4與r3的值。
170行,如果r4=r3,那么跳到175行處執行,即子程序返回。如果r4不等於r3,那么執行171行,將r5的值加上sizeof(struct proc_info_list),即指向下一個struct proc_info_list變量。
172行,比較r5和r6。
173行,如果r5小於r6,則跳轉到167行,重復上面的過程。如果所有struct proc_info_list變量都比較后都沒有找到對應的CPU ID,那么執行174行,r5 = 0,然后返回。
至此,__lookup_processor_type分析完畢,回到head.S的83行,把r5的值賦給r10,並影響標志位。
84行,如果r5=0,那么跳轉到__error_p標號。這里假設內核是支持當前CPU的,即r5不為0,因此不分析__error_p的內容。
85行,跳到__lookup_machine_type標號處,同樣是在arch/arm/kernel/head-common.S中定義:
00000196 4: .long . 00000197 .long __arch_info_begin 00000198 .long __arch_info_end 00000211 __lookup_machine_type: 00000212 adr r3, 4b 00000213 ldmia r3, {r4, r5, r6} 00000214 sub r3, r3, r4 @ get offset between virt&phys 00000215 add r5, r5, r3 @ convert virt addresses to 00000216 add r6, r6, r3 @ physical address space 00000217 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type 00000218 teq r3, r1 @ matches loader number? 00000219 beq 2f @ found 00000220 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc 00000221 cmp r5, r6 00000222 blo 1b 00000223 mov r5, #0 @ unknown machine 00000224 2: mov pc, lr 00000225 ENDPROC(__lookup_machine_type)
和前面的__lookup_processor_type非常類似,只不過這里查找的是struct machine_desc結構體變量,比較的是struct machine_desc的成員nr的值,因此不再分析。這里需要提一下的是,比如對於mini6410(tiny6410),struct machine_desc變量的定義在arch/arm/mach-s3c64xx/mach-mini6410.c文件中,如下所示:
00000512 MACHINE_START(MINI6410, "MINI6410") 00000513 /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ 00000514 .phys_io = S3C_PA_UART & 0xfff00000, 00000515 .io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc, 00000516 .boot_params = S3C64XX_PA_SDRAM + 0x100, 00000517 00000518 .init_irq = s3c6410_init_irq, 00000519 .map_io = mini6410_map_io, 00000520 .init_machine = mini6410_machine_init, 00000521 .timer = &s3c24xx_timer, 00000522 MACHINE_END
回到head.S,86、87行判斷是否支持當前的機器號,不支持就跳到__error_a標號處。
88行,跳到__vet_atags,同樣是在arch/arm/kernel/head-common.S中定義:
00000250 __vet_atags: 00000251 tst r2, #0x3 @ aligned? 00000252 bne 1f 00000253 00000254 ldr r5, [r2, #0] @ is first tag ATAG_CORE? 00000255 cmp r5, #ATAG_CORE_SIZE 00000256 cmpne r5, #ATAG_CORE_SIZE_EMPTY 00000257 bne 1f 00000258 ldr r5, [r2, #4] 00000259 ldr r6, =ATAG_CORE 00000260 cmp r5, r6 00000261 bne 1f 00000262 00000263 mov pc, lr @ atag pointer is ok 00000264 00000265 1: mov r2, #0 00000266 mov pc, lr 00000267 ENDPROC(__vet_atags)
251行,測試r2的低2位是否為0,也即r2的值是否4字節對齊。
252行,如果r2的低2位不為0,則跳轉到265行,將r2的值設為0,然后返回。
下面先看一下bootloader傳遞參數給內核的結構定義,在arch/arm/include/asm/setup.h文件中:
00000146 struct tag { 00000147 struct tag_header hdr; 00000148 union { 00000149 struct tag_core core; 00000150 struct tag_mem32 mem; 00000151 struct tag_videotext videotext; 00000152 struct tag_ramdisk ramdisk; 00000153 struct tag_initrd initrd; 00000154 struct tag_serialnr serialnr; 00000155 struct tag_revision revision; 00000156 struct tag_videolfb videolfb; 00000157 struct tag_cmdline cmdline; 00000158 00000159 /* 00000160 * Acorn specific 00000161 */ 00000162 struct tag_acorn acorn; 00000163 00000164 /* 00000165 * DC21285 specific 00000166 */ 00000167 struct tag_memclk memclk; 00000168 } u; 00000169 };
147行,struct tag_header的定義:
00000024 struct tag_header { 00000025 __u32 size; 00000026 __u32 tag; 00000027 };
從struct tag的定義可以知道,bootloader傳遞的參數有好幾種類型的tag,但是內核規定第一個tag必須是ATAG_CORE類型,最后一個必須是ATAG_NONE類型,每一種類型的tag都有一個編號,例如ATAG_CORE為0x54410001,ATAG_NONE為0x00000000。struct tag_header的tag成員就是用來描述tag的類型,而size成員用來描述整個tag的大小。每個tag連續存放。
那么標號__vet_atags的254行的意思就是獲取ATAG_CORE類型tag的size成員的值賦給r5。
255行,將r5的值與ATAG_CORE_SIZE比較,ATAG_CORE_SIZE的值為((2*4 + 3*4) >> 2),即5。
256行,如果255行比較的結果不相等,那么將r5與ATAG_CORE_SIZE_EMPTY進行比較,ATAG_CORE_SIZE_EMPTY的值為((2*4) >> 2),即2。
257行,如果還是不相等,那么跳轉到265行執行,同樣是將r2設為0,然后返回。
258行,獲取struct tag_header的tag成員,將它的值賦給r5。
259行,r6 = ATAG_CORE,即0x54410001。
260行,比較r5和r6的值。
261行,如果r5和r6的值不相等則跳轉到265行,如果相等則執行263行直接返回。
至此,__vet_atags標號的內容分析完畢。
回到head.S的89行,跳轉到__create_page_tables標號處,在head.S里定義:
00000219 __create_page_tables: 00000220 pgtbl r4 @ page table address 00000221 00000222 /* 00000223 * Clear the 16K level 1 swapper page table 00000224 */ 00000225 mov r0, r4 00000226 mov r3, #0 00000227 add r6, r0, #0x4000 00000228 1: str r3, [r0], #4 00000229 str r3, [r0], #4 00000230 str r3, [r0], #4 00000231 str r3, [r0], #4 00000232 teq r0, r6 00000233 bne 1b 00000234 00000235 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags 00000236 00000237 /* 00000238 * Create identity mapping for first MB of kernel to 00000239 * cater for the MMU enable. This identity mapping 00000240 * will be removed by paging_init(). We use our current program 00000241 * counter to determine corresponding section base address. 00000242 */ 00000243 mov r6, pc 00000244 mov r6, r6, lsr #20 @ start of kernel section 00000245 orr r3, r7, r6, lsl #20 @ flags + kernel base 00000246 str r3, [r4, r6, lsl #2] @ identity mapping 00000247 00000248 /* 00000249 * Now setup the pagetables for our kernel direct 00000250 * mapped region. 00000251 */ 00000252 add r0, r4, #(KERNEL_START & 0xff000000) >> 18 00000253 str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! 00000254 ldr r6, =(KERNEL_END - 1) 00000255 add r0, r0, #4 00000256 add r6, r4, r6, lsr #18 00000257 1: cmp r0, r6 00000258 add r3, r3, #1 << 20 00000259 strls r3, [r0], #4 00000260 bls 1b 00000261 00000262 #ifdef CONFIG_XIP_KERNEL 00000263 /* 00000264 * Map some ram to cover our .data and .bss areas. 00000265 */ 00000266 orr r3, r7, #(KERNEL_RAM_PADDR & 0xff000000) 00000267 .if (KERNEL_RAM_PADDR & 0x00f00000) 00000268 orr r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000) 00000269 .endif 00000270 add r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18 00000271 str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]! 00000272 ldr r6, =(_end - 1) 00000273 add r0, r0, #4 00000274 add r6, r4, r6, lsr #18 00000275 1: cmp r0, r6 00000276 add r3, r3, #1 << 20 00000277 strls r3, [r0], #4 00000278 bls 1b 00000279 #endif 00000280 00000281 /* 00000282 * Then map first 1MB of ram in case it contains our boot params. 00000283 */ 00000284 add r0, r4, #PAGE_OFFSET >> 18 00000285 orr r6, r7, #(PHYS_OFFSET & 0xff000000) 00000286 .if (PHYS_OFFSET & 0x00f00000) 00000287 orr r6, r6, #(PHYS_OFFSET & 0x00f00000) 00000288 .endif 00000289 str r6, [r0] 00000290 00000291 #ifdef CONFIG_DEBUG_LL 00000292 ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags 00000293 /* 00000294 * Map in IO space for serial debugging. 00000295 * This allows debug messages to be output 00000296 * via a serial console before paging_init. 00000297 */ 00000298 ldr r3, [r8, #MACHINFO_PGOFFIO] 00000299 add r0, r4, r3 00000300 rsb r3, r3, #0x4000 @ PTRS_PER_PGD*sizeof(long) 00000301 cmp r3, #0x0800 @ limit to 512MB 00000302 movhi r3, #0x0800 00000303 add r6, r0, r3 00000304 ldr r3, [r8, #MACHINFO_PHYSIO] 00000305 orr r3, r3, r7 00000306 1: str r3, [r0], #4 00000307 add r3, r3, #1 << 20 00000308 teq r0, r6 00000309 bne 1b 00000310 #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS) 00000311 /* 00000312 * If we're using the NetWinder or CATS, we also need to map 00000313 * in the 16550-type serial port for the debug messages 00000314 */ 00000315 add r0, r4, #0xff000000 >> 18 00000316 orr r3, r7, #0x7c000000 00000317 str r3, [r0] 00000318 #endif 00000319 #ifdef CONFIG_ARCH_RPC 00000320 /* 00000321 * Map in screen at 0x02000000 & SCREEN2_BASE 00000322 * Similar reasons here - for debug. This is 00000323 * only for Acorn RiscPC architectures. 00000324 */ 00000325 add r0, r4, #0x02000000 >> 18 00000326 orr r3, r7, #0x02000000 00000327 str r3, [r0] 00000328 add r0, r4, #0xd8000000 >> 18 00000329 str r3, [r0] 00000330 #endif 00000331 #endif 00000332 mov pc, lr 00000333 ENDPROC(__create_page_tables)
別看這個定義這么長,其實需要關注的代碼並不多。
220行,pgtbl是一個宏,定義如下:
00000047 .macro pgtbl, rd 00000048 ldr \rd, =(KERNEL_RAM_PADDR - 0x4000) 00000049 .endm
就是將KERNEL_RAM_PADDR - 0x4000的值賦給r4,現在關鍵是KERNEL_RAM_PADDR的定義:
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
其中PHYS_OFFSET就是SDRAM的起始地址,對於s3c6410,它的值為0x50000000,TEXT_OFFSET在arch/arm/Makefile中定義:
00000222 TEXT_OFFSET := $(textofs-y) 00000240 export TEXT_OFFSET GZFLAGS MMUEXT
而textofs-y的定義為:
00000118 textofs-y := 0x00008000
因此KERNEL_RAM_PADDR的值就為0x50008000,而r4的值就為0x50004000。
225行,r0 = r4。
226行,r3 = 0。
227行,r6 = r0 + 0x4000,即0x50008000。
228到233行,將0x50004000開始到0x50008000這段內存清零。
235行,別忘了r10存的是struct proc_info_list變量的起始地址。這里將其__cpu_mm_mmu_flags成員的值賦給r7。
在分析下面的代碼之前,先了解點預備知識。我們知道MMU的主要作用是將虛擬地址轉換為物理地址,但是虛擬地址與物理地址的轉換關系需要我們預先設置好(就是設置頁表項),而轉換的過程需要通過頁表來完成。對於ARM來說,映射大體分為段映射和二級映射,段映射只需要一級頁表,段映射的大小為1MB,二級映射需要兩級頁表。下面分析的代碼都只用到段映射,因此只介紹段映射。
如圖1所示(以ARM9為例),根據上面的分析可知,寄存器r4里存放的是一級頁表的基地址,當啟動MMU后,CPU發出的是虛擬地址(正確來說是修正后的虛擬地址,即MVA),然后MMU利用該地址的最高12位(MVA[31:20])做為索引值,以一級頁表基地址作為起始地址索引對應的頁表項,當索引到相應的頁表項后,根據頁表項的內容找到對應的大小為1MB的起始物理地址,然后利用MVA的低20位(MVA[19:0])索引確切的物理地址(精確到1個字節)。
圖1 段映射
具體過程如圖2所示,關鍵看圖中的虛線部分,由於頁表項的大小為4字節,因此最低兩位為0,也即4字節對齊,根據虛線里的值就可以找到相應頁表項的起始地址。從圖中也可以知道頁表基地址是16KB對齊的(最低14位為0)。
圖2 獲取一級描述符
有了上面的基礎知識后就可以繼續分析代碼了。
243行,r6 = pc,保存當前PC的值。
244行,r6 = r6 >> 20。
245行,r3 = r7 | (r6 << 20)。此時,r3的值就是一個頁表項的內容,也即段描述符。從這就可以知道244行的作用是清零r6的低20位。
246行,mem[r4 + r6 << 2] = r3,剛好與圖2中的虛線部分對應。將r3的值存到頁表相應的位置里,這樣就完成了一個頁表項的構建,也即完成了內核前1MB的映射。因為這里直接使用物理地址作為索引,所以虛擬地址與物理地址是直接映射關系,比如說虛擬地址0x50008000對應的物理地址也是0x50008000。后面會看到,這樣做是為了開啟MMU之后不用考慮太多的事情。
252行,r0 = r4 + (KERNEL_START & 0xff000000) >> 18,KERNEL_START的定義如下:
00000055 #define KERNEL_START KERNEL_RAM_VADDR
而KERNEL_RAM_VADDR的定義為:
00000029 #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
PAGE_OFFSET的值板子對應的config文件里定義,這里為0xC0000000,因此KERNEL_START = 0xC0000000 + 0x00008000。
253行,mem[r0 + (KERNEL_START & 0x00f00000) >> 18] = r3和r0 = r0 + (KERNEL_START & 0x00f00000) >> 18。其實252行253行的意思就是mem[r4 + (0xC0008000 & 0xfff00000) >> 18] = r3,即將內核的前1MB映射到以0xC0008000為起始的虛擬內存處。
254行,r6 = KERNEL_END – 1,KERNEL_END的定義為:
00000056 #define KERNEL_END _end
而_end在arch/arm/kernel/vmlinux.lds.S中定義,表示的是內核Image的結束鏈接地址。
255行,r0 = r0 + 4,即下一個頁表項的起始地址。
256行,r6 = r4 + r6 >> 18。
257行,比較r0,r6的值,並根據結果影響標志位。
258行,r3 = r3 + 1 << 20,即將r3的值加1MB。
259行,如果257行r0 <= r6的值就執行次句,mem[r0] = r3,r0 = r0 + 4。
260行,如果257行r0 <= r6的值就執行此句,跳轉到257行。
257到260行的作用就是將整個內核Image映射到以0xC0008000為起始地址的虛擬地址處,如圖3所示。
圖3 內核Image映射到虛擬地址
162行,XIP大概就是說在Flash里執行內核,而不必把內核拷貝到內存里再執行,具體沒了解過,在此略過,直接到284行。
284行,r0 = r4 + PAGE_OFFSET >> 18。
285行,r6 = r7 |( PHYS_OFFSET & 0xff000000)。
289行,mem[r0] = r6,即將物理內存的前1MB映射到0xC0000000,因為這1MB里存放有bootloader傳過來的啟動參數,從這可以看到,映射的虛擬地址存在重疊,但並沒有關系,一個虛擬地址肯定只對應一個物理地址,但一個物理地址可以對應多個虛擬地址。
291行,看名字就知道是與調試有關的,因此不分析,直接到332行,子程序返回,至此__create_page_tables分析完畢。
98行,r13 = __switch_data的地址,等會再分析__switch_data的內容。
100行,lr = __enable_mmu的物理地址。
101行,pc = r10 + PROCINFO_INITFUNC,跳到struct proc_info_list變量的__cpu_flush成員處,從arch/arm/mm/proc-v6.S文件中可以知道,那里放的是一條跳轉指令:b __v6_setup。__v6_setup也是在proc-v6.S中文件中定義:
00000157 __v6_setup: 00000158 #ifdef CONFIG_SMP 00000159 mrc p15, 0, r0, c1, c0, 1 @ Enable SMP/nAMP mode 00000160 orr r0, r0, #0x20 00000161 mcr p15, 0, r0, c1, c0, 1 00000162 #endif 00000163 00000164 mov r0, #0 00000165 mcr p15, 0, r0, c7, c14, 0 @ clean+invalidate D cache 00000166 mcr p15, 0, r0, c7, c5, 0 @ invalidate I cache 00000167 mcr p15, 0, r0, c7, c15, 0 @ clean+invalidate cache 00000168 mcr p15, 0, r0, c7, c10, 4 @ drain write buffer 00000169 #ifdef CONFIG_MMU 00000170 mcr p15, 0, r0, c8, c7, 0 @ invalidate I + D TLBs 00000171 mcr p15, 0, r0, c2, c0, 2 @ TTB control register 00000172 orr r4, r4, #TTB_FLAGS 00000173 mcr p15, 0, r4, c2, c0, 1 @ load TTB1 00000174 #endif /* CONFIG_MMU */ 00000175 adr r5, v6_crval 00000176 ldmia r5, {r5, r6} 00000177 #ifdef CONFIG_CPU_ENDIAN_BE8 00000178 orr r6, r6, #1 << 25 @ big-endian page tables 00000179 #endif 00000180 mrc p15, 0, r0, c1, c0, 0 @ read control register 00000181 bic r0, r0, r5 @ clear bits them 00000182 orr r0, r0, r6 @ set them 00000183 mov pc, lr @ return to head.S:__ret
158到162行,如果CPU是雙核以上的,那么就使能多核模式。
164到168行,失能數據Cache、指令cache和write buffer。
169到174行,如果支持MMU,那么失能數據和指令TLB,將r4或上TTB_FLAGS之后寫入到TTB1寄存器。
175行,取得v6_crval標號的物理地址,v6_crval的定義:
00000191 .type v6_crval, #object 00000192 v6_crval: 00000193 crval clear=0x01e0fb7f, mmuset=0x00c0387d, ucset=0x00c0187c
其中crval是一個宏,定義如下:
.macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
.word \clear
.word \mmuset
#else
.word \clear
.word \ucset
#endif
.endm
這里假設是支持MMU的,因此v6_crval標號的定義替換為:
v6_crval: .word 0x01e0fb7f .word 0x00c0387d
176行,r5 = 0x01e0fb7f,r6 = 0x00c0387d
177到179行,大端模式相關,現在大部分CPU都工作在小端模式。
180行,讀控制寄存器的值。
181行,r0 = r0 & (~r5)。
182行,r0 = r0 | r6。
183行,返回,注意,這里lr的值為__enable_mmu標號的物理地址,因為返回到__enable_mmu標號處執行,至此__v6_setup分析完畢,下面看__enable_mmu。
00000160 __enable_mmu: 00000161 #ifdef CONFIG_ALIGNMENT_TRAP 00000162 orr r0, r0, #CR_A 00000163 #else 00000164 bic r0, r0, #CR_A 00000165 #endif 00000166 #ifdef CONFIG_CPU_DCACHE_DISABLE 00000167 bic r0, r0, #CR_C 00000168 #endif 00000169 #ifdef CONFIG_CPU_BPREDICT_DISABLE 00000170 bic r0, r0, #CR_Z 00000171 #endif 00000172 #ifdef CONFIG_CPU_ICACHE_DISABLE 00000173 bic r0, r0, #CR_I 00000174 #endif 00000175 mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \ 00000176 domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ 00000177 domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \ 00000178 domain_val(DOMAIN_IO, DOMAIN_CLIENT)) 00000179 mcr p15, 0, r5, c3, c0, 0 @ load domain access register 00000180 mcr p15, 0, r4, c2, c0, 0 @ load page table pointer 00000181 b __turn_mmu_on 00000182 ENDPROC(__enable_mmu)
161到174行,根據配置設置相應的位,不說了。
175到179行,設置域存取寄存器。
180行,設置TTB寄存器。
181行,跳到__turn_mmu_on標號處。
00000196 __turn_mmu_on: 00000197 mov r0, r0 00000198 mcr p15, 0, r0, c1, c0, 0 @ write control reg 00000199 mrc p15, 0, r3, c0, c0, 0 @ read id reg 00000200 mov r3, r3 00000201 mov r3, r13 00000202 mov pc, r3 00000203 ENDPROC(__turn_mmu_on)
需要注意的是在執行200行時,MMU已經開啟,CPU以后發出的都是虛擬地址。201行,r3 = r13,而r13的值為__switch_data標號的絕對地址(虛擬地址),因此202行就跳到__switch_data標號處。
00000019 .type __switch_data, %object 00000020 __switch_data: 00000021 .long __mmap_switched 00000022 .long __data_loc @ r4 00000023 .long _data @ r5 00000024 .long __bss_start @ r6 00000025 .long _end @ r7 00000026 .long processor_id @ r4 00000027 .long __machine_arch_type @ r5 00000028 .long __atags_pointer @ r6 00000029 .long cr_alignment @ r7 00000030 .long init_thread_union + THREAD_START_SP @ sp
取出21行的代碼執行,也即跳轉到__mmap_switched標號處。
00000041 __mmap_switched: 00000042 adr r3, __switch_data + 4 00000043 00000044 ldmia r3!, {r4, r5, r6, r7} 00000045 cmp r4, r5 @ Copy data segment if needed 00000046 1: cmpne r5, r6 00000047 ldrne fp, [r4], #4 00000048 strne fp, [r5], #4 00000049 bne 1b 00000050 00000051 mov fp, #0 @ Clear BSS (and zero fp) 00000052 1: cmp r6, r7 00000053 strcc fp, [r6],#4 00000054 bcc 1b 00000055 00000056 ARM( ldmia r3, {r4, r5, r6, r7, sp}) 00000057 THUMB( ldmia r3, {r4, r5, r6, r7} ) 00000058 THUMB( ldr sp, [r3, #16] ) 00000059 str r9, [r4] @ Save processor ID 00000060 str r1, [r5] @ Save machine type 00000061 str r2, [r6] @ Save atags pointer 00000062 bic r4, r0, #CR_A @ Clear 'A' bit 00000063 stmia r7, {r0, r4} @ Save control register values 00000064 b start_kernel 00000065 ENDPROC(__mmap_switched)
42行,獲得__switch_data + 4的地址。
44行,將__data_loc的地址存到r4,_data的地址存到r5,__bss_start的地址存到r6,_end的地址存到r7。
45行,比較r4和r5的值,對於XIP,它們是不相等,這里顯然是相等的,因此46到49行都不執行。
51到54行,清BSS段。
56行,r4 = processor_id,r5 = __machine_arch_type,r6 = __atags_pointer,r7 = cr_alignment,sp = init_thread_union + THREAD_START_SP。
57、58行,是對於Thumb狀態的,這里啥也沒做。
59到61行,將值存到對應的地址上。
62行,清掉r0的’A’位然后存到r4,該位表示數據存取是否需要對齊。
63行,保存r0,r4的值。
64行,start_kernel,歡呼吧……