• ARM linux kernel从入口到start_kernel代码分析 只到machine type选中为止


    转载自:http://blog.sina.com.cn/bytex

    本文针对arm linux,
    从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.
    我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照.
    例:
    在文件init/main.c中:
    00478:
    asmlinkage void __init start_kernel(void)
    前面的"00478:"
    表示478行,冒号后面的内容就是源码了.


    在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.


    由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.


    另外,本文是以uncompressed kernel开始讲解的.对于内核解压缩部分的code,在
    arch/arm/boot/compressed中,本文不做讨论.



    一. 启动条件
    通常从系统上电到执行到linux kenel这部分的任务是由boot
    loader来完成.
    关于boot loader的内容,本文就不做过多介绍.
    这里只讨论进入到linux
    kernel的时候的一些限制条件,这一般是boot loader在最后跳转到kernel之前要完成的:
    1.
    CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;
    2. MMU(内存管理单元)必须是关闭的,
    此时虚拟地址对物理地址;
    3. 数据cache(Data cache)必须是关闭的
    4.
    指令cache(Instruction cache)可以是打开的,也可以是关闭的,这个没有强制要求;
    5. CPU 通用寄存器0
    (r0)必须是 0;
    6. CPU 通用寄存器1 (r1)必须是 ARM Linux machine type (关于machine
    type, 我们后面会有讲解)
    7. CPU 通用寄存器2 (r2) 必须是 kernel parameter list
    的物理地址(parameter list 是由boot loader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM
    Linux"文档).

    二. starting kernel


    首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况):




    位置
    默认值
    说明
    KERNEL_RAM_ADDR arch/arm/kernel/head.S
    +26
    0xc0008000
    kernel在RAM中的的虚拟地址
    PAGE_OFFSET include/asm-arm/memeory.h
    +50 0xc0000000
    内核空间的起始虚拟地址
    TEXT_OFFSET arch/arm/Makefile
    +137 0x00008000
    内核相对于存储空间的偏移
    TEXTADDR arch/arm/kernel/head.S
    +49 0xc0008000
    kernel的起始虚拟地址
    PHYS_OFFSET include/asm-arm/arch-xxx/memory.h 平台相关
    RAM的起始物理地址



    内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:
    00011:
    ENTRY(stext)
    对于vmlinux.lds.S,这是ld script文件,此文件的格式和汇编及C程序都不同,本文不对ld
    script作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考ld.info
    这里的ENTRY(stext)
    表示程序的入口是在符号stext.
    而符号stext是在arch/arm/kernel/head.S中定义的:
    下面我们将arm
    linux
    boot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.

    在arch/arm/kernel/head.S中
    72 - 94 行,是arm linux boot的主代码:


    00072:
    ENTRY(stext)
    00073:
    msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc
    mode
    00074:  @ and irqs
    disabled
    00075:  mrc p15,
    0, r9, c0, c0 @ get processor
    id
    00076:
    bl __lookup_processor_type @ r5=procinfo
    r9=cpuid
    00077:  movs r10,
    r5 @ invalid processor (r5=0)?
    00078:
    beq __error_p @ yes, error
    'p'
    00079:
    bl __lookup_machine_type @
    r5=machinfo
    00080:
    movs r8, r5 @ invalid machine
    (r5=0)?
    00081:  beq __error_a @ yes, error
    'a'
    00082:
    bl __create_page_tables
    00083:
    00084:

    00091:
    ldr r13, __switch_data @ address to jump to
    after
    00092:  @ mmu has been
    enabled
    00093:  adr lr,
    __enable_mmu @ return (PIC) address
    00094:
    add pc, r10,
    #PROCINFO_INITFUNC



    其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.


    arm linux boot的主线可以概括为以下几个步骤:
    1. 确定 processor
    type
    (75 - 78行)
    2. 确定 machine
    type
    (79 - 81行)
    3.
    创建页表
    (82行)
    4.
    调用平台特定的__cpu_flush函数 (在struct
    proc_info_list中) (94
    行)
    5.
    开启mmu
    (93行)
    6. 切换数据

    (91行)

    最终跳转到start_kernel
    (在__switch_data的结束的时候,调用了 b start_kernel)


    下面,我们按照这个主线,逐步的分析Code.

    1. 确定 processor type



    arch/arm/kernel/head.S中:
    00075:  mrc p15,
    0, r9, c0, c0 @ get processor
    id
    00076:
    bl __lookup_processor_type @ r5=procinfo
    r9=cpuid
    00077:  movs r10,
    r5 @ invalid processor (r5=0)?
    00078:
    beq __error_p @ yes, error
    'p'


    75行: 通过cp15协处理器的c0寄存器来获得processor id的指令. 关于cp15的详细内容可参考相关的arm手册


    76行: 跳转到__lookup_processor_type.在__lookup_processor_type中,会把processor type
    存储在r5中
    77,78行: 判断r5中的processor type是否是0,如果是0,说明是无效的processor
    type,跳转到__error_p(出错)


    __lookup_processor_type 函数主要是根据从cpu中获得的processor
    id和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中, 0表示没有找到对应的processor type.


    下面我们分析__lookup_processor_type函数
    arch/arm/kernel/head-common.S中:

    00145:
    .type __lookup_processor_type, %function
    00146:
    __lookup_processor_type:
    00147:  adr r3, 3f
    00148:
    ldmda r3, {r5 - r7}
    00149:  sub r3, r3,
    r7 @ get offset between virt&phys
    00150:
    add r5, r5, r3 @ convert virt addresses to
    00151:
    add r6, r6, r3 @ physical address space
    00152:
    1: ldmia r5, {r3, r4} @ value, mask
    00153:
    and r4, r4, r9 @ mask wanted bits
    00154:
    teq r3, r4
    00155:  beq 2f
    00156:  add r5,
    r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
    00157:
    cmp r5, r6
    00158:  blo 1b
    00159:  mov r5,
    #0 @ unknown processor
    00160: 2: mov pc,
    lr
    00161:
    00162:
    00165: ENTRY(lookup_processor_type)
    00166:
    stmfd sp!, {r4 - r7, r9, lr}
    00167:  mov r9,
    r0
    00168:  bl __lookup_processor_type
    00169:  mov r0,
    r5
    00170:  ldmfd sp!, {r4 - r7, r9,
    pc}
    00171:
    00172:
    00176:  .long __proc_info_begin
    00177:
    .long __proc_info_end
    00178: 3: .long .
    00179:
    .long __arch_info_begin
    00180:
    .long __arch_info_end


    145,
    146行是函数定义
    147行:
    取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3.

    这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册)

    148行:
    因为r3中的地址是178行的位置的地址,因而执行完后:

    r5存的是176行符号 __proc_info_begin的地址;

    r6存的是177行符号 __proc_info_end的地址;

    r7存的是3f处的地址.
    这里需要注意链接地址和运行时地址的区别.
    r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址).


    __proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中:

    00031: __proc_info_begin =
    .;

    00032: *(.proc.info.init)

    00033: __proc_info_end = .;


    这里是声明了两个变量:__proc_info_begin 和
    __proc_info_end,其中等号后面的"."是location
    counter(详细内容请参考ld.info)
    这三行的意思是:
    __proc_info_begin 的位置上,放置所有文件中的 ".proc.info.init" 段的内容,然后紧接着是 __proc_info_end
    的位置.


    kernel 使用struct
    proc_info_list来描述processor type.

    在 include/asm-arm/procinfo.h
    中:
    00029: struct proc_info_list
    {
    00030:  unsigned
    int cpu_val;
    00031:
    unsigned
    int cpu_mask;
    00032:
    unsigned
    long __cpu_mm_mmu_flags;

    00033:  unsigned
    long __cpu_io_mmu_flags;

    00034:  unsigned
    long __cpu_flush;

    00035:  const
    char *arch_name;
    00036:
    const
    char *elf_name;
    00037:
    unsigned
    int elf_hwcap;
    00038:
    const
    char *cpu_name;
    00039:
    struct processor *proc;

    00040:  struct
    cpu_tlb_fns *tlb;
    00041:
    struct
    cpu_user_fns *user;
    00042:
    struct
    cpu_cache_fns *cache;
    00043:
    };


    我们当前以at91为例,其processor是926的.

    在arch/arm/mm/proc-arm926.S 中:

    00464:  .section ".proc.info.init", #alloc,
    #execinstr

    00465:
    00466:
    .type __arm926_proc_info,#object

    00467: __arm926_proc_info:
    00468:
    .long 0x41069260 @ ARM926EJ-S
    (v5TEJ)
    00469:
    .long 0xff0ffff0
    00470:
    .long PMD_TYPE_SECT |
    \
    00471:
    PMD_SECT_BUFFERABLE |
    \
    00472:
    PMD_SECT_CACHEABLE | \

    00473:  PMD_BIT4 | \

    00474:  PMD_SECT_AP_WRITE |
    \
    00475:
    PMD_SECT_AP_READ

    00476:  .long PMD_TYPE_SECT |
    \
    00477:  PMD_BIT4 |
    \
    00478:
    PMD_SECT_AP_WRITE | \

    00479:
    PMD_SECT_AP_READ

    00480:  b __arm926_setup

    00481:
    .long cpu_arch_name

    00482:
    .long cpu_elf_name

    00483:
    .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA

    00484:
    .long cpu_arm926_name

    00485:
    .long arm926_processor_functions

    00486:
    .long v4wbi_tlb_fns

    00487:
    .long v4wb_user_fns

    00488:
    .long arm926_cache_fns

    00489:  .size __arm926_proc_info, . -
    __arm926_proc_info


    从464行,我们可以看到 __arm926_proc_info
    被放到了".proc.info.init"段中.
    对照struct
    proc_info_list,我们可以看到 __cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4.
    调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.)

    从以上的内容我们可以看出:
    r5中的__proc_info_begin是proc_info_list的起始地址,
    r6中的__proc_info_end是proc_info_list的结束地址.


    149行:
    从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.


    150行: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址
    151行:
    将r6存储的虚拟地址(__proc_info_end)转换成物理地址
    152行: 对照struct
    proc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3, r4中
    153行:
    r9中存储了processor
    id(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值
    154行:
    将153行中得到的值与r3中的cpu_val进行比较
    155行: 如果相等,说明我们找到了对应的processor
    type,跳到160行,返回
    156行: (如果不相等) , 将r5指向下一个proc_info,
    157行:
    和r6比较,检查是否到了__proc_info_end.
    158行:
    如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找
    159行:
    执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknown processor)
    160行: 返回

    2. 确定 machine type


    arch/arm/kernel/head.S中:
    00079:
    bl __lookup_machine_type @
    r5=machinfo
    00080:
    movs r8, r5 @ invalid machine
    (r5=0)?
    00081:  beq __error_a @ yes, error
    'a'


    79行: 跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把struct
    machine_desc的基地址(machine type)存储在r5中
    80,81行: 将r5中的
    machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machine type,跳转到__error_a(出错)


    __lookup_machine_type 函数
    下面我们分析__lookup_machine_type 函数:



    arch/arm/kernel/head-common.S中:

    00176:
    .long __proc_info_begin
    00177:
    .long __proc_info_end
    00178: 3: .long .
    00179:
    .long __arch_info_begin
    00180:
    .long __arch_info_end
    00181:
    00182: 
    00193:
    .type __lookup_machine_type, %function
    00194:
    __lookup_machine_type:
    00195:  adr r3, 3b
    00196:
    ldmia r3, {r4, r5, r6}
    00197:  sub r3, r3,
    r4 @ get offset between virt&phys
    00198:
    add r5, r5, r3 @ convert virt addresses to
    00199:
    add r6, r6, r3 @ physical address space
    00200:
    1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
    00201:
    teq r3, r1 @ matches loader number?
    00202:
    beq 2f @ found
    00203:  add r5,
    r5, #SIZEOF_MACHINE_DESC @ next machine_desc
    00204:  cmp r5,
    r6
    00205:  blo 1b
    00206:  mov r5,
    #0 @ unknown machine
    00207: 2: mov pc,
    lr


    193, 194行: 函数声明
    195行:
    取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3.

    和上面我们对__lookup_processor_type 函数的分析相同,r3中存放的是3b处物理地址.
    196行:
    r3是3b处的地址,因而执行完后:
    r4存的是
    3b处的地址
    r5存的是__arch_info_begin
    的地址
    r6存的是__arch_info_end 的地址


    __arch_info_begin 和
    __arch_info_end是在 arch/arm/kernel/vmlinux.lds.S中:



    00034: __arch_info_begin =
    .;

    00035: *(.arch.info.init)

    00036: __arch_info_end = .;


    这里是声明了两个变量:__arch_info_begin 和
    __arch_info_end,其中等号后面的"."是location
    counter(详细内容请参考ld.info)
    这三行的意思是:
    __arch_info_begin 的位置上,放置所有文件中的 ".arch.info.init" 段的内容,然后紧接着是 __arch_info_end
    的位置.


    kernel 使用struct machine_desc 来描述
    machine type.

    include/asm-arm/mach/arch.h 中:


    00017: struct machine_desc
    {
    00018:

    00022:  unsigned
    int nr;

    00023:  unsigned
    int phys_io;

    00024:  unsigned
    int io_pg_offst;

    00026:
    00027:  const
    char *name;

    00028:  unsigned
    long boot_params;

    00029:
    00030:  unsigned
    int video_start;

    00031:  unsigned
    int video_end;

    00032:
    00033:  unsigned
    int reserve_lp0
    :1;
    00034:  unsigned
    int reserve_lp1
    :1;
    00035:  unsigned
    int reserve_lp2
    :1;
    00036:  unsigned
    int soft_reboot
    :1;
    00037:
    void (*fixup)(struct machine_desc
    *,
    00038:
    struct tag *, char
    **,
    00039:
    struct meminfo
    *);
    00040:
    void (*map_io)(void);

    00041:
    void (*init_irq)(void);

    00042:  struct
    sys_timer *timer;

    00043:
    void (*init_machine)(void);

    00044: };

    00045:

    00046:
    00050: #define
    MACHINE_START(_type,_name) \

    00051: static const struct machine_desc
    __mach_desc_##_type \

    00052:
    __attribute_used__ \

    00053: __attribute__((__section__(".arch.info.init"))) =
    { \
    00054:
    .nr =
    MACH_TYPE_##_type, \

    00055:  .name =
    _name,

    00056:
    00057: #define
    MACHINE_END \

    00058:
    };


    内核中,一般使用宏MACHINE_START来定义machine
    type.
    对于at91, 在
    arch/arm/mach-at91rm9200/board-ek.c
    中:
    00137:
    MACHINE_START(AT91RM9200EK, "Atmel
    AT91RM9200-EK")
    00138:

    00139:
    .phys_io =
    AT91_BASE_SYS,
    00140:
    .io_pg_offst = (AT91_VA_BASE_SYS >> 18) &
    0xfffc,
    00141:
    .boot_params = AT91_SDRAM_BASE +
    0x100,
    00142:
    .timer =
    &at91rm9200_timer,
    00143:
    .map_io =
    ek_map_io,
    00144:
    .init_irq =
    ek_init_irq,
    00145:
    .init_machine =
    ek_board_init,
    00146:
    MACHINE_END



    197行: r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中
    198行:
    将r5存储的虚拟地址(__arch_info_begin)转换成物理地址
    199行:
    将r6存储的虚拟地址(__arch_info_end)转换成物理地址
    200行:
    MACHINFO_TYPE 在 arch/arm/kernel/asm-offset.c 101行定义, 这里是取 struct
    machine_desc中的nr(architecture number) 到r3中
    201行: 将r3中取到的machine type 和 r1中的
    machine type(见前面的"启动条件")进行比较
    202行: 如果相同,说明找到了对应的machine
    type,跳转到207行的2f处,此时r5中存储了对应的struct machine_desc的基地址
    203行: (不相同),
    取下一个machine_desc的地址
    204行: 和r6进行比较,检查是否到了__arch_info_end.
    205行:
    如果不相同,说明还有machine_desc,返回200行继续查找.
    206行:
    执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的, 将r5设置成0(unknown machine).
    207行:
    返回

  • 相关阅读:
    十七 SpringCloud Alibaba入门简介
    十六、Spring Cloud Sleuth 分布式请求链路追踪
    Linux下安装mongo
    MongoDB零基础入门到高级进阶-尚学堂
    十五、Spring Cloud Stream 消息驱动
    十四、SpringCloud Bus 消息总线
    十二、Gateway新一代网关
    十一、Zuul路由网关
    十、Hystrix熔断器(三)服务熔断
    十、Hystrix 断路器(二)服务降级
  • 原文地址:https://www.cnblogs.com/super119/p/2429927.html
Copyright © 2020-2023  润新知