IMX6Solo启动流程-Linux 内核启动 六

释放双眼,带上耳机,听听看~!

写在前头

*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个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-&gt;__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-&gt;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 &amp; 0xff000000) &gt;&gt; 18;KERNEL_START为映射后的内核起始地址,r0保存该地址所对应页表中的位置
46    str r3, [r0, #(KERNEL_START &amp; 0x00f00000) &gt;&gt; 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 &lt;&lt; 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
  1. 清零用来保存页表的16K数据。
  2. 对__enable_mmu和__enable_mmu_end之间做平等映射,所谓平等映射就是虚拟地址跟物理地址一样。这段代码是开启MMU,在开启MMU时PC的地址为物理地址,开启之后,PC只是继续读取下一条指令(即PC+=4),但是此时已变成虚拟地址,如果没有做平等映射,此时PC的值为虚拟地址,就会跑飞掉,但是有做平等映射后,虚拟地址跟物理地址一致,所以PC会正常执行,直到一个跳转指令的到来。
  3. 做内核起始地址和终止地址之间的映射。
  4. 经过这些映射,开启MMU之后,内核依然可以正常执行。

总结

由于链接地址跟加载地址的不一致,需要开启MMU,使用地址映射方法指之一致,所以需要稍微理解一下映射原理。还有一个是平等映射的关系,理解平等映射之后就不会对开启MMU时执行流程产生疑惑。

参考

暂无

给TA打赏
共{{data.count}}人
人已打赏
安全运维

WordPress网站专用docker容器环境带Waf

2020-7-18 20:04:44

安全运维

运维安全-Gitlab管理员权限安全思考

2021-9-19 9:16:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索