您的位置:首页 > 运维架构 > Linux

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

2016-07-02 14:51 681 查看
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 : {

    _stext = .;

    _sinittext = .;

    *(.text.head)

}

1.2 程序的起始在 arch/arm/kernel/head.S

 79 ENTRY(stext)

 80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE
@ ensure svc mode

 81     @ and irqs disabled

 82     mrc p15, 0, r9, c0, c0
@ get processor id

 83     bl __lookup_processor_type @ r5=procinfo r9=cpuid

 84     movs r10, r5 @ invalid processor (r5=0)?

 85     beq __error_p @ yes, error 'p'

 86     bl __lookup_machine_type @ r5=machinfo

 87     movs r8, r5 @ invalid machine (r5=0)?

 88     beq __error_a @ yes, error 'a'

 89     bl __vet_atags

 90     bl __create_page_tables

 91

 92 /*

 93 * The following calls CPU specific code in a position independent

 94 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of

 95 * xxx_proc_info structure selected by __lookup_machine_type

 96 * above. On return, the CPU will be ready for the MMU to be

 97 * turned on, and r0 will hold the CPU control register value.

 98 */

 99     ldr r13, __switch_data @ address to jump to after

100     @ mmu has been enabled

101     adr lr, __enable_mmu @ return (PIC) address

102     add pc, r10, #PROCINFO_INITFUNC

80行:



将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中

151 .type __lookup_processor_type, %function

152 __lookup_processor_type:

153      adr r3, 3f                                     //L184行的运行地址(0x30008000+offset)存到r3中

154     ldmda r3, {r5 - r7}                            
//r5=_proc_info_begin; r6=__proc_info_end; r7=3f; r5,r6,r7都是程序的存储地址

155     sub r3, r3, r7 @ get offset between virt&phys      //
r3=r3-r7=0x30008000

156     add r5, r5, r3 @ convert virt addresses to         //
r5=r5+r3=0x30008000+__proc_info_begin(即r5指向当前内存中第一个proc_info结构体首地址) 

157     add r6, r6, r3 @ physical address space            //
r6=r6+r3=0x30008000+__proc_info_end 

158 1:  ldmia r5, {r3, r4} @ value, mask                  
// r3=cpu_val=0x41009200; r4=cpu_mask=0xff00fff0

159     and r4, r4, r9 @ mask wanted
bits                  // r4=r4&r9 r9是从协处理器中读取出来的cpu_val

160     teq r3, r4                                         // 将程序中的cpu_val与硬件中的cpu_val相比较看是否相同

161     beq 2f                                             //相等说明找到了这个结构体,就返回                                   
  

162     add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) //不相等查找下一个结构体

163     cmp r5, r6

164     blo 1b

165     mov r5, #0 @ unknown processor

166 2:  mov pc, lr

167

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

arch/arm/kernel/asm-offsets.c

struct proc_info_list {

    unsigned int        cpu_val;

    unsigned int        cpu_mask;

    unsigned long        __cpu_mm_mmu_flags;    /* used by head.S */

    unsigned long        __cpu_io_mmu_flags;    /* used by head.S */

    unsigned long        __cpu_flush;        /* used by head.S */

    const char        *arch_name;

    const char        *elf_name;

    unsigned int        elf_hwcap;

    const char        *cpu_name;

    struct processor    *proc;

    struct cpu_tlb_fns    *tlb;

    struct cpu_user_fns    *user;

    struct cpu_cache_fns    *cache;

};

2.3 从arch/arm/kernel/vmlinux.lds.S可以看出

.init : {            /* Init code and data        */

    __proc_info_begin = .;

        *(.proc.info.init)

    __proc_info_end = .;

}

同时 arch/arm/mm/proc-arm920.S中

.align

    .section ".proc.info.init", #alloc, #execinstr
    .type    __arm920_proc_info,#object

__arm920_proc_info:

    .long    0x41009200

    .long    0xff00fff0

    .long PMD_TYPE_SECT | \

        PMD_SECT_BUFFERABLE | \

        PMD_SECT_CACHEABLE | \

        PMD_BIT4 | \

        PMD_SECT_AP_WRITE | \

        PMD_SECT_AP_READ

    .long PMD_TYPE_SECT | \

        PMD_BIT4 | \

        PMD_SECT_AP_WRITE | \

        PMD_SECT_AP_READ

    b    __arm920_setup

    .long    cpu_arch_name

    .long    cpu_elf_name

    .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

    .long    cpu_arm920_name

    .long    arm920_processor_functions

    .long    v4wbi_tlb_fns

    .long    v4wb_user_fns

#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH

    .long    arm920_cache_fns

#else

    .long    v4wt_cache_fns

#endif

    .size    __arm920_proc_info, . - __arm920_proc_info

三、__lookup_machine_type:

3.1 在文件arch/arm/kernel/head_common.S中

 182 .long __proc_info_begin

183 .long __proc_info_end

184 3: .long .

185 .long __arch_info_begin

186 .long __arch_info_end

199 .type __lookup_machine_type, %function

200 __lookup_machine_type:

201     adr r3, 3b                                            // r3=r3-r7=0x30008000;小三还是可以重复利用的

202     ldmia r3, {r4, r5, r6}                                //
r4=3f, r5=arch_info_begin; r6=arch_info_end; arch_info紧跟 proc_info

203     sub r3, r3, r4 @ get offset between virt&phys         //
r3=r3-r4=0x30008000

204     add r5, r5, r3 @ convert virt addresses to            //
r5=r5+r3   

205     add r6, r6, r3 @ physical address space               //
r6=r6+r3     

206 1:  ldr r3, [r5, #MACHINFO_TYPE] @
get machine type       // r3=struct machine_desc->nr,因machine_nr并没有存在结构体的首位,所以需要加上偏移

207     teq r3, r1 @ matches loader number?                   // r3是较内核中的machine_nr, r1是uboot传入的machine_nr,比较两者是否相同

208     beq 2f @ found

209     add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc  //
不相同,则查找下一个machine_desc

210     cmp r5, r6

211     blo 1b

212     mov r5, #0 @ unknown machine

213 2:  mov pc, lr

3.2 从arch/arm/kernel/vmlinux.lds.S可以看出

  .init : {            /* Init code and data        */

     __proc_info_begin = .;

       *(.proc.info.init)

    __proc_info_end = .;

    __arch_info_begin = .;

       *(.arch.info.init)

    __arch_info_end = .;

}

arch_info紧跟 proc_info

3.2.1 在arch/arm/mach/arch.h中

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);

};

#define MACHINE_START(_type,_name)            \

static const struct machine_desc __mach_desc_##_type    \

 __used                            \

 __attribute__((__section__(".arch.info.init"))) = {    \

    .nr        = MACH_TYPE_##_type,        \

    .name        = _name,

#define MACHINE_END                \

}

3.4 在arch/arm/mach-s3c440/mach-smdk2440.c中
对结构体machine_desc进行初始化

  MACHINE_START(S3C2440, "SMDK2440")

    /* Maintainer: Ben Dooks <ben@fluff.org> */

    .phys_io    = S3C2410_PA_UART,

    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

    .boot_params    = S3C2410_SDRAM_PA + 0x100,

    .init_irq    = s3c24xx_init_irq,

    .map_io        = smdk2440_map_io,

    .init_machine    = smdk2440_machine_init,

    .timer        = &s3c24xx_timer,

MACHINE_END

四、检查uboot的参数
4.1 在文件arch/arm/kernel/head_common.S中

 238 .type __vet_atags, %function

239 __vet_atags:

240 tst r2, #0x3 @ aligned?

241 bne 1f

242 

243 ldr r5, [r2, #0] @
is first tag ATAG_CORE?

244 subs r5, r5, #ATAG_CORE_SIZE

245 bne 1f

246 ldr r5, [r2, #4]

247 ldr r6, =ATAG_CORE

248 cmp r5, r6

249 bne 1f

250 

251 mov pc, lr @ atag pointer is ok

252 

253 1: mov r2, #0

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     过后减少存储
    关于空递减和满递减等等,理解:
    指针存储往减少方向发展的是“递减”,反之,存储往增加方向发展的是“递增”。
    指针指向下一个存储位置的是“空”,反之,指针指向最后一个存储位置的是“满”。



[参考文章]

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体系结构与编程-杜春雷

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: