• linux-2.6.22.6内核启动分析之head.S引导段代码


    学习目标:

    了解arch/arm/kernel/head.S作为内核启动的第一个文件所实现的功能!


    前面通过对内核Makefile的分析,可以知道arch/arm/kernel/head.S是内核启动的第一个文件。另外,U-boot调用内核时,r1寄存器中存储“机器类型ID”,内核会使用它。

    打开arch/arm/kernel/head.S文件,可以看到stext函数是内核入口函数,函数内容如下:

    76     .section ".text.head", "ax"                             /* 定义一个.text.head段,段的属性a是允许段,x是可执行 */
    77     .type    stext, %function                               /* 定义u-boot进入内核的入口函数 */
    78 ENTRY(stext)                                                /* 入口地址stext函数 */
    79     msr    cpsr_c,  #PSR_F_BIT | PSR_I_BIT | SVC_MODE       /* 关中断,设置CPU工作在管理模式 */
    80
    81 mrc p15, 0, r9, c0, c0 /* 获取CPU的ID */
    82 bl __lookup_processor_type /* 调用函数,输入参数r9=cpuid,返回值r5=procinfo */
    83 movs r10, r5 /* 不支持当前CPU,则返回值r5=0 */ 84 beq __error_p /* 如果r5=0,则打印错误 */ 85 bl __lookup_machine_type /* 调用函数,返回值r5=machinfo */
    86 movs r8, r5 /* 不支持当前开发板,返回值r5=machinfo */ 87 beq __error_a /* 如果r5=0,则打印错误 */ 88 bl __create_page_tables /* 创建页表 */

    先来大概介绍入口函数stext每条语句实现的功能,然后再详细分析__lookup_processor_type和__lookup_machine_type这两个函数:

    第79行通过设置CPSR寄存器来确保处理器进入管理模式,并且禁止中断。

    第81行读取协处理器CP15的寄存器C0获得CPU ID。

    第82行调用__lookup_processor_type函数,检测内核是否支持当前CPU。如果支持,r5寄存器返回一个用来描述处理器结构的地址,否则r5的值为0。

    第85行调用__lookup_machine_type函数,确定内核是否支持当前开发板。如果支持,r5寄存器返回一个用来描述这个开发板的结构的地址,否则r5的值为0。

    第88行调用__create_page_table函数,其中的__create_page_table函数用来创建以及页表以建立虚拟地址到物理地址的映射关系,它用到__lookup_processer_type函数返回的proc_info_list结构。

    如果__lookup_processor_type、__lookup_machine_type这两个函数中有一个返回值为0,则内核不能启动,如果配置内核时使能了CONFIG_DEBUG_LL,还会打印错误提示信息。


    在介绍__lookup_processor_type和__lookup_machine_type这两个函数之前,还要先插讲一些内容。我们在前面简要的说过__lookup_processor_type和__lookup_machine_type这两个函数分别是用来检测内核是否支持当前架构的处理器、是否支持当前开发板,如果内核想实现检测功能,那么内核中一定会存放自己所支持的处理器架构信息以及所支持的开发板信息,下面先来找到内核中这些信息是如何被定义的。

    内核中,定义了若干个pro_info_list结构,表示它所支持的CPU。对于ARM架构的CPU,这些结构体的源码在arch/arm/mm/目录下,例如proc-arm920.S中的如下代码,它表示arm920架构CPU的pro_info_list结构。

    448     .section ".proc.info.init", #alloc, #execinstr
    449 
    450     .type    __arm920_proc_info,#object
    451 __arm920_proc_info:
    452     .long    0x41009200             /* cpu val */
    453     .long    0xff00fff0             /* cpu mask */

    内核所支持每个处理器架构都有自己的pro_info_list结构体用来保存自己CPU信息,这些不同处理器使用的pro_info_list结构体都被强制定义在“.proc_info_init”段中。在连接内核时,这些结构体被组织在一起,起始地址为__proc_info_begin,结束地址为__proc_info_end。可以从连接脚本arch/arm/kernel/vmlinux.lds中看出来。

    35    __proc_info_begin = .;          #.proc_info_init段起始地址(连接程序是动态确定)
    36      *(.proc.info.init)
    37    __proc_info_end = .;            #.proc_info_init结束地址(连接程序时动态确定)

    再来看内核中支持开发板信息内容,是如何被定义和存放的。内核中对于每种所支持的开发板都会使用宏MACHINE_START、MACHINE_END来定义一个machine_desc结构,这个结构定义了开发板相关的一些属性和函数,比如机器ID、起始I/O物理地址、Bootloader传入参数的地址、中断初始化函数等等。例如SMDK2410开发板,在arch/arm/mach-sc32410/mach-smdk2410.c中定义。

    198 MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
    199                     * to SMDK2410 */
    200     /* Maintainer: Jonas Dietsche */
    201     .phys_io    = S3C2410_PA_UART,
    202     .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    203     .boot_params    = S3C2410_SDRAM_PA + 0x100,
    204     .map_io        = smdk2410_map_io,
    205     .init_irq    = s3c24xx_init_irq,
    206     .init_machine    = smdk2410_init,
    207     .timer        = &s3c24xx_timer,
    208 MACHINE_END

    在内核的include/asm/-arm/mach/arch.h文件中找到第198、202行的宏MACHINE_START、MACHINE_END定义,这两个宏定义如下:

    50 #define MACHINE_START(_type,_name)            
    51 static const struct machine_desc __mach_desc_##_type    
    52  __used                            
    53  __attribute__((__section__(".arch.info.init"))) = {    
    54     .nr        = MACH_TYPE_##_type,        
    55    .name        = _name,
    56 
    57 #define MACHINE_END                
    58 };

    按照上述宏定义,将198行~208行代码展开如下所示:

    static const struct machine_desc __mach_desc_SMDK2410    
     __used                            
     __attribute__((__section__(".arch.info.init"))) = {    
        .nr        = MACH_TYPE_SMDK2410,        
        .name        = "SMDK2410",
            .phys_io    = S3C2410_PA_UART,
        .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
        .boot_params    = S3C2410_SDRAM_PA + 0x100,
        .map_io        = smdk2410_map_io,
        .init_irq    = s3c24xx_init_irq,
        .init_machine    = smdk2410_init,
        .timer        = &s3c24xx_timer,
    };

    将198~208行的代码展开,可以看出这段内容定义了一个静态常量的machine_desc类型结构体__mach_desc_SMDK2410。其中的MACH_TYPE_SMDK2410在arch/arm/tools/mach-types中定义,最后这个文件会被转换成一个头文件include/asm/arm/mach-type.h被其它文件包含。machine_des结构体在include/asm-arm/mach/arch.h文件中定义。__attribute__((__section__(".arch.info.init")))语句表示将所有的machine_desc结构都存放在“.arch.info.init”段中,在连接内核时,它们被组织到一起,开始地址为__arch_info_begin,结束地址为__arch_info_end。可以从连接脚本arch/arm/kernel/vmlinux.lds中看出来。 

    38   __arch_info_begin = .;                #.arch.info.init段起始地址(连接时动态确定)
    39     *(.arch.info.init)
    40   __arch_info_end = .;                  #.arch.info.init段结束地址(连接时动态确定)

    有了上面的插讲内容作为铺垫,下面对 __lookup_processor_type和__lookup_machine_type这两个函数如何去实现各自功能理解就会更加方便了。先来看 __lookup_processor_type函数,在arch/arm/kernel/head-common.S文件中定义如下:

    145     .type    __lookup_processor_type, %function
    146 __lookup_processor_type:
    147     adr    r3, 3f
    148     ldmda  r3, {r5 - r7}
    149     sub    r3, r3, r7            @ get offset between virt&phys
    150     add    r5, r5, r3            @ convert virt addresses to
    151     add    r6, r6, r3            @ physical address space
    152 1:  ldmia    r5, {r3, r4}            @ value, mask
    153     and    r4, r4, r9            @ mask wanted bits
    154     teq    r3, r4
    155     beq    2f
    156     add    r5, r5, #PROC_INFO_SZ        @ sizeof(proc_info_list)
    157     cmp    r5, r6
    158     blo    1b
    159     mov    r5, #0                @ unknown processor
    160 2:  mov    pc, lr
    176     .long    __proc_info_begin
    177     .long    __proc_info_end
    178 3:  .long    .
    179     .long    __arch_info_begin
    180     .long    __arch_info_end

    在调用__enable_mmu函数之前使用的都是物理地址,而内核却以虚拟地址链接的。所以在访问pro_info_list结构之前,先将它的虚拟地址转换为物理地址,上面代码的147行~151行就是实现上述的转换。

    第147行先获得178行物理地址。adr指令基于pc寄存器计算地址,此时MMU功能关闭,PC寄存器中使用的还是物理地址,所以执行“adr r3,3f”后,r3内存放的是178行代码的物理地址, 指令中的3f的f是forward的意思,意思是跳到程序的后面(往下)。

    第148行用来获取第176行~178行定义的数据:__proc_info_begin、__pro_info_end和"."。前两个变量是在连接内核时确定,它们是虚拟地址,在前面插讲中我们对这__proc_info_begin、__pro_info_end已经做出了详细介绍,"."表示当前的代码在编译链接后的虚拟地址。ldmda r3, {r5-r7}指令,从源地址[r3]读取4个字节数据放到寄存器中,每读一次r3-4,而指令执行后r3内容不变,数据存放到寄存器规则是低地址对于低寄存器编号,高地址对于高寄存器编号。

    第149行计算物理地址和虚拟地址的差值,第150~151根据这个差值计算__pro_info_begin、__pro_info_end的物理地址。

    下面的代码一次读取存放在“.proc_info_init”段中每个cpu架构的pro_info_list结构体前面两个成员,判断cpu_val是否等于r9&cpu_mask,r9是读取head.S中获取的CPU ID.如果比较相等,则表示内核支持当前CPU,直接返回这个结构地址。如果“.proc_info_init”段所有pro_info_list结构都不支持这个CPU,则返回0。

    第160行是子函数调用返回语句。

    接着再来分析__lookup_machine_type这个函数,同样的这个函数也在在arch/arm/kernel/head-common.S文件中定义,其代码如下所示:

    193     .type    __lookup_machine_type, %function
    194 __lookup_machine_type:
    195     adr    r3, 3b                     @address of 3b, Physical address
    196     ldmia    r3, {r4, r5, r6}   @r4="." virtual address of 3b,r5=__arch_info_begin ,r6=__arch_info_end
    197     sub    r3, r3, r4            @ get offset between virt&phys
    198     add    r5, r5, r3            @ convert virt addresses to
    199     add    r6, r6, r3            @ physical address space
    200 1:  ldr    r3, [r5, #MACHINFO_TYPE]    @ get machine type
    201     teq    r3, r1                @ matches loader number?
    202     beq    2f                @ found
    203     add    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    204     cmp    r5, r6
    205     blo    1b
    206     mov    r5, #0                @ unknown machine
    207 2:  mov    pc, lr

    第195行先获得178行物理地址。adr指令基于pc寄存器计算地址,此时MMU功能关闭,PC寄存器中使用的还是物理地址,所以执行“adr r3,3f”后,r3内存放的是178行代码的物理地址, 指令中的3b的b是backward的意思,意思是跳到程序的前面(往上)。

    第196行用来获取第178行~180行定义的数据:__arch_info_begin、__arch_info_end和"."。前两个变量都是在连接内核时确定,它们是虚拟地址,在前面插讲中我们对这__arch_info_begin、__arch_info_end已经做出了详细介绍,"."表示当前的代码在编译链接后的虚拟地址。ldmia r3, {r5-r7}指令,从源地址[r3]读取4个字节数据放到寄存器中,每读一次r3+4,而指令执行后r3内容不变,数据存放到寄存器规则是低地址对于低寄存器编号,高地址对于高寄存器编号。

    第197行计算物理地址和虚拟地址的差值,第198~199行根据这个差值计算__arch_info_begin、__arch_info_end的物理地址。

    下面的代码读取存放在“.arch_info_init”段中每个machine_des结构体机器类型ID,判断uboot传入机器ID(通过r1寄存器传入)是否等于内核中存放的不同machine_des结构体的机器类型ID。如果比较相等,则表示内核支持当前开发板,直接返回这个结构地址。如果“.arch_info_init”段所有存放machine_des结构都不支持这个开发板,则返回0。

    第207行是子函数调用返回语句。


    继续分析arch/arm/kernel/head.S代码

    97     ldr    r13, __switch_data        @ address to jump to after
    98                         @ mmu has been enabled
    99     adr    lr, __enable_mmu        @ return (PIC) address
    100    add    pc, r10, #PROCINFO_INITFUNC

    第97行把__switch_data函数地址存放到r13寄存器中,存放的地址为虚拟地址。

    第99行把__enable_mmu函数地址存放到lr(r4)寄存器中,存放地址为物理地址。

    第100行#PROCINFO_INITFUNC为16,程序计数器pc值=r10+16,由上面分析可以知道r10内容为内核中描述当前处理器结构的地址,也就是arch/arm/mm/pro-arm920.S文件中__arm920_proc_info入口地址,pc=r10+16,即跳转下面代码第502处执行__arm920_setup函数。

    451 __arm920_proc_info:
    452    .long    0x41009200
    453    .long    0xff00fff0
    454    .long   PMD_TYPE_SECT | 
    455        PMD_SECT_BUFFERABLE | 
    456         PMD_SECT_CACHEABLE | 
    457         PMD_BIT4 | 
    458         PMD_SECT_AP_WRITE | 
    459         PMD_SECT_AP_READ
    460    .long   PMD_TYPE_SECT | 
    461         PMD_BIT4 | 
    462         PMD_SECT_AP_WRITE | 
    463         PMD_SECT_AP_READ
    464    b    __arm920_setup
    465    .long    cpu_arch_name
    ...................
    476 #endif
    477    .size    __arm920_proc_info, . - __arm920_proc_info

      __arm920_setup函数内容如下:

    385     .type    __arm920_setup, #function
    386 __arm920_setup:
    387     mov    r0, #0
    388     mcr    p15, 0, r0, c7, c7        @ invalidate I,D caches on v4
    389     mcr    p15, 0, r0, c7, c10, 4        @ drain write buffer on v4
    390 #ifdef CONFIG_MMU
    391     mcr    p15, 0, r0, c8, c7        @ invalidate I,D TLBs on v4
    392 #endif
    393     adr    r5, arm920_crval
    394     ldmia    r5, {r5, r6}
    395     mrc    p15, 0, r0, c1, c0        @ get control register v4
    396     bic    r0, r0, r5
    397     orr    r0, r0, r6
    398     mov    pc, lr
    399    .size    __arm920_setup, . - __arm920_setup

     __arm920_setup函数功能是禁止ICache、DCache、数据Cache、指令Cache,最后将lr寄存器内容传送给PC程序计数器,由上面知道lr寄存器存放为__enable_mmu函数地址,此时程序将跳转到__enable_mmu函数处执行。__enable_mmu函数在arch/arm/kernel/head.S文件中,内容如下:

    152     .type    __enable_mmu, %function
    153 __enable_mmu:
    154 #ifdef CONFIG_ALIGNMENT_TRAP
    155     orr    r0, r0, #CR_A
    156 #else
    157     bic    r0, r0, #CR_A
    158 #endif
    159 #ifdef CONFIG_CPU_DCACHE_DISABLE
    160     bic    r0, r0, #CR_C
    161 #endif
    162 #ifdef CONFIG_CPU_BPREDICT_DISABLE
    163     bic    r0, r0, #CR_Z
    164 #endif
    165 #ifdef CONFIG_CPU_ICACHE_DISABLE
    166     bic    r0, r0, #CR_I
    167 #endif
    168     mov    r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | 
    169               domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | 
    170               domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | 
    171               domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    172     mcr    p15, 0, r5, c3, c0, 0        @ load domain access register
    173    mcr    p15, 0, r4, c2, c0, 0        @ load page table pointer
    174     b    __turn_mmu_on

     __turn_mmu_on代码如下:

    187     .align    5
    188     .type    __turn_mmu_on, %function
    189 __turn_mmu_on:
    190     mov    r0, r0
    191     mcr    p15, 0, r0, c1, c0, 0        @ write control reg
    192     mrc    p15, 0, r3, c0, c0, 0        @ read id reg
    193     mov    r3, r3
    194     mov    r3, r3
    195     mov    pc, r13

    __enable_mmu函数用来使能MMU,最后将r13寄存器内容赋给PC寄存器,由上面分析可知r13寄存器内容为__switch_data函数地址(此处地址为虚拟地址,因为已经开启了MMU功能),最终程序跳转到arm/arch/kernel/head-common.S文件__switch_data入口地址。__switch_data地址存放内容如下:

    14     .type    __switch_data, %object
    15 __switch_data:
    16     .long    __mmap_switched
    17     .long    __data_loc            @ r4
    18     .long    __data_start            @ r5
    19     .long    __bss_start            @ r6
    20     .long    _end                @ r7
    21     .long    processor_id            @ r4
    22     .long    __machine_arch_type        @ r5
    23     .long    cr_alignment            @ r6
    24     .long    init_thread_union + THREAD_START_SP @ sp

     __switch_data地址处存放内容为__mmap_switched地址,PC跳转到__mmp_switched处执行,__mmp_swirched函数内容如下:

    34     .type    __mmap_switched, %function
    35 __mmap_switched:
    36     adr    r3, __switch_data + 4
    37 
    38     ldmia    r3!, {r4, r5, r6, r7}
    39     cmp    r4, r5                @ Copy data segment if needed
    40 1:  cmpne    r5, r6
    41     ldrne    fp, [r4], #4
    42     strne    fp, [r5], #4
    43     bne    1b
    44 
    45     mov    fp, #0                @ Clear BSS (and zero fp)
    46 1:  cmp    r6, r7
    47     strcc    fp, [r6],#4
    48     bcc    1b
    49 
    50     ldmia    r3, {r4, r5, r6, sp}
    51     str    r9, [r4]            @ Save processor ID
    52     str    r1, [r5]            @ Save machine type
    53     bic    r4, r0, #CR_A            @ Clear 'A' bit
    54     stmia    r6, {r0, r4}            @ Save control register values
    55     b    start_kernel

    第36~43行实现复制数据段。

    第45~48行实现清除BSS段。

    第50行设置栈指针。

    第51行保存CPU ID。

    第52行存机器类型ID。

    第55行跳转到start_kernel函数执行。

    总结:引导阶段代码做了以下内容

    1、首先检查内核是否支持当前架构处理器,然后检测是否支持当前开发板,若支持执行后续操作。
    2、设置页表、使能MMU
    3、执行调用内核执行第一个C函数start_kernel之前的常规工作,包括复制数据段、清除BSS段等
  • 相关阅读:
    Struts2与Ajax数据交互
    Struts2笔记--文件下载
    Struts2笔记--文件上传
    Struts2笔记--Action访问Servlet API
    Servlet笔记2-文件上传
    Listener监听器笔记1
    ios开发 "此证书的签发者无效"
    WinObjC 微软搞了一个这个Windows Bridge for iOS,吸引iOS开发者; 表示很期待
    unity与iOS、Android交互
    iOS 9检测QQ、微信是否安装
  • 原文地址:https://www.cnblogs.com/053179hu/p/9289106.html
Copyright © 2020-2023  润新知