ARM Linux啟動(dòng)代碼分析
在學(xué)習(xí)、分析之前首先要弄明白一個(gè)問(wèn)題:為什么要分析啟動(dòng)代碼?
本文引用地址:http://m.butianyuan.cn/article/201611/318002.htm因?yàn)閱?dòng)代碼絕大部分都是用匯編語(yǔ)言寫的,對(duì)于沒(méi)學(xué)過(guò)或者不熟悉匯編語(yǔ)言的同學(xué)確實(shí)有一定難度,但是如果你想真正深入地學(xué)習(xí)Linux,那么讀、分析某一個(gè)體系結(jié)構(gòu)(比如ARM)的啟動(dòng)代碼或者其他底層代碼是必不可少的。當(dāng)分析之后會(huì)發(fā)現(xiàn)這是有很多好處的:分析啟動(dòng)代碼可以加深對(duì)匯編語(yǔ)言的理解;可以學(xué)習(xí)匯編語(yǔ)言的使用技巧;可以學(xué)習(xí)如何編寫位置無(wú)關(guān)的代碼,可以知道從啟動(dòng)到start_kernel()函數(shù)之前內(nèi)核到底干了什么事情,從而為后續(xù)其他內(nèi)核子系統(tǒng)的學(xué)習(xí)打下基礎(chǔ)。
廢話不多說(shuō),下面基于s3c6410,以Linux-2.6.36版本為基礎(chǔ)進(jìn)行分析。ARM Linux的啟動(dòng)代碼有兩處,一處是經(jīng)過(guò)壓縮的,一處是沒(méi)有經(jīng)過(guò)壓縮的,壓縮的最終還是會(huì)調(diào)用沒(méi)有壓縮的,沒(méi)有壓縮的入口在arch/arm/kernel/head.S文件中,如下所示:
77 __HEAD78 ENTRY(stext)79 setmode PSR_F_BIT PSR_I_BIT SVC_MODE, r9 @ ensure svc mode80 @ and irqs disabled81 mrc p15, 0, r9, c0, c0 @ get processor id82 bl __lookup_processor_type @ r5=procinfo r9=cpuid83 movs r10, r5 @ invalid processor (r5=0)?84 beq __error_p @ yes, error p85 bl __lookup_machine_type @ r5=machinfo86 movs r8, r5 @ invalid machine (r5=0)?87 beq __error_a @ yes, error a88 bl __vet_atags89 bl __create_page_tables90 91 /*92 * The following calls CPU specific code in a position independent93 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of94 * xxx_proc_info structure selected by __lookup_machine_type95 * above. On return, the CPU will be ready for the MMU to be96 * turned on, and r0 will hold the CPU control register value.97 */98 ldr r13, __switch_data @ address to jump to after99 @ mmu has been enabled00100 adr lr, BSYM(__enable_mmu) @ return (PIC) address00101 ARM( add pc, r10, #PROCINFO_INITFUNC )00102 THUMB( add r12, r10, #PROCINFO_INITFUNC )00103 THUMB( mov pc, r12 )00104 ENDPROC(stext)
79行就是要分析的第一行代碼,設(shè)置CPU為管理模式,這也是CPU一上電所處的模式,關(guān)閉CPU普通中斷和CPU快速中斷。
81行,讀協(xié)處理器p15獲取CPU ID,結(jié)果存在r9寄存器里,待會(huì)會(huì)用到。
82行,跳轉(zhuǎn)到__lookup_processor_type標(biāo)號(hào)處,在arch/arm/kernel/head-common.S文件里定義:
00160 __lookup_processor_type:00161 adr r3, 3f00162 ldmia r3, {r5 - r7}00163 add r3, r3, #800164 sub r3, r3, r7 @ get offset between virt&phys00165 add r5, r5, r3 @ convert virt addresses to00166 add r6, r6, r3 @ physical address space00167 1: ldmia r5, {r3, r4} @ value, mask00168 and r4, r4, r9 @ mask wanted bits00169 teq r3, r400170 beq 2f00171 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)00172 cmp r5, r600173 blo 1b00174 mov r5, #0 @ unknown processor00175 2: mov pc, lr00176 ENDPROC(__lookup_processor_type)……00193 .align 200194 3: .long __proc_info_begin00195 .long __proc_info_end00196 4: .long .00197 .long __arch_info_begin00198 .long __arch_info_end
在匯編語(yǔ)言中,標(biāo)號(hào)代表的是地址,準(zhǔn)確來(lái)說(shuō)是鏈接地址。adr和ldr都是偽指令,它們兩者的作用都是將標(biāo)號(hào)處所代表的地址存放到寄存器中。但是adr采用基于PC值的相對(duì)地址(PC+偏移值),而ldr采用的是絕對(duì)地址(直接采用標(biāo)號(hào)的值),另外adr要求指令與標(biāo)號(hào)位于同一個(gè)段中。
161行,因此當(dāng)前PC值是存放的是一個(gè)物理地址,為什么是物理地址?為了搞清楚這個(gè)問(wèn)題,下面簡(jiǎn)單說(shuō)說(shuō)上一個(gè)“年代”的bootloader是怎么引導(dǎo)、啟動(dòng)內(nèi)核的,主要的流程如下:
(1)上電
(2)必要的設(shè)置
(3)關(guān)看門狗
(4)初始化SDRAM、初始化Nand Flash
(5)把bootloader拷貝到SDRAM的高處
(6)清BSS段
(7)跳到SDRAM繼續(xù)執(zhí)行
(8)把Nand Flash中的內(nèi)核Image拷貝到SDRAM(0x58)
(9)設(shè)置啟動(dòng)參數(shù),r0、r1等寄存器,關(guān)閉MMU、cache等
(10)跳到內(nèi)核Image的起始處(0x58)執(zhí)行,此后,bootloader時(shí)代一去不復(fù)返,進(jìn)入Linux新時(shí)代。
現(xiàn)在應(yīng)該知道執(zhí)行到161行時(shí),PC的值就為0x50~0x58之間的某一個(gè)值(假定內(nèi)存為128MB,s3c6410物理內(nèi)存的起始地址為0x50),即一物理地址,因此r3的值就為194行的標(biāo)號(hào)3處的物理地址。
162行,分別將r3、r3+4、r3+8地址上的內(nèi)容存放到r5、r6、r7寄存器中,即r5存放的是__proc_info_begin的值(是一個(gè)鏈接地址,或者說(shuō)虛擬地址),r6存放的是__proc_info_end的值(是一個(gè)鏈接地址,或者說(shuō)虛擬地址),因?yàn)?. 表示的是當(dāng)前的鏈接地址,所以r7存放的是標(biāo)號(hào)4的鏈接地址,這跟LD鏈接腳本里的 . 表示的意思是一樣的。
163行,將r3的值加8,即現(xiàn)在r3的值為196行的標(biāo)號(hào)4的物理地址。
164行,r3 = r3 – r7,即r3 = 標(biāo)號(hào)4的物理地址 - 標(biāo)號(hào)4的虛擬地址,這樣就可以計(jì)算出物理地址和虛擬地址的偏移量,顯然r3的值為一負(fù)數(shù)。
165行,結(jié)果為r5 = __proc_info_begin的物理地址。
166行,結(jié)果為r6 = __proc_info_end的物理地址。
167行,取出struct proc_info_list結(jié)構(gòu)體的前兩個(gè)成員的值分別放到r3、r4。struct proc_info_list結(jié)構(gòu)體的定義如下:
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;};
每一種體系結(jié)構(gòu)都有一個(gè)這樣的結(jié)構(gòu)體變量,對(duì)于s3c6410,來(lái)說(shuō),它屬于ARMv6體系結(jié)構(gòu),它的struct proc_info_list變量在arch/arm/mm/proc-v6.S中定義,在鏈接的時(shí)候所有這些變量都被放在__proc_info_begin和__proc_info_end之間。因此,167行執(zhí)行后,r3 = cpu_val,r4 = cpu_mask。
168行,將r4的值與r9的值相與,得到的CPU ID存在r4中。
169行,比較r4與r3的值。
170行,如果r4=r3,那么跳到175行處執(zhí)行,即子程序返回。如果r4不等于r3,那么執(zhí)行171行,將r5的值加上sizeof(struct proc_info_list),即指向下一個(gè)struct proc_info_list變量。
172行,比較r5和r6。
173行,如果r5小于r6,則跳轉(zhuǎn)到167行,重復(fù)上面的過(guò)程。如果所有struct proc_info_list變量都比較后都沒(méi)有找到對(duì)應(yīng)的CPU ID,那么執(zhí)行174行,r5 = 0,然后返回。
至此,__lookup_processor_type分析完畢,回到head.S的83行,把r5的值賦給r10,并影響標(biāo)志位。
84行,如果r5=0,那么跳轉(zhuǎn)到__error_p標(biāo)號(hào)。這里假設(shè)內(nèi)核是支持當(dāng)前CPU的,即r5不為0,因此不分析__error_p的內(nèi)容。
85行,跳到__lookup_machine_type標(biāo)號(hào)處,同樣是在arch/arm/kernel/head-common.S中定義:
00196 4: .long .00197 .long __arch_info_begin00198 .long __arch_info_end00211 __lookup_machine_type:00212 adr r3, 4b00213 ldmia r3, {r4, r5, r6}00214 sub r3, r3, r4 @ get offset between virt&phys00215 add r5, r5, r3 @ convert virt addresses to00216 add r6, r6, r3 @ physical address space00217 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type00218 teq r3, r1 @ matches loader number?00219 beq 2f @ found00220 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc00221 cmp r5, r600 blo 1b00223 mov r5, #0 @ unknown machine00224 2: mov pc, lr00225 ENDPROC(__lookup_machine_type)
和前面的__lookup_processor_type非常類似,只不過(guò)這里查找的是struct machine_desc結(jié)構(gòu)體變量,比較的是struct machine_desc的成員nr的值,因此不再分析。這里需要提一下的是,比如對(duì)于mini6410(tiny6410),struct machine_desc變量的定義在arch/arm/mach-s3c64xx/mach-mini6410.c文件中,如下所示:
00512 MACHINE_START(MINI6410, "MINI6410")00513 /* Maintainer: Ben Dooks*/00514 .phys_io = S3C_PA_UART & 0xfff00,00515 .io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc,00516 .boot_params = S3C64XX_PA_SDRAM + 0x100,00517 00518 .init_irq = s3c6410_init_irq,00519 .map_io = mini6410_map_io,00520 .init_machine = mini6410_machine_init,00521 .timer = &s3c24xx_timer,00522 MACHINE_END
回到head.S,86、87行判斷是否支持當(dāng)前的機(jī)器號(hào),不支持就跳到__error_a標(biāo)號(hào)處。
88行,跳到__vet_atags,同樣是在arch/arm/kernel/head-common.S中定義:
00250 __vet_atags:00251 tst r2, #0x3 @ aligned?00252 bne 1f00253 00254 ldr r5, [r2, #0] @ is first tag ATAG_CORE?00255 cmp r5, #ATAG_CORE_SIZE00256 cmpne r5, #ATAG_CORE_SIZE_EMPTY00257 bne 1f00258 ldr r5, [r2, #4]00259 ldr r6, =ATAG_CORE00260 cmp r5, r600261 bne 1f00262 00263 mov pc, lr @ atag pointer is ok00264 00265 1: mov r2, #000266 mov pc, lr00267 ENDPROC(__vet_atags)
251行,測(cè)試r2的低2位是否為0,也即r2的值是否4字節(jié)對(duì)齊。
252行,如果r2的低2位不為0,則跳轉(zhuǎn)到265行,將r2的值設(shè)為0,然后返回。
下面先看一下bootloader傳遞參數(shù)給內(nèi)核的結(jié)構(gòu)定義,在arch/arm/include/asm/setup.h文件中:
00146 struct tag {00147 struct tag_header hdr;00148 union {00149 struct tag_core core;00150 struct tag_mem32 mem;00151 struct tag_videotext videotext;00152 struct tag_ramdisk ramdisk;00153 struct tag_initrd initrd;00154 struct tag_serialnr serialnr;00155 struct tag_revision revision;00156 struct tag_videolfb videolfb;00157 struct tag_cmdline cmdline;00158 00159 /*00160 * Acorn specific00161 */00162 struct tag_acorn acorn;00163 00164 /*00165 * DC21285 specific00166 */00167 struct tag_memclk memclk;00168 } u;00169 };
147行,struct tag_header的定義:
24 struct tag_header {25 __u32 size;26 __u32 tag;27 };
從struct tag的定義可以知道,bootloader傳遞的參數(shù)有好幾種類型的tag,但是內(nèi)核規(guī)定第一個(gè)tag必須是ATAG_CORE類型,最后一個(gè)必須是ATAG_NONE類型,每一種類型的tag都有一個(gè)編號(hào),例如ATAG_CORE為0x54411,ATAG_NONE為0x00。struct tag_header的tag成員就是用來(lái)描述tag的類型,而size成員用來(lái)描述整個(gè)tag的大小。每個(gè)tag連續(xù)存放。
那么標(biāo)號(hào)__vet_atags的254行的意思就是獲取ATAG_CORE類型tag的size成員的值賦給r5。
255行,將r5的值與ATAG_CORE_SIZE比較,ATAG_CORE_SIZE的值為((2*4 + 3*4) >> 2),即5。
256行,如果255行比較的結(jié)果不相等,那么將r5與ATAG_CORE_SIZE_EMPTY進(jìn)行比較,ATAG_CORE_SIZE_EMPTY的值為((2*4) >> 2),即2。
257行,如果還是不相等,那么跳轉(zhuǎn)到265行執(zhí)行,同樣是將r2設(shè)為0,然后返回。
258行,獲取struct tag_header的tag成員,將它的值賦給r5。
259行,r6 = ATAG_CORE,即0x54411。
260行,比較r5和r6的值。
261行,如果r5和r6的值不相等則跳轉(zhuǎn)到265行,如果相等則執(zhí)行263行直接返回。
至此,__vet_atags標(biāo)號(hào)的內(nèi)容分析完畢。
回到head.S的89行,跳轉(zhuǎn)到__create_page_tables標(biāo)號(hào)處,在head.S里定義:
00219 __create_page_tables:00220 pgtbl r4 @ page table address00221 00 /*00223 * Clear the 16K level 1 swapper page table00224 */00225 mov r0, r400226 mov r3, #000227 add r6, r0, #0x400228 1: str r3, [r0], #400229 str r3, [r0], #400230 str r3, [r0], #400231 str r3, [r0], #400232 teq r0, r600233 bne 1b00234 00235 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags00236 00237 /*00238 * Create identity mapping for first MB of kernel to00239 * cater for the MMU enable. This identity mapping00240 * will be removed by paging_init(). We use our current program00241 * counter to determine corresponding section base address.00242 */00243 mov r6, pc00244 mov r6, r6, lsr #20 @ start of kernel section00245 orr r3, r7, r6, lsl #20 @ flags + kernel base00246 str r3, [r4, r6, lsl #2] @ identity mapping00247 00248 /*00249 * Now setup the pagetables for our kernel direct00250 * mapped region.00251 */00252 add r0, r4, #(KERNEL_START & 0xff) >> 1800253 str r3, [r0, #(KERNEL_START & 0x00f00) >> 18]!00254 ldr r6, =(KERNEL_END - 1)00255 add r0, r0, #400256 add r6, r4, r6, lsr #1800257 1: cmp r0, r600258 add r3, r3, #1 << 2000259 strls r3, [r0], #400260 bls 1b00261 00262 #ifdef CONFIG_XIP_KERNEL00263 /*00264 * Map some ram to cover our .data and .bss areas.00265 */00266 orr r3, r7, #(KERNEL_RAM_PADDR & 0xff)00267 .if (KERNEL_RAM_PADDR & 0x00f00)00268 orr r3, r3, #(KERNEL_RAM_PADDR & 0x00f00)00269 .endif00270 add r0, r4, #(KERNEL_RAM_VADDR & 0xff) >> 1800271 str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00) >> 18]!00272 ldr r6, =(_end - 1)00273 add r0, r0, #400274 add r6, r4, r6, lsr #1800275 1: cmp r0, r600276 add r3, r3, #1 << 2000277 strls r3, [r0], #400278 bls 1b00279 #endif00280 00281 /*00282 * Then map first 1MB of ram in case it contains our boot params.00283 */00284 add r0, r4, #PAGE_OFFSET >> 1800285 orr r6, r7, #(PHYS_OFFSET & 0xff)00286 .if (PHYS_OFFSET & 0x00f00)00287 orr r6, r6, #(PHYS_OFFSET & 0x00f00)00288 .endif00289 str r6, [r0]00290 00291 #ifdef CONFIG_DEBUG_LL00292 ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags00293 /*00294 * Map in IO space for serial debugging.00295 * This allows debug messages to be output00296 * via a serial console before paging_init.00297 */00298 ldr r3, [r8, #MACHINFO_PGOFFIO]00299 add r0, r4, r300300 rsb r3, r3, #0x4 @ PTRS_PER_PGD*sizeof(long)00301 cmp r3, #0x0800 @ limit to 512MB00302 movhi r3, #0x080000303 add r6, r0, r300304 ldr r3, [r8, #MACHINFO_PHYSIO]00305 orr r3, r3, r700306 1: str r3, [r0], #400307 add r3, r3, #1 << 2000308 teq r0, r600309 bne 1b00310 #if defined(CONFIG_ARCH_NETWINDER) defined(CONFIG_ARCH_CATS)00311 /*00312 * If were using the NetWinder or CATS, we also need to map00313 * in the 16550-type serial port for the debug messages00314 */00315 add r0, r4, #0xff >> 1800316 orr r3, r7, #0x7c00317 str r3, [r0]00318 #endif00319 #ifdef CONFIG_ARCH_RPC00320 /*00321 * Map in screen at 0x02 & SCREEN2_BASE00322 * Similar reasons here - for debug. This is00323 * only for Acorn RiscPC architectures.00324 */00325 add r0, r4, #0x02 >> 1800326 orr r3, r7, #0x0200327 str r3, [r0]00328 add r0, r4, #0xd8 >> 1800329 str r3, [r0]00330 #endif00331 #endif00332 mov pc, lr00 ENDPROC(__create_page_tables)
別看這個(gè)定義這么長(zhǎng),其實(shí)需要關(guān)注的代碼并不多。
220行,pgtbl是一個(gè)宏,定義如下:
47 .macro pgtbl, rd48 ldr rd, =(KERNEL_RAM_PADDR - 0x4)49 .endm
就是將KERNEL_RAM_PADDR - 0x4的值賦給r4,現(xiàn)在關(guān)鍵是KERNEL_RAM_PADDR的定義:
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
其中PHYS_OFFSET就是SDRAM的起始地址,對(duì)于s3c6410,它的值為0x50,TEXT_OFFSET在arch/arm/Makefile中定義:
00 TEXT_OFFSET := $(textofs-y)00240 export TEXT_OFFSET GZFLAGS MMUEXT
而textofs-y的定義為:
00118 textofs-y := 0x08
因此KERNEL_RAM_PADDR的值就為0x58,而r4的值就為0x54。
225行,r0 = r4。
226行,r3 = 0。
227行,r6 = r0 + 0x4,即0x58。
228到233行,將0x54開始到0x58這段內(nèi)存清零。
235行,別忘了r10存的是struct proc_info_list變量的起始地址。這里將其__cpu_mm_mmu_flags成員的值賦給r7。
在分析下面的代碼之前,先了解點(diǎn)預(yù)備知識(shí)。我們知道MMU的主要作用是將虛擬地址轉(zhuǎn)換為物理地址,但是虛擬地址與物理地址的轉(zhuǎn)換關(guān)系需要我們預(yù)先設(shè)置好(就是設(shè)置頁(yè)表項(xiàng)),而轉(zhuǎn)換的過(guò)程需要通過(guò)頁(yè)表來(lái)完成。對(duì)于ARM來(lái)說(shuō),映射大體分為段映射和二級(jí)映射,段映射只需要一級(jí)頁(yè)表,段映射的大小為1MB,二級(jí)映射需要兩級(jí)頁(yè)表。下面分析的代碼都只用到段映射,因此只介紹段映射。
如圖1所示(以ARM9為例),根據(jù)上面的分析可知,寄存器r4里存放的是一級(jí)頁(yè)表的基地址,當(dāng)啟動(dòng)MMU后,CPU發(fā)出的是虛擬地址(正確來(lái)說(shuō)是修正后的虛擬地址,即MVA),然后MMU利用該地址的最高12位(MVA[31:20])做為索引值,以一級(jí)頁(yè)表基地址作為起始地址索引對(duì)應(yīng)的頁(yè)表項(xiàng),當(dāng)索引到相應(yīng)的頁(yè)表項(xiàng)后,根據(jù)頁(yè)表項(xiàng)的內(nèi)容找到對(duì)應(yīng)的大小為1MB的起始物理地址,然后利用MVA的低20位(MVA[19:0])索引確切的物理地址(精確到1個(gè)字節(jié))。
圖1 段映射
具體過(guò)程如圖2所示,關(guān)鍵看圖中的虛線部分,由于頁(yè)表項(xiàng)的大小為4字節(jié),因此最低兩位為0,也即4字節(jié)對(duì)齊,根據(jù)虛線里的值就可以找到相應(yīng)頁(yè)表項(xiàng)的起始地址。從圖中也可以知道頁(yè)表基地址是16KB對(duì)齊的(最低14位為0)。
圖2 獲取一級(jí)描述符
有了上面的基礎(chǔ)知識(shí)后就可以繼續(xù)分析代碼了。
243行,r6 = pc,保存當(dāng)前PC的值。
244行,r6 = r6 >> 20。
245行,r3 = r7 (r6 << 20)。此時(shí),r3的值就是一個(gè)頁(yè)表項(xiàng)的內(nèi)容,也即段描述符。從這就可以知道244行的作用是清零r6的低20位。
246行,mem[r4 + r6 << 2] = r3,剛好與圖2中的虛線部分對(duì)應(yīng)。將r3的值存到頁(yè)表相應(yīng)的位置里,這樣就完成了一個(gè)頁(yè)表項(xiàng)的構(gòu)建,也即完成了內(nèi)核前1MB的映射。因?yàn)檫@里直接使用物理地址作為索引,所以虛擬地址與物理地址是直接映射關(guān)系,比如說(shuō)虛擬地址0x58對(duì)應(yīng)的物理地址也是0x58。后面會(huì)看到,這樣做是為了開啟MMU之后不用考慮太多的事情。
252行,r0 = r4 + (KERNEL_START & 0xff) >> 18,KERNEL_START的定義如下:
55 #define KERNEL_START KERNEL_RAM_VADDR
而KERNEL_RAM_VADDR的定義為:
29 #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
PAGE_OFFSET的值板子對(duì)應(yīng)的config文件里定義,這里為0xC0,因此KERNEL_START = 0xC0 + 0x08。
253行,mem[r0 + (KERNEL_START & 0x00f00) >> 18] = r3和r0 = r0 + (KERNEL_START & 0x00f00) >> 18。其實(shí)252行253行的意思就是mem[r4 + (0xC8 & 0xfff00) >> 18] = r3,即將內(nèi)核的前1MB映射到以0xC8為起始的虛擬內(nèi)存處。
254行,r6 = KERNEL_END – 1,KERNEL_END的定義為:
56 #define KERNEL_END _end
而_end在arch/arm/kernel/vmlinux.lds.S中定義,表示的是內(nèi)核Image的結(jié)束鏈接地址。
255行,r0 = r0 + 4,即下一個(gè)頁(yè)表項(xiàng)的起始地址。
256行,r6 = r4 + r6 >> 18。
257行,比較r0,r6的值,并根據(jù)結(jié)果影響標(biāo)志位。
258行,r3 = r3 + 1 << 20,即將r3的值加1MB。
259行,如果257行r0 <= r6的值就執(zhí)行次句,mem[r0] = r3,r0 = r0 + 4。
260行,如果257行r0 <= r6的值就執(zhí)行此句,跳轉(zhuǎn)到257行。
257到260行的作用就是將整個(gè)內(nèi)核Image映射到以0xC8為起始地址的虛擬地址處,如圖3所示。
圖3 內(nèi)核Image映射到虛擬地址
162行,XIP大概就是說(shuō)在Flash里執(zhí)行內(nèi)核,而不必把內(nèi)核拷貝到內(nèi)存里再執(zhí)行,具體沒(méi)了解過(guò),在此略過(guò),直接到284行。
284行,r0 = r4 + PAGE_OFFSET >> 18。
285行,r6 = r7 ( PHYS_OFFSET & 0xff)。
289行,mem[r0] = r6,即將物理內(nèi)存的前1MB映射到0xC0,因?yàn)檫@1MB里存放有bootloader傳過(guò)來(lái)的啟動(dòng)參數(shù),從這可以看到,映射的虛擬地址存在重疊,但并沒(méi)有關(guān)系,一個(gè)虛擬地址肯定只對(duì)應(yīng)一個(gè)物理地址,但一個(gè)物理地址可以對(duì)應(yīng)多個(gè)虛擬地址。
291行,看名字就知道是與調(diào)試有關(guān)的,因此不分析,直接到332行,子程序返回,至此__create_page_tables分析完畢。
98行,r13 = __switch_data的地址,等會(huì)再分析__switch_data的內(nèi)容。
100行,lr = __enable_mmu的物理地址。
101行,pc = r10 + PROCINFO_INITFUNC,跳到struct proc_info_list變量的__cpu_flush成員處,從arch/arm/mm/proc-v6.S文件中可以知道,那里放的是一條跳轉(zhuǎn)指令:b __v6_setup。__v6_setup也是在proc-v6.S中文件中定義:
00157 __v6_setup:00158 #ifdef CONFIG_SMP00159 mrc p15, 0, r0, c1, c0, 1 @ Enable SMP/nAMP mode00160 orr r0, r0, #0x2000161 mcr p15, 0, r0, c1, c0, 100162 #endif00163 00164 mov r0, #000165 mcr p15, 0, r0, c7, c14, 0 @ clean+invalidate D cache00166 mcr p15, 0, r0, c7, c5, 0 @ invalidate I cache00167 mcr p15, 0, r0, c7, c15, 0 @ clean+invalidate cache00168 mcr p15, 0, r0, c7, c10, 4 @ drain write buffer00169 #ifdef CONFIG_MMU00170 mcr p15, 0, r0, c8, c7, 0 @ invalidate I + D TLBs00171 mcr p15, 0, r0, c2, c0, 2 @ TTB control register00172 orr r4, r4, #TTB_FLAGS00173 mcr p15, 0, r4, c2, c0, 1 @ load TTB100174 #endif /* CONFIG_MMU */00175 adr r5, v6_crval00176 ldmia r5, {r5, r6}00177 #ifdef CONFIG_CPU_ENDIAN_BE800178 orr r6, r6, #1 << 25 @ big-endian page tables00179 #endif00180 mrc p15, 0, r0, c1, c0, 0 @ read control register00181 bic r0, r0, r5 @ clear bits them00182 orr r0, r0, r6 @ set them00183 mov pc, lr @ return to head.S:__ret
158到162行,如果CPU是雙核以上的,那么就使能多核模式。
164到168行,失能數(shù)據(jù)Cache、指令cache和write buffer。
169到174行,如果支持MMU,那么失能數(shù)據(jù)和指令TLB,將r4或上TTB_FLAGS之后寫入到TTB1寄存器。
175行,取得v6_crval標(biāo)號(hào)的物理地址,v6_crval的定義:
00191 .type v6_crval, #object00192 v6_crval:00193 crval clear=0x01e0fb7f, mmuset=0x00c0387d, ucset=0x00c0187c
其中crval是一個(gè)宏,定義如下:
.macro crval, clear, mmuset, ucset#ifdef CONFIG_MMU.word clear.word mmuset#else.word clear.word ucset#endif.endm
這里假設(shè)是支持MMU的,因此v6_crval標(biāo)號(hào)的定義替換為:
v6_crval:.word 0x01e0fb7f.word 0x00c0387d
176行,r5 = 0x01e0fb7f,r6 = 0x00c0387d
177到179行,大端模式相關(guān),現(xiàn)在大部分CPU都工作在小端模式。
180行,讀控制寄存器的值。
181行,r0 = r0 & (~r5)。
182行,r0 = r0 r6。
183行,返回,注意,這里lr的值為__enable_mmu標(biāo)號(hào)的物理地址,因?yàn)榉祷氐絖_enable_mmu標(biāo)號(hào)處執(zhí)行,至此__v6_setup分析完畢,下面看__enable_mmu。
00160 __enable_mmu:00161 #ifdef CONFIG_ALIGNMENT_TRAP00162 orr r0, r0, #CR_A00163 #else00164 bic r0, r0, #CR_A00165 #endif00166 #ifdef CONFIG_CPU_DCACHE_DISABLE00167 bic r0, r0, #CR_C00168 #endif00169 #ifdef CONFIG_CPU_BPREDICT_DISABLE00170 bic r0, r0, #CR_Z00171 #endif00172 #ifdef CONFIG_CPU_ICACHE_DISABLE00173 bic r0, r0, #CR_I00174 #endif00175 mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER)