Linux内核---13.启动分析1之arch/arm/kernel/head.S

时间:2021-04-06 16:38:21
0、实验环境:

硬件: TQ2440
内核:   2.6.25
uboot将内核从nand flash读到内存的0x3000800处,并解压,此时:
r0 = 0
r1 = machine_number     (uboot中设为168)
r2 = 0x30008000            (r2不是参数地址)
真正的参数是在uboot的setup_linux_param设置的,
uboot1.1.6/lib_arm/test_zImage.c
    int test_zImage(void)
            --> setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET); //将参数拷贝到了0x3000100处
注意: 虽然跳过了uncompress阶段,但是arch / arm / kernel / head . S还是运行在0x3000800处。
一、 整体分析
1.1 从arch/arm/kernel/vmlinux.lds.S可以看出
.text.head : {
  1.     _stext = .;
  2.     _sinittext = .;
  3.     *(.text.head)
  4. }
1.2 程序的起始在 arch/arm/kernel/head.S

  1.  79 ENTRY(stext)
  2.  80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
  3.  81     @ and irqs disabled
  4.  82     mrc p15, 0, r9, c0, c0 @ get processor id
  5.  83     bl __lookup_processor_type @ r5=procinfo r9=cpuid
  6.  84     movs r10, r5 @ invalid processor (r5=0)?
  7.  85     beq __error_p @ yes, error 'p'
  8.  86     bl __lookup_machine_type @ r5=machinfo
  9.  87     movs r8, r5 @ invalid machine (r5=0)?
  10.  88     beq __error_a @ yes, error 'a'
  11.  89     bl __vet_atags
  12.  90     bl __create_page_tables
  13.  91
  14.  92 /*
  15.  93 * The following calls CPU specific code in a position independent
  16.  94 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
  17.  95 * xxx_proc_info structure selected by __lookup_machine_type
  18.  96 * above. On return, the CPU will be ready for the MMU to be
  19.  97 * turned on, and r0 will hold the CPU control register value.
  20.  98 */
  21.  99     ldr r13, __switch_data @ address to jump to after
  22. 100     @ mmu has been enabled
  23. 101     adr lr, __enable_mmu @ return (PIC) address
  24. 102     add pc, r10, #PROCINFO_INITFUNC
80行:
Linux内核---13.启动分析1之arch/arm/kernel/head.S
将CPSR的I与F位置1,关闭IRQ与FRQ,同时模式设为SVC模式
82行 将cpu_id读到r9中
83行 查找_proc_info段,看有没有与以硬件寄存器中的cpu_id相匹配的proc_info, r5指向查找到的procinfo
84行 将r5保存在r10中,即r10指向查找到的procinfo的首地址
85行 为0,打印错误信息
86行 在arch_info中查找machine_nr=r1=168的machine_desc, r5指向查找到的machine_desc
87行 将r5保存在r8中,即r8是指向machine_desc的首地址
88行 为0,打印错误信息
89行 检查参数, 因为r2=0x30008000,不是参数列表的地址,所以执行完后r2=0
90行  创建页表
99-102行 执行顺序 initfunc->__enble_mmu->switch_data->start_kernel

1.2  试想如果有以下要求,自己怎么写?
            把r5结果保存到r10中,并且判断是否为0,为0则打印错误
            肯定会写出:
                  mov r10, r5
                  cmp r10, #0
                 beq __error_p
           但是还有更简炼的写法:
                 movs r10 ,  r5
                 beq __error_p

二、__lookup_processor_type分析

2.1 在文件arch/arm/kernel/head_common.S中
  1. 151 .type __lookup_processor_type, %function
  2. 152 __lookup_processor_type:
  3. 153      adr r3, 3f                                     //L184行的运行地址(0x30008000+offset)存到r3中
  4. 154     ldmda r3, {r5 - r7}                             //r5=_proc_info_begin; r6=__proc_info_end; r7=3f; r5,r6,r7都是程序的存储地址
  5. 155     sub r3, r3, r7 @ get offset between virt&phys      // r3=r3-r7=0x30008000
  6. 156     add r5, r5, r3 @ convert virt addresses to         // r5=r5+r3=0x30008000+__proc_info_begin(即r5指向当前内存中第一个proc_info结构体首地址) 
  7. 157     add r6, r6, r3 @ physical address space            // r6=r6+r3=0x30008000+__proc_info_end 
  8. 158 1:  ldmia r5, {r3, r4} @ value, mask                   // r3=cpu_val=0x41009200; r4=cpu_mask=0xff00fff0
  9. 159     and r4, r4, r9 @ mask wanted bits                  // r4=r4&r9 r9是从协处理器中读取出来的cpu_val
  10. 160     teq r3, r4                                         // 将程序中的cpu_val与硬件中的cpu_val相比较看是否相同
  11. 161     beq 2f                                             //相等说明找到了这个结构体,就返回                                      
  12. 162     add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) //不相等查找下一个结构体
  13. 163     cmp r5, r6
  14. 164     blo 1b
  15. 165     mov r5, #0 @ unknown processor
  16. 166 2:  mov pc, lr
  17. 167
  18. 168 /*
    169  * This provides a C-API version of the above function.
    170  */
    171 ENTRY(lookup_processor_type)
    172     stmfd   sp!, {r4 - r7, r9, lr}
    173     mov r9, r0
    174     bl  __lookup_processor_type
    175     mov r0, r5
    176     ldmfd   sp!, {r4 - r7, r9, pc}
    177 
    178 /*
    179  * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
    180  * more information about the __proc_info and __arch_info structures.
    181  */
    182     .long   __proc_info_begin
    183     .long   __proc_info_end
    184 3:  .long   .
    185     .long   __arch_info_begin
    186     .long   __arch_info_end
2.2  struct proc_info_list
  1. arch/arm/kernel/asm-offsets.c
  2. struct proc_info_list {
  3.     unsigned int        cpu_val;
  4.     unsigned int        cpu_mask;
  5.     unsigned long        __cpu_mm_mmu_flags;    /* used by head.*/
  6.     unsigned long        __cpu_io_mmu_flags;    /* used by head.*/
  7.     unsigned long        __cpu_flush;        /* used by head.*/
  8.     const char        *arch_name;
  9.     const char        *elf_name;
  10.     unsigned int        elf_hwcap;
  11.     const char        *cpu_name;
  12.     struct processor    *proc;
  13.     struct cpu_tlb_fns    *tlb;
  14.     struct cpu_user_fns    *user;
  15.     struct cpu_cache_fns    *cache;
  16. };
2.3 从arch/arm/kernel/vmlinux.lds.S可以看出
  1. .init : {            /* Init code and data        */
        __proc_info_begin = .;
            *(.proc.info.init)
        __proc_info_end = .;
    }
  2. 同时 arch/arm/mm/proc-arm920.S中
  3. .align
        .section ".proc.info.init", #alloc, #execinstr
        .type    __arm920_proc_info,#object
  4. __arm920_proc_info:
  5.     .long    0x41009200
  6.     .long    0xff00fff0
  7.     .long PMD_TYPE_SECT | \
  8.         PMD_SECT_BUFFERABLE | \
  9.         PMD_SECT_CACHEABLE | \
  10.         PMD_BIT4 | \
  11.         PMD_SECT_AP_WRITE | \
  12.         PMD_SECT_AP_READ
  13.     .long PMD_TYPE_SECT | \
  14.         PMD_BIT4 | \
  15.         PMD_SECT_AP_WRITE | \
  16.         PMD_SECT_AP_READ
  17.     b    __arm920_setup
  18.     .long    cpu_arch_name
  19.     .long    cpu_elf_name
  20.     .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
  21.     .long    cpu_arm920_name
  22.     .long    arm920_processor_functions
  23.     .long    v4wbi_tlb_fns
  24.     .long    v4wb_user_fns
  25. #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
  26.     .long    arm920_cache_fns
  27. #else
  28.     .long    v4wt_cache_fns
  29. #endif
  30.     .size    __arm920_proc_info, . - __arm920_proc_info
三、__lookup_machine_type:

3.1 在文件arch/arm/kernel/head_common.S中
 182 .long __proc_info_begin
  1. 183 .long __proc_info_end
  2. 184 3: .long .
  3. 185 .long __arch_info_begin
  4. 186 .long __arch_info_end

  5. 199 .type __lookup_machine_type, %function
  6. 200 __lookup_machine_type:
  7. 201     adr r3, 3b                                            // r3=r3-r7=0x30008000;小三还是可以重复利用的
  8. 202     ldmia r3, {r4, r5, r6}                                // r4=3f, r5=arch_info_begin; r6=arch_info_end; arch_info紧跟 proc_info
  9. 203     sub r3, r3, r4 @ get offset between virt&phys         // r3=r3-r4=0x30008000
  10. 204     add r5, r5, r3 @ convert virt addresses to            // r5=r5+r3   
  11. 205     add r6, r6, r3 @ physical address space               // r6=r6+r3     
  12. 206 1:  ldr r3, [r5, #MACHINFO_TYPE] @ get machine type       // r3=struct machine_desc->nr,因machine_nr并没有存在结构体的首位,所以需要加上偏移
  13. 207     teq r3, r1 @ matches loader number?                   // r3是较核中的machine_nr, r1是uboot传入的machine_nr比较两者是否相同
  14. 208     beq 2f @ found
  15. 209     add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc  // 不相同,则查找下一个machine_desc
  16. 210     cmp r5, r6
  17. 211     blo 1b
  18. 212     mov r5, #0 @ unknown machine
  19. 213 2:  mov pc, lr
3.2 从arch/arm/kernel/vmlinux.lds.S可以看出
  .init : {            /* Init code and data        */
  1.      __proc_info_begin = .;
           *(.proc.info.init)
        __proc_info_end = .;
        __arch_info_begin = .;
           *(.arch.info.init)
        __arch_info_end = .;
    }
  2. arch_info紧跟 proc_info

  3. 3.2.1 在arch/arm/mach/arch.h中
  4. struct machine_desc {
        /*
         * Note! The first four elements are used
         * by assembler code in head.S, head-common.S
         */
        unsigned int        nr;        /* architecture number    */
        unsigned int        phys_io;    /* start of physical io    */
        unsigned int        io_pg_offst;    /* byte offset for io 
                             * page tabe entry    */

        const char        *name;        /* architecture name    */
        unsigned long        boot_params;    /* tagged list        */

        unsigned int        video_start;    /* start of video RAM    */
        unsigned int        video_end;    /* end of video RAM    */

        unsigned int        reserve_lp0 :1;    /* never has lp0    */
        unsigned int        reserve_lp1 :1;    /* never has lp1    */
        unsigned int        reserve_lp2 :1;    /* never has lp2    */
        unsigned int        soft_reboot :1;    /* soft reboot        */
        void            (*fixup)(struct machine_desc *,
                         struct tag *, char **,
                         struct meminfo *);
        void            (*map_io)(void);/* IO mapping function    */
        void            (*init_irq)(void);
        struct sys_timer    *timer;        /* system tick timer    */
        void            (*init_machine)(void);
    };

  5. #define MACHINE_START(_type,_name)            \
  6. static const struct machine_desc __mach_desc_##_type    \
  7.  __used                            \
  8.  __attribute__((__section__(".arch.info.init"))) = {    \
  9.     .nr        = MACH_TYPE_##_type,        \
  10.     .name        = _name,

  11. #define MACHINE_END                \
  12. }
3.4 在arch/arm/mach-s3c440/mach-smdk2440.c中
对结构体machine_desc进行初始化
  MACHINE_START(S3C2440, "SMDK2440")
  1.     /* Maintainer: Ben Dooks <ben@fluff.org> */
  2.     .phys_io    = S3C2410_PA_UART,
  3.     .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
  4.     .boot_params    = S3C2410_SDRAM_PA + 0x100,

  5.     .init_irq    = s3c24xx_init_irq,
  6.     .map_io        = smdk2440_map_io,
  7.     .init_machine    = smdk2440_machine_init,
  8.     .timer        = &s3c24xx_timer,
  9. MACHINE_END

四、检查uboot的参数
4.1 在文件arch/arm/kernel/head_common.S中
 238 .type __vet_atags, %function
  1. 239 __vet_atags:
  2. 240 tst r2, #0x3 @ aligned?
  3. 241 bne 1f
  4. 242 
  5. 243 ldr r5, [r2, #0] @ is first tag ATAG_CORE?
  6. 244 subs r5, r5, #ATAG_CORE_SIZE
  7. 245 bne 1f
  8. 246 ldr r5, [r2, #4]
  9. 247 ldr r6, =ATAG_CORE
  10. 248 cmp r5, r6
  11. 249 bne 1f
  12. 250 
  13. 251 mov pc, lr @ atag pointer is ok
  14. 252 
  15. 253 1: mov r2, #0
  16. 254 mov pc, lr
这儿的r2=0x30008000,内核就拷贝到了这个地址,并不是参数链表,所以这个地方的检查 bne 1f, 然后r2=0;

附:
   1.协处理寄存器->arm寄存器
    mrc {} p15, 0, , , {,}
    a. 条件码,忽略时无条件执行
    b. 永远为 0
    c. , 保存结果
    d. , 协处理器中的寄存器 
    d. , 协寄存器的附加信息,当不需要附加信息时,要设为c0
    d. , 附加信息,省略时默为0

2: 指令
2.1 movs指令
    S决定指令操作是否影响CPSR的值
2.2 ldmda指令:
    使用多数据传送指令(LDM 和 STM)来装载和存储多个字的数据从/到内存。 
    LDM/STM 的主要用途是把需要保存的寄存器复制到栈上。如我们以前见到过的 STMFD R13!, {R0-R12, R14}。
    指令格式是:
    xxM{条件}{类型} Rn{!}, <寄存器列表>{^}
    ‘xx’是 LD 表示装载,或 ST 表示存储。
    再加 4 种‘类型’就变成了 8 个指令:
    栈                                             其他
    LDMED    (空递减)                LDMIB     预先增加装载
    LDMFD    (满递减)                 LDMIA     过后增加装载
    LDMEA    (空递增)                 LDMDB     预先减少装载
    LDMFA    ?(满递增)                 LDMDA     过后减少装载
    STMFA    ?(满递增)                 STMIB     预先增加存储
    STMEA   (空递增)                 STMIA     过后增加存储
    STMFD   (满递减)                 STMDB     预先减少存储
    STMED    ?(空递减)                 STMDA     过后减少存储
    关于空递减和满递减等等,理解:
    指针存储往减少方向发展的是“递减”,反之,存储往增加方向发展的是“递增”。
    指针指向下一个存储位置的是“空”,反之,指针指向最后一个存储位置的是“满”。
Linux内核---13.启动分析1之arch/arm/kernel/head.S
[参考文章]

1. arm linux kernel 从入口到start_kernel的代码分析

http://bbs.chinaunix.net/thread-2039668-1-1.html
2. Linux 内核启动分析之"arch/arm/kernel/head.S"
http://blog.sina.com.cn/s/blog_63ac1cef0100vbcb.html
3. linux内核启运过程分析
http://chxxxyg.blog.163.com/blog/static/150281193201072603030285/
4. arm体系结构与编程-杜春雷