写在前头
*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列内核代码基于linux-3.0.35-imx。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!
接上篇分析完__lookup_processor_type函数,程序继续往下执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1 adr r3, 2f
2 ldmia r3, {r4, r8}
3 sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
4 add r8, r8, r4 @ PHYS_OFFSET
5
6 /*
7 * r1 = machine no, r2 = atags or dtb,
8 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
9 */
10 bl __vet_atags
11#ifdef CONFIG_SMP_ON_UP
12 bl __fixup_smp
13#endif
14 ...
15 bl __create_page_tables
16
计算出PAGE_OFFSET的物理地址。
调用__vet_atags、__fixup_smp、__create_page_tables,接下来我们只分析__vet_atags和__create_page_tables。
__vet_atags
定义在head-common.S中,主要作用就是检查UBoot传进来的参数是否合法。即是否为ATAG_CORE开头的内核参数或者以OF_DT_MAGIC开头的设备树。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1__vet_atags:
2 tst r2, #0x3 @ aligned?
3 bne 1f ;判断是否对齐,如果没有对齐直接跳出
4
5 ldr r5, [r2, #0] ;加载r2寄存器里的值指向的地址的数据
6#ifdef CONFIG_OF_FLATTREE
7 ldr r6, =OF_DT_MAGIC @ is it a DTB?第二个参数可能是内核参数,也有可能是设备树
8 cmp r5, r6
9 beq 2f
10#endif
11 cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?ATAG_CORE是固定长度的
12 cmpne r5, #ATAG_CORE_SIZE_EMPTY
13 bne 1f ;不是ATAG_CORE或者ATAG_CORE长度不对,跳出
14 ldr r5, [r2, #4]
15 ldr r6, =ATAG_CORE ;判断是否为ATAG_CORE
16 cmp r5, r6
17 bne 1f
18
192: mov pc, lr @ atag/dtb pointer is ok
20
211: mov r2, #0
22 mov pc, lr
23ENDPROC(__vet_atags)
24
如果不合法,则将r2的寄存器值赋为0。
__create_page_tables
在开启MMU之前,要先做好一个页表,作为虚拟地址跟物理地址的映射,要将虚拟地址0x000000000~0xFFFFFFFF这4G的地址映射到实际的物理地址上面,由于是临时的,只是为了执行内核(因为内核编译的时候链接地址是PAGE_OFFSET+TEXT_OFFSET),与开启MMU前执行地址(即内核被bootload加载的地方,或者说内核自解压的地方)PHY_OFFSET(本开发板为0x10008000)不一致,涉及到一些变量的存放地址都是虚拟地址,所以要开启MMU做映射,才能正确执行内核。
映射只是将32位虚拟地址中高12位做映射,低20位不做映射,访问一个地址时,先取出它的高12位,然后读取页表偏移量为高12位的值的数据作为映射后的高12位,低20位未改变,如此就是物理地址。
做这样子映射总共需要4096(即2^12)个页表项,每个页表项要用4个字节保存,所以总共需要16K的空间(即0x10004000到0x10008000的物理地址空间),然后对这16K的空间进行赋值。每个页表项里面保存的值高12位是映射值,低20位是MMU flag值。
开启MMU之后,例如要访问一个地址为0x8000FFFF的变量,首先MMU会计算出它在页表项的位置,由于一个页表项代表1M(即2^20),所以0x8000FFFF的位置是0x2000(0x8000FFFF>>20),然后读取该页表项里面指向的物理地址页表,即读取0x10004000+0x2000的位置,假如此时位置的值是0x1000,那么虚拟地址0x8000FFFF对应的物理地址是((0x1000) << 20) | (0x8000FFFF & 0x000FFFFF)。
此时一些寄存器的值如下:
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
定义在head.S中,去掉未定义的宏所包括的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 1__create_page_tables:
2 pgtbl r4, r8 @ page table address;pgtbl是一个宏,定义在同文件,计算出r8所保存的内核起始物理地址前16K的地址保存在r4寄存器
3
4 /*
5 * Clear the 16K level 1 swapper page table
6 */
7 mov r0, r4
8 mov r3, #0
9 add r6, r0, #0x4000
101: str r3, [r0], #4
11 str r3, [r0], #4
12 str r3, [r0], #4
13 str r3, [r0], #4
14 teq r0, r6
15 bne 1b ;清零这16K数据,作为一级交互页表
16
17 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags;加载struct proc_info_list->__cpu_mm_mmu_flags到r7
18
19 /*
20 * Create identity mapping to cater for __enable_mmu.
21 * This identity mapping will be removed by paging_init().
22 */
23 adr r0, __enable_mmu_loc
24 ldmia r0, {r3, r5, r6}
25 sub r0, r0, r3 @ virt->phys offset
26 add r5, r5, r0 @ phys __enable_mmu,计算__enable_mmu物理地址
27 add r6, r6, r0 @ phys __enable_mmu_end,计算__enable_mmu_end物理地址
28 mov r5, r5, lsr #20 ;取高12位
29 mov r6, r6, lsr #20 ;取高12位
30
31;做__enable_mmu和__enable_mmu_end之间的平等映射
321: orr r3, r7, r5, lsl #20 @ flags + kernel base,映射值或上MMU flag
33 str r3, [r4, r5, lsl #2] @ identity mapping,将映射值保存在对应的页表项中
34 teq r5, r6
35 addne r5, r5, #1 @ next section
36 bne 1b
37
38 /*
39 * Now setup the pagetables for our kernel direct
40 * mapped region.
41 */
42 mov r3, pc
43 mov r3, r3, lsr #20
44 orr r3, r7, r3, lsl #20 ;r3保存当前PC的映射值
45 add r0, r4, #(KERNEL_START & 0xff000000) >> 18;KERNEL_START为映射后的内核起始地址,r0保存该地址所对应页表中的位置
46 str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!;保存虚拟内核起始地址的映射值
47 ldr r6, =(KERNEL_END - 1);KERNEL_END为内核映射终止地址
48 add r0, r0, #4;跳过一个页表项
49 add r6, r4, r6, lsr #18;r6保存虚拟内核终止地址所对应页表位置。
50;设置映射值
511: cmp r0, r6
52 add r3, r3, #1 << 20
53 strls r3, [r0], #4
54 bls 1b
55
56 mov pc, lr
57ENDPROC(__create_page_tables)
58 .ltorg
59 .align
60__enable_mmu_loc:
61 .long .
62 .long __enable_mmu
63 .long __enable_mmu_end
64
- 清零用来保存页表的16K数据。
- 对__enable_mmu和__enable_mmu_end之间做平等映射,所谓平等映射就是虚拟地址跟物理地址一样。这段代码是开启MMU,在开启MMU时PC的地址为物理地址,开启之后,PC只是继续读取下一条指令(即PC+=4),但是此时已变成虚拟地址,如果没有做平等映射,此时PC的值为虚拟地址,就会跑飞掉,但是有做平等映射后,虚拟地址跟物理地址一致,所以PC会正常执行,直到一个跳转指令的到来。
- 做内核起始地址和终止地址之间的映射。
- 经过这些映射,开启MMU之后,内核依然可以正常执行。
总结
由于链接地址跟加载地址的不一致,需要开启MMU,使用地址映射方法指之一致,所以需要稍微理解一下映射原理。还有一个是平等映射的关系,理解平等映射之后就不会对开启MMU时执行流程产生疑惑。
参考
暂无