在学习、分析之前首先要弄明白一个问题:为什么要分析启动代码?
因为启动代码绝大部分都是用汇编语言写的,对于没学过或者不熟悉汇编语言的同学确实有一定难度,但是如果你想真正深入地学习Linux,那么读、分析某一个体系结构(比如ARM)的启动代码或者其他底层代码是必不可少的。当分析之后会发现这是有很多好处的:分析启动代码可以加深对汇编语言的理解;可以学习汇编语言的使用技巧;可以学习如何编写位置无关的代码,可以知道从启动到start_kernel()函数之前内核到底干了什么事情,从而为后续其他内核子系统的学习打下基础。
废话不多说,下面基于s3c6410,以Linux-2.6.36版本为基础进行分析。ARM Linux的启动代码有两处,一处是经过压缩的,一处是没有经过压缩的,压缩的最终还是会调用没有压缩的,没有压缩的入口在arch/arm/kernel/head.S文件中,如下所示:
77 __HEAD 78 ENTRY(stext) 79 setmode PSR_F_BIT PSR_I_BIT SVC_MODE, r9 @ ensure svc mode 80 @ and irqs disabled 81 mrc p15, 0, r9, c0, c0 @ get processor id 82 bl __lookup_processor_type @ r5=procinfo r9=cpuid 83 movs r10, r5 @ invalid processor (r5=0)? 84 beq __error_p @ yes, error 'p' 85 bl __lookup_machine_type @ r5=machinfo 86 movs r8, r5 @ invalid machine (r5=0)? 87 beq __error_a @ yes, error 'a' 88 bl __vet_atags 89 bl __create_page_tables 90 91 /* 92 * The following calls CPU specific code in a position independent 93 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of 94 * xxx_proc_info structure selected by __lookup_machine_type 95 * above. On return, the CPU will be ready for the MMU to be 96 * turned on, and r0 will hold the CPU control register value. 97 */ 98 ldr r13, __switch_data @ address to jump to after 99 @ mmu has been enabled 00100 adr lr, BSYM(__enable_mmu) @ return (PIC) address 00101 ARM( add pc, r10, #PROCINFO_INITFUNC ) 00102 THUMB( add r12, r10, #PROCINFO_INITFUNC ) 00103 THUMB( mov pc, r12 ) 00104 ENDPROC(stext)
79行就是要分析的第一行代码,设置CPU为管理模式,这也是CPU一上电所处的模式,关闭CPU普通中断和CPU快速中断。
81行,读协处理器p15获取CPU ID,结果存在r9寄存器里,待会会用到。
82行,跳转到__lookup_processor_type标号处,在arch/arm/kernel/head-common.S文件里定义:
00160 __lookup_processor_type: 00161 adr r3, 3f 00162 ldmia r3, {r5 - r7} 00163 add r3, r3, #8 00164 sub r3, r3, r7 @ get offset between virt&phys 00165 add r5, r5, r3 @ convert virt addresses to 00166 add r6, r6, r3 @ physical address space 00167 1: ldmia r5, {r3, r4} @ value, mask 00168 and r4, r4, r9 @ mask wanted bits 00169 teq r3, r4 00170 beq 2f 00171 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) 00172 cmp r5, r6 00173 blo 1b 00174 mov r5, #0 @ unknown processor 00175 2: mov pc, lr 00176 ENDPROC(__lookup_processor_type) …… 00193 .align 2 00194 3: .long __proc_info_begin 00195 .long __proc_info_end 00196 4: .long . 00197 .long __arch_info_begin 00198 .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(0x58)
(9)设置启动参数,r0、r1等寄存器,关闭MMU、cache等
(10)跳到内核Image的起始处(0x58)执行,此后,bootloader时代一去不复返,进入Linux新时代。
现在应该知道执行到161行时,PC的值就为0x50~0x58之间的某一个值(假定内存为128MB,s3c6410物理内存的起始地址为0x50),即一物理地址,因此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中定义:
00196 4: .long . 00197 .long __arch_info_begin 00198 .long __arch_info_end 00211 __lookup_machine_type: 00212 adr r3, 4b 00213 ldmia r3, {r4, r5, r6} 00214 sub r3, r3, r4 @ get offset between virt&phys 00215 add r5, r5, r3 @ convert virt addresses to 00216 add r6, r6, r3 @ physical address space 00217 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type 00218 teq r3, r1 @ matches loader number? 00219 beq 2f @ found 00220 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc 00221 cmp r5, r6 00 blo 1b 00223 mov r5, #0 @ unknown machine 00224 2: mov pc, lr 00225 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文件中,如下所示:
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行判断是否支持当前的机器号,不支持就跳到__error_a标号处。
88行,跳到__vet_atags,同样是在arch/arm/kernel/head-common.S中定义:
00250 __vet_atags: 00251 tst r2, #0x3 @ aligned? 00252 bne 1f 00253 00254 ldr r5, [r2, #0] @ is first tag ATAG_CORE? 00255 cmp r5, #ATAG_CORE_SIZE 00256 cmpne r5, #ATAG_CORE_SIZE_EMPTY 00257 bne 1f 00258 ldr r5, [r2, #4] 00259 ldr r6, =ATAG_CORE 00260 cmp r5, r6 00261 bne 1f 00262 00263 mov pc, lr @ atag pointer is ok 00264 00265 1: mov r2, #0 00266 mov pc, lr 00267 ENDPROC(__vet_atags)
251行,测试r2的低2位是否为0,也即r2的值是否4字节对齐。
252行,如果r2的低2位不为0,则跳转到265行,将r2的值设为0,然后返回。
下面先看一下bootloader传递参数给内核的结构定义,在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 specific 00161 */ 00162 struct tag_acorn acorn; 00163 00164 /* 00165 * DC21285 specific 00166 */ 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传递的参数有好几种类型的tag,但是内核规定第一个tag必须是ATAG_CORE类型,最后一个必须是ATAG_NONE类型,每一种类型的tag都有一个编号,例如ATAG_CORE为0x54411,ATAG_NONE为0x00。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,即0x54411。
260行,比较r5和r6的值。
261行,如果r5和r6的值不相等则跳转到265行,如果相等则执行263行直接返回。
至此,__vet_atags标号的内容分析完毕。
回到head.S的89行,跳转到__create_page_tables标号处,在head.S里定义:
00219 __create_page_tables: 00220 pgtbl r4 @ page table address 00221 00 /* 00223 * Clear the 16K level 1 swapper page table 00224 */ 00225 mov r0, r4 00226 mov r3, #0 00227 add r6, r0, #0x4 00228 1: str r3, [r0], #4 00229 str r3, [r0], #4 00230 str r3, [r0], #4 00231 str r3, [r0], #4 00232 teq r0, r6 00233 bne 1b 00234 00235 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags 00236 00237 /* 00238 * Create identity mapping for first MB of kernel to 00239 * cater for the MMU enable. This identity mapping 00240 * will be removed by paging_init(). We use our current program 00241 * counter to determine corresponding section base address. 00242 */ 00243 mov r6, pc 00244 mov r6, r6, lsr #20 @ start of kernel section 00245 orr r3, r7, r6, lsl #20 @ flags + kernel base 00246 str r3, [r4, r6, lsl #2] @ identity mapping 00247 00248 /* 00249 * Now setup the pagetables for our kernel direct 00250 * mapped region. 00251 */ 00252 add r0, r4, #(KERNEL_START & 0xff) >> 18 00253 str r3, [r0, #(KERNEL_START & 0x00f00) >> 18]! 00254 ldr r6, =(KERNEL_END - 1) 00255 add r0, r0, #4 00256 add r6, r4, r6, lsr #18 00257 1: cmp r0, r6 00258 add r3, r3, #1 << 20 00259 strls r3, [r0], #4 00260 bls 1b 00261 00262 #ifdef CONFIG_XIP_KERNEL 00263 /* 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 .endif 00270 add r0, r4, #(KERNEL_RAM_VADDR & 0xff) >> 18 00271 str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00) >> 18]! 00272 ldr r6, =(_end - 1) 00273 add r0, r0, #4 00274 add r6, r4, r6, lsr #18 00275 1: cmp r0, r6 00276 add r3, r3, #1 << 20 00277 strls r3, [r0], #4 00278 bls 1b 00279 #endif 00280 00281 /* 00282 * Then map first 1MB of ram in case it contains our boot params. 00283 */ 00284 add r0, r4, #PAGE_OFFSET >> 18 00285 orr r6, r7, #(PHYS_OFFSET & 0xff) 00286 .if (PHYS_OFFSET & 0x00f00) 00287 orr r6, r6, #(PHYS_OFFSET & 0x00f00) 00288 .endif 00289 str r6, [r0] 00290 00291 #ifdef CONFIG_DEBUG_LL 00292 ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags 00293 /* 00294 * Map in IO space for serial debugging. 00295 * This allows debug messages to be output 00296 * via a serial console before paging_init. 00297 */ 00298 ldr r3, [r8, #MACHINFO_PGOFFIO] 00299 add r0, r4, r3 00300 rsb r3, r3, #0x4 @ PTRS_PER_PGD*sizeof(long) 00301 cmp r3, #0x0800 @ limit to 512MB 00302 movhi r3, #0x0800 00303 add r6, r0, r3 00304 ldr r3, [r8, #MACHINFO_PHYSIO] 00305 orr r3, r3, r7 00306 1: str r3, [r0], #4 00307 add r3, r3, #1 << 20 00308 teq r0, r6 00309 bne 1b 00310 #if defined(CONFIG_ARCH_NETWINDER) defined(CONFIG_ARCH_CATS) 00311 /* 00312 * If we're using the NetWinder or CATS, we also need to map 00313 * in the 16550-type serial port for the debug messages 00314 */ 00315 add r0, r4, #0xff >> 18 00316 orr r3, r7, #0x7c 00317 str r3, [r0] 00318 #endif 00319 #ifdef CONFIG_ARCH_RPC 00320 /* 00321 * Map in screen at 0x02 & SCREEN2_BASE 00322 * Similar reasons here - for debug. This is 00323 * only for Acorn RiscPC architectures. 00324 */ 00325 add r0, r4, #0x02 >> 18 00326 orr r3, r7, #0x02 00327 str r3, [r0] 00328 add r0, r4, #0xd8 >> 18 00329 str r3, [r0] 00330 #endif 00331 #endif 00332 mov pc, lr 00 ENDPROC(__create_page_tables)
别看这个定义这么长,其实需要关注的代码并不多。
220行,pgtbl是一个宏,定义如下:
47 .macro pgtbl, rd 48 ldr \rd, =(KERNEL_RAM_PADDR - 0x4) 49 .endm
就是将KERNEL_RAM_PADDR - 0x4的值赋给r4,现在关键是KERNEL_RAM_PADDR的定义:
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
其中PHYS_OFFSET就是SDRAM的起始地址,对于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这段内存清零。
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的映射。因为这里直接使用物理地址作为索引,所以虚拟地址与物理地址是直接映射关系,比如说虚拟地址0x58对应的物理地址也是0x58。后面会看到,这样做是为了开启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的值板子对应的config文件里定义,这里为0xC0,因此KERNEL_START = 0xC0 + 0x08。
253行,mem[r0 + (KERNEL_START & 0x00f00) >> 18] = r3和r0 = r0 + (KERNEL_START & 0x00f00) >> 18。其实252行253行的意思就是mem[r4 + (0xC8 & 0xfff00) >> 18] = r3,即将内核的前1MB映射到以0xC8为起始的虚拟内存处。
254行,r6 = KERNEL_END – 1,KERNEL_END的定义为:
56 #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映射到以0xC8为起始地址的虚拟地址处,如图3所示。
图3 内核Image映射到虚拟地址
162行,XIP大概就是说在Flash里执行内核,而不必把内核拷贝到内存里再执行,具体没了解过,在此略过,直接到284行。
284行,r0 = r4 + PAGE_OFFSET >> 18。
285行,r6 = r7 ( PHYS_OFFSET & 0xff)。
289行,mem[r0] = r6,即将物理内存的前1MB映射到0xC0,因为这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中文件中定义:
00157 __v6_setup: 00158 #ifdef CONFIG_SMP 00159 mrc p15, 0, r0, c1, c0, 1 @ Enable SMP/nAMP mode 00160 orr r0, r0, #0x20 00161 mcr p15, 0, r0, c1, c0, 1 00162 #endif 00163 00164 mov r0, #0 00165 mcr p15, 0, r0, c7, c14, 0 @ clean+invalidate D cache 00166 mcr p15, 0, r0, c7, c5, 0 @ invalidate I cache 00167 mcr p15, 0, r0, c7, c15, 0 @ clean+invalidate cache 00168 mcr p15, 0, r0, c7, c10, 4 @ drain write buffer 00169 #ifdef CONFIG_MMU 00170 mcr p15, 0, r0, c8, c7, 0 @ invalidate I + D TLBs 00171 mcr p15, 0, r0, c2, c0, 2 @ TTB control register 00172 orr r4, r4, #TTB_FLAGS 00173 mcr p15, 0, r4, c2, c0, 1 @ load TTB1 00174 #endif /* CONFIG_MMU */ 00175 adr r5, v6_crval 00176 ldmia r5, {r5, r6} 00177 #ifdef CONFIG_CPU_ENDIAN_BE8 00178 orr r6, r6, #1 << 25 @ big-endian page tables 00179 #endif 00180 mrc p15, 0, r0, c1, c0, 0 @ read control register 00181 bic r0, r0, r5 @ clear bits them 00182 orr r0, r0, r6 @ set them 00183 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的定义:
00191 .type v6_crval, #object 00192 v6_crval: 00193 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。
00160 __enable_mmu: 00161 #ifdef CONFIG_ALIGNMENT_TRAP 00162 orr r0, r0, #CR_A 00163 #else 00164 bic r0, r0, #CR_A 00165 #endif 00166 #ifdef CONFIG_CPU_DCACHE_DISABLE 00167 bic r0, r0, #CR_C 00168 #endif 00169 #ifdef CONFIG_CPU_BPREDICT_DISABLE 00170 bic r0, r0, #CR_Z 00171 #endif 00172 #ifdef CONFIG_CPU_ICACHE_DISABLE 00173 bic r0, r0, #CR_I 00174 #endif 00175 mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) \ 00176 domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) \ 00177 domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) \ 00178 domain_val(DOMAIN_IO, DOMAIN_CLIENT)) 00179 mcr p15, 0, r5, c3, c0, 0 @ load domain access register 00180 mcr p15, 0, r4, c2, c0, 0 @ load page table pointer 00181 b __turn_mmu_on 00182 ENDPROC(__enable_mmu)
161到174行,根据配置设置相应的位,不说了。
175到179行,设置域存取寄存器。
180行,设置TTB寄存器。
181行,跳到__turn_mmu_on标号处。
00196 __turn_mmu_on: 00197 mov r0, r0 00198 mcr p15, 0, r0, c1, c0, 0 @ write control reg 00199 mrc p15, 0, r3, c0, c0, 0 @ read id reg 00200 mov r3, r3 00201 mov r3, r13 00202 mov pc, r3 00203 ENDPROC(__turn_mmu_on)
需要注意的是在执行200行时,MMU已经开启,CPU以后发出的都是虚拟地址。201行,r3 = r13,而r13的值为__switch_data标号的绝对地址(虚拟地址),因此202行就跳到__switch_data标号处。
19 .type __switch_data, %object 20 __switch_data: 21 .long __mmap_switched 22 .long __data_loc @ r4 23 .long _data @ r5 24 .long __bss_start @ r6 25 .long _end @ r7 26 .long processor_id @ r4 27 .long __machine_arch_type @ r5 28 .long __atags_pointer @ r6 29 .long cr_alignment @ r7 30 .long init_thread_union + THREAD_START_SP @ sp
取出21行的代码执行,也即跳转到__mmap_switched标号处。
41 __mmap_switched: 42 adr r3, __switch_data + 4 43 44 ldmia r3!, {r4, r5, r6, r7} 45 cmp r4, r5 @ Copy data segment if needed 46 1: cmpne r5, r6 47 ldrne fp, [r4], #4 48 strne fp, [r5], #4 49 bne 1b 50 51 mov fp, #0 @ Clear BSS (and zero fp) 52 1: cmp r6, r7 53 strcc fp, [r6],#4 54 bcc 1b 55 56 ARM( ldmia r3, {r4, r5, r6, r7, sp}) 57 THUMB( ldmia r3, {r4, r5, r6, r7} ) 58 THUMB( ldr sp, [r3, #16] ) 59 str r9, [r4] @ Save processor ID 60 str r1, [r5] @ Save machine type 61 str r2, [r6] @ Save atags pointer 62 bic r4, r0, #CR_A @ Clear 'A' bit 63 stmia r7, {r0, r4} @ Save control register values 64 b start_kernel 65 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,欢呼吧……
上一篇:arm linux 启动之一:汇编启动到start_kernel
下一篇:装载ARM Linux内核启动过程
推荐阅读最新更新时间:2024-03-16 14:57
设计资源 培训 开发板 精华推荐
- 用富士通Cortex-M3 Easy Kit开发板,DIY出你的精彩!l
- 有奖直播:ADI数字主动降噪耳机方案 8月6日上午10:00-11:30 邀您聆听让技术为我们静下来
- 【EEWORLD第十七届社区明星人物】伙拼七月明星人物胜出!
- PSoC4 Cortex-M0开发板免费申请,分享心得赢惊喜大礼!
- ADI有奖下载活动之21:ADI民用雷达解决方案
- 看美信视频 答题赢好礼!
- 12月6日上午10:00有奖直播:如何使用Microchip安全方案为IoT设备保驾护航
- 据说,来挑战的都是“老司机”,TE答题排位赛火热进行中,不服来战!
- 下载有礼|ADI 系统方案精选 (总辑)
- 已结束|TI 直播【创新下一代汽车网关系统】