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

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

写在前头

*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列内核代码基于linux-3.0.35-imx。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!


入口stext

在之前的文章里面已经说过了内核镜像文件的生成流程以及内核的自解压,内核解压之后就跳转到解压的起始地址。其实就是跳转到arch/arm/boot/Image的入口,或者说跳转到vmlinux的入口。
老规矩,先看vmlinux的链接脚本,在arch/arm/kernel/vmlinux.lds:

OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0x80000000 + 0x00008000;

}

这么这个片段我们可以看出一个是入口地址是stext,另外一个是链接地址是0x80008000。(注意此时运行地址是0x10800000,所以在内核开启MMU之前运行地址和链接地址是不一致的,只有到开启MMU之后的代码运行地址跟链接地址才一致)。
stext的定义在arch/arm/kernel/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
1    __HEAD
2ENTRY(stext)
3    setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
4                        @ and irqs disabled
5    mrc p15, 0, r9, c0, c0      @ get processor id
6    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
7    movs    r10, r5             @ invalid processor (r5=0)?
8 THUMB( it  eq )        @ force fixup-able long branch encoding
9    beq __error_p           @ yes, error 'p'
10
11#ifndef CONFIG_XIP_KERNEL
12    adr r3, 2f
13    ldmia   r3, {r4, r8}
14    sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)
15    add r8, r8, r4          @ PHYS_OFFSET
16#else
17    ldr r8, =PLAT_PHYS_OFFSET
18#endif
19
20    /*
21     * r1 = machine no, r2 = atags or dtb,
22     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
23     */
24    bl  __vet_atags
25#ifdef CONFIG_SMP_ON_UP
26    bl  __fixup_smp
27#endif
28#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
29    bl  __fixup_pv_table
30#endif
31    bl  __create_page_tables
32
33    /*
34     * The following calls CPU specific code in a position independent
35     * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
36     * xxx_proc_info structure selected by __lookup_processor_type
37     * above.  On return, the CPU will be ready for the MMU to be
38     * turned on, and r0 will hold the CPU control register value.
39     */
40    ldr r13, =__mmap_switched       @ address to jump to after
41                        @ mmu has been enabled
42    adr lr, BSYM(1f)            @ return (PIC) address
43    mov r8, r4              @ set TTBR1 to swapper_pg_dir
44 ARM(   add pc, r10, #PROCINFO_INITFUNC )
45 THUMB( add r12, r10, #PROCINFO_INITFUNC    )
46 THUMB( mov pc, r12             )
471:  b   __enable_mmu
48ENDPROC(stext)
49

我分析下重点部分:


__lookup_processor_type

在进入SVC模式,关闭中断之后,接着:


1
2
3
1    mrc p15, 0, r9, c0, c0      @ get processor id
2    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
3

将处理器ID保存在r9寄存器,然后调用__lookup_processor_type。__lookup_processor_type函数定义在同目录的head-common.S中,主要的作用就是根据r9寄存器里面保存的处理器类型判断该处理器是否被支持。
定义为:


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
1/*
2 * Read processor ID register (CP#15, CR0), and look up in the linker-built
3 * supported processor list.  Note that we can't use the absolute addresses
4 * for the __proc_info lists since we aren't running with the MMU on
5 * (and therefore, we are not in the correct address space).  We have to
6 * calculate the offset.
7 *
8 *  r9 = cpuid
9 * Returns:
10 *  r3, r4, r6 corrupted
11 *  r5 = proc_info pointer in physical address space
12 *  r9 = cpuid (preserved)
13 */
14    __CPUINIT
15__lookup_processor_type:
16    adr r3, __lookup_processor_type_data
17    ldmia   r3, {r4 - r6}
18    sub r3, r3, r4          @ get offset between virt&phys
19    add r5, r5, r3          @ convert virt addresses to
20    add r6, r6, r3          @ physical address space
211:  ldmia   r5, {r3, r4}            @ value, mask
22    and r4, r4, r9          @ mask wanted bits
23    teq r3, r4
24    beq 2f
25    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
26    cmp r5, r6
27    blo 1b
28    mov r5, #0              @ unknown processor
292:  mov pc, lr
30ENDPROC(__lookup_processor_type)
31
  1. 将__lookup_processor_type_data处的数据加载到r3~r6寄存器内。__lookup_processor_type_data处的定义


1
2
3
4
5
1    .long   .    ;当前位置,被加载到r4寄存器
2    .long   __proc_info_begin  ;被加载到r5寄存器
3    .long   __proc_info_end    ;被加载到r6寄存器
4    .size   __lookup_processor_type_data, . - __lookup_processor_type_data
5

__proc_info_begin和__proc_info_end定义在vmlinux.lds中,后续讲到它的具体作用。
由于__proc_info_begin和__proc_info_end是链接时候赋值的,而链接地址与运行地址不一致,所以我们需要重新计算它的运行地址。


1
2
3
4
1    sub r3, r3, r4          @ get offset between virt&phys
2    add r5, r5, r3          @ convert virt addresses to
3    add r6, r6, r3          @ physical address space
4

之前的代码有出现过计算运行地址和链接地址的偏移量但是我没有详细分析,在这里统一分析。
首先是计算实际地址:

adr r3, __lookup_processor_type_data

adr指令是一条与位置无关的指令,相当于add r3,pc,offset;
其中offset是当前指令与__lookup_processor_type_data之间的偏移量,所以无论程序被加载到哪个位置,总能计算出__lookup_processor_type_data的实际位置。
然后将__lookup_processor_type_data处的位置加载到r4~r6寄存器中,其中加载到r4寄存器的数据是

.long .

即是一个当前位置,这个地址是链接地址,在链接的时候已经固定,而r3寄存器里面保存的是这个位置的实际地址,所以两者相减,就是运行地址和链接地址的偏移量。
2. __proc_info_begin和__proc_info_end之中保存的数据是*(.proc.info.init)段,*(.proc.info.init)段是处理器信息的一个段,在arch/arm/mm/proc-**.S中。
例如在arch/arm/mm/proc-v7.S中的定义:


1
2
3
4
5
6
7
8
1    .section ".proc.info.init", #alloc, #execinstr
2
3    .type   __v7_ca9mp_proc_info, #object
4__v7_ca9mp_proc_info:
5    .long   0x410fc090      @ Required ID value
6    .long   0xff0ffff0      @ Mask for ID
7    ...
8

实际上,段.proc.info.init有个C语言的数据结构定义在arch/arm/include/asm/procinfo.h:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1struct proc_info_list {
2    unsigned int        cpu_val;
3    unsigned int        cpu_mask;
4    unsigned long       __cpu_mm_mmu_flags; /* used by head.S */
5    unsigned long       __cpu_io_mmu_flags; /* used by head.S */
6    unsigned long       __cpu_flush;        /* used by head.S */
7    const char      *arch_name;
8    const char      *elf_name;
9    unsigned int        elf_hwcap;
10    const char      *cpu_name;
11    struct processor    *proc;
12    struct cpu_tlb_fns  *tlb;
13    struct cpu_user_fns *user;
14    struct cpu_cache_fns    *cache;
15};
16

可以直观地看出它的定义。特别注意的是该数据结构的前两个成员cpu_val和cpu_mask,判断CPU是否支持就是根据这两个成员来判断的。
3. 接下来就是遍历支持的CPU类型数据结构表中,判断是否支持本CPU类型:


1
2
3
4
5
6
7
8
9
10
11:  ldmia   r5, {r3, r4}            @ value, mask
2    and r4, r4, r9          @ mask wanted bits
3    teq r3, r4
4    beq 2f
5    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
6    cmp r5, r6
7    blo 1b
8    mov r5, #0              @ unknown processor
92:  mov pc, lr
10

此时r5寄存器中保存的是.proc.info.init的起始地址,r6寄存器保存的是.proc.info.init段的终止地址。
首先将r5地址处的两个数据加载到r3,r4寄存器内,根据定义我们可以知道此时加载的是cpu_val和cpu_mask,然后将r9寄存器与cpu_mask与操作再与cpu_val比较。
如果相等则说明支持该CPU,跳转到

2: mov pc,lr

即返回。
不相等则

add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)

跳到下一个struct proc_info_list再对比。遍历所有的struct proc_info_list结构体。
如果有找到CPU类型,则r5里面保存的是对应的struct proc_info_list地址,否则为0.
返回到linux的入口处,接着就判断r5寄存器的值是否为0,如果为0则说明不支持该CPU,跳转到__error_p(不分析),否则继续执行。


总结

入口地址做的第一件事就是判断内核是否支持该CPU,如果支持,同时获取到该CPU的struct proc_info_list数据结构的地址保存到r0寄存器内。

参考

暂无

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

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

2020-7-18 20:04:44

安全运维

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

2021-9-19 9:16:14

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