当进入linux内核后,arch/arm/kernel/head.S是内核最先执行的一个文件,包括从内核入口ENTRY(stext)到start_kernel之间的初始化代码,下面以我所使用的平台s3c2410为例,说明一下他的汇编代码:
1:      __INIT
2:      .type   stext, %function
3:  ENTRY(stext)
/* 程序状态,禁止FIQ、IRQ,设定SVC模式 */
4:      msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | MODE_SVC @ ensure svc mode
                                                  @ and irqs disabled
    /* 判断CPU类型,查找运行的CPU ID值与Linux编译支持的ID值是否支持 */
5:      bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
6:      movs    r10, r5             @ invalid processor (r5=0)?
    /* 判断如果r10的值为0,则表示函数执行错误,跳转到出错处理,*/
7:      beq __error_p               @ yes, error 'p'
    /* 判断体系类型,查看R1寄存器的Architecture Type值是否支持 */
8:      bl  __lookup_machine_type       @ r5=machinfo
9:      movs    r8, r5              @ invalid machine (r5=0)?
    /* 判断如果r8的值为0,则表示函数执行错误,跳转到出错处理,*/
10:     beq __error_a           @ yes, error 'a'
    /* 创建核心页表 */
11:     bl  __create_page_tables
12:     ldr r13, __switch_data      @ address to jump to after
                                    @ mmu has been enabled
13:     adr lr, __enable_mmu        @ return (PIC) address
14:     add pc, r10, #PROCINFO_INITFUNC
                             
第4行,准备进入SVC工作模式,同时关闭中断(I_BIT)和快速中断(F_BIT)
第5行,查看处理器类型,主要是为了得到处理器的ID以及页表的flags。
第8行,查看一些体系结构的信息。
第11行,建立页表。
第14行,跳转到处理器的初始化函数,其函数地址是从__lookup_processor_type中得到的,需要注意的是第13行,当处理器初始化完成后,会直接跳转到__enable_mmu去执行,
这是由于初始化函数最后的语句是mov pc, lr。
 
函数__lookup_processor_type介绍:
内核中使用了一个结构struct proc_info_list,用来记录处理器相关的信息,该结构定义在
kernel/include/asm-arm/procinfo.h头文件中。
/*
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
struct proc_info_list {
    unsigned int        cpu_val;
    unsigned int        cpu_mask;
    unsigned long       __cpu_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;
};
在arch/arm/mm/proc-arm920.S文件中定义了所有和arm920有关的proc_info_list,我们使用的arm920定义如下:
    .section ".proc.info.init", #alloc, #execinstr
    .type   __arm920_proc_info,#object
__arm920_proc_info:
    .long   0x41009200
    .long   0xff00fff0
.
.
由于.section指示符,上面定义的__arm920_proc_info信息在编译的时候被放到了.proc.info段中,这是由linux的链接脚本文件arch/arm/kernel/vmlinux.lds指定的,参考如下:
SECTIONS
{
    . = TEXTADDR;
    .init : {           /* Init code and data       */
        _stext = .;
        _sinittext = .;
            *(.init.text)
            _einittext = .;
        __proc_info_begin = .;
            *(.proc.info.init)
        __proc_info_end = .;
                .
                .
这里的符号__proc_info_begin指向.proc.info的起始地址,而符号__proc_info_end指向.proc.info的结束地址。后面就会引用这两个符号,来指向.proc.info这个段。
下面来来看看函数的源代码,为了分析方便将函数按行进行编号,其中17-18行就是前面提到的对.proc.info的引用,
第2行将19行的地址放到寄存器r3中,adr是小范围的地址读取伪指令。
第3行将r3所指向的数据区的数据读出到r5,r6,r9,执行结果是
R5=__proc_info_begin,r6=__proc_info_end,r9=第19行的地址
用仿真器查看:
r5=0xc0018664
r6=0xc0018694
r9=0xc0008324(为何是0xc0008324而不是0x30008324???)
第4行算出虚、实地址的偏移,
第5-6行的结果应该是r5指向__proc_info_begin的实地址,r6指向__proc_info_end的实地址。
第7行读取cpu的id,这是一个协处理器指令,将processor ID存储在r9中。
第8行将r5指向的__arm920_proc_info开始的数据读出放到寄存器r3,r4,结果r3=0x41009200 (cpu_val),r4=0xff00fff0 (cpu_mask)。
第9-10行将读出的id和结构中的id进行比较,如果id相同则返回,返回时r9存储
processor ID,如果id不匹配,则将指针r10增加PROC_INFO_SZ (proc_info_list结构的长度,在这等于48),如果r5小于r6指定的地址,也就是
__proc_info_end,则继续循环比较下一个proc_info_list中的id,如第11-14行的代码,如果查找到__proc_info_end,仍未找到一个匹配的id,则将r5清零并返回,如15-16行,也就是说如果函数执行成功则r5指向匹配的proc_info_list结构地址,如果函数返回错误则r5为0。
 
/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 * Returns:
 *  r3, r4, r6 corrupted
 *  r5 = proc_info pointer in physical address space
 *  r9 = cpuid
 */
    .type   __lookup_processor_type, %function
1   __lookup_processor_type:
2       adr r3, 3f
3       ldmda   r3, {r5, r6, r9}
4       sub r3, r3, r9          @ get offset between virt&phys
5       add r5, r5, r3          @ convert virt addresses to
6       add r6, r6, r3          @ physical address space
7       mrc p15, 0, r9, c0, c0      @ get processor id
8   1:  ldmia   r5, {r3, r4}            @ value, mask
9       and r4, r4, r9          @ mask wanted bits
10     teq r3, r4
11      beq 2f
12      add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
13      cmp r5, r6
14      blo 1b
15      mov r5, #0              @ unknown processor
16  2:  mov pc, lr
/*
 * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
 * more information about the __proc_info and __arch_info structures.
 */
17      .long   __proc_info_begin
18      .long   __proc_info_end
19  3:  .long   .
20      .long   __arch_info_begin
21      .long   __arch_info_end
 
函数__lookup_architecture_type介绍:
每个机器(一般指的是某一个电路板)都有自己的特殊结构,如物理内存地址,物理I/O地址,显存起始地址等等,
这个结构为struct machine_desc,定义在asm-arm/mach/arch.h中:
struct machine_desc {
    /*
     * Note! The first five elements are used
     * by assembler code in head-armv.S
     */
    unsigned int        nr;         /* architecture number */
    unsigned int        phys_ram;   /* start of physical ram */
    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);
};
这个结构一般都定义在(以arm平台为例)kernel\arch\arm\mach-xxx\xxx.c中,是用宏来定义的,以s3c2410的开发板为例:
定义在kernel\arch\arm\mach-s3c2410\mach-smdk2410.c文件中,如下所示:
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch to SMDK2410 */
    /* Maintainer: Jonas Dietsche */
    .phys_ram   = S3C2410_SDRAM_PA,
    .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   = smdk2410_init_irq,
    .timer      = &s3c24xx_timer,
MACHINE_END
这些宏也定义在kernel/include/asm-arm/mach/arch.h中,以MACHINE_START为例:
#define MACHINE_START(_type,_name)      \
const struct machine_desc __mach_desc_##_type   \
 __attribute__((__section__(".arch.info.init"))) = {    \
    .nr     = MACH_TYPE_##_type,    \
    .name       = _name,
#define MACHINE_END             \
};
展开之后结构的是:
__mach_desc_SMDK2410= {
 .nr = MACH_TYPE_SMDK2410,
 .name = "SMDK2410",
中间的1行__attribute__((__section__(".arch.info"))) = {说明将这个结构放到指定的段.arch.info中,这和前面的
.proc.info是一个意思,__attribute__((__section__的含义参考GNU手册。后面的宏都是类似的含义,这里就不再一一介绍。
 
下面开始说明源码:
第1行实现r3指向3b的地址,3b如__lookup_processor_type介绍的第19行,
第3行将r3所指向的数据区的数据读出到r4,r5,r6,执行结果是
R5= __arch_info_begin,r6= __arch_info_end,r4=第19行的地址
用仿真器查看:
R5= 0xc0018694
R6= 0xc00186cc
R4= 0xc0008324 (为何是0xc0008324而不是0x30008324???)
第4行算出虚、实地址的偏移,
第5-6行的结果应该是r5指向__arch_info_begin的实地址,r6指向__arch_info_end的实地址。
第7行读取__mach_desc_ SMDK2410结构中的nr参数到r3中,
第8行比较r3和r1中的机器编号是否相同,
r3中的nr值MACH_TYPE_SMDK2410定义在kernel\include\asm-arm\mach-types.h中:
#define MACH_TYPE_SMDK2410             193
r1中的值是由bootloader传递过来的,这在<<linux启动流程分析(1)---bootloader启动内核过程>>中有说明,如果机器编号相同,跳到14行返回。如果不同则将地址指针增加,在跳到7行继续查找,见10--12行的代码,如果检索完所有的machine_desc仍然没有找到则将r5清零并返回。
/*
 * Lookup machine architecture in the linker-build list of architectures.
 * Note that we can't use the absolute addresses for the __arch_info
 * lists since we aren't running with the MMU on (and therefore, we are
 * not in the correct address space).  We have to calculate the offset.
 *
 *  r1 = machine architecture number
 * Returns:
 *  r3, r4, r6 corrupted
 *  r5 = mach_info pointer in physical address space
 */
    .type   __lookup_machine_type, %function
1:  __lookup_machine_type:
2:      adr r3, 3b
3:      ldmia   r3, {r4, r5, r6}
4:      sub r3, r3, r4          @ get offset between virt&phys
5:      add r5, r5, r3          @ convert virt addresses to
6:      add r6, r6, r3          @ physical address space
7:  1:  ldr r3, [r5, #MACHINFO_TYPE]    @ get machine type
8:      teq r3, r1              @ matches loader number?
9:      beq 2f              @ found
10:     add r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
11:     cmp r5, r6
12:     blo 1b
13:     mov r5, #0              @ unknown machine
14  2:  mov pc, lr
 
函数__create_page_tables介绍:
假设内核起始物理地址是0x30008000,虚拟地址是0xC0008000,下面的代码是建立内核起始处4MB空间的映射,
采用了一级映射方式,即段式(section)映射方式,每段映射范围为1MB空间。于是需要建立4个表项,实现:
虚拟地址0xC0000000~0xC0300000,映射到物理地址0x30000000~0x30300000。
     .macro  pgtbl, reg, rambase
     adr \reg, stext
     sub \reg, \reg, #0x4000    
     .endm
    
     .macro  krnladr, rd, pgtable, rambase
     bic \rd, \pgtable, #0x000ff000
     .endm
    
/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8  = machinfo
 * r9  = cpuid
 * r10 = procinfo
 *
 * Returns:
 *  r0, r3, r5, r6, r7 corrupted
 *  r4 = physical page table address
 */
    .type   __create_page_tables, %function
__create_page_tables:
    ldr r5, [r8, #MACHINFO_PHYSRAM] @ physram   r5=0x30000000
    pgtbl   r4, r5              @ page table address    r4=0x30004000
    /*
     * Clear the 16K level 1 swapper page table
     */
    mov r0, r4
    mov r3, #0
    add r6, r0, #0x4000
1:  str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    teq r0, r6
    bne 1b
    ldr r7, [r10, #PROCINFO_MMUFLAGS]   @ mmuflags         //r7=0x00000c1e
    /*
     *  r7用于设置第一级表描述符之用:
     *      AP          :11(读/写权限)
     *      域          :0000
     *      C           : 1(高速缓存)
     *      B           : 1 (缓冲)
     *      bit[1 0]    :10(标识此为节描述符)
    */
    /*
     * Create identity mapping for first MB of kernel to
     * cater for the MMU enable.  This identity mapping
     * will be removed by paging_init().  We use our current program
     * counter to determine corresponding section base address.
     *
     * 为以后MMU的开启准备好内存头1M空间的转换表,
     * 采用平板地址映射模式(表索引==节基址)
     */
    mov r6, pc, lsr #20         @ start of kernel section      //r6=0x00000300
    orr r3, r7, r6, lsl #20     @ flags + kernel base          //r3=0x30000c1e
    str r3, [r4, r6, lsl #2]    @ identity mapping  //[0x30004c00]=0x30000c1e
    /*  结果:
     *  虚拟地址        物理地址
     *  0x300000000     0x30000000。
     *
     * Now setup the pagetables for our kernel direct
     * mapped region.  We round TEXTADDR down to the
     * nearest megabyte boundary.  It is assumed that
     * the kernel fits within 4 contigous 1MB sections.
     *
     * 为内核占用的头4M地址准备好转换表(在这采用的就不是平板地址映射模式了)
     */
    add r0, r4,  #(TEXTADDR & 0xff000000) >> 18                 //ro=0x30007000
    str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!       @ KERNEL + 0MB
    add r3, r3, #1 << 20                                        //r3=0x30100c1e
    str r3, [r0, #4]!                                   @ KERNEL + 1MB
    add r3, r3, #1 << 20                                        //r3=0x30200c1e
    str r3, [r0, #4]!                                   @ KERNEL + 2MB
    add r3, r3, #1 << 20                                        //0x30300c1e
    str r3, [r0, #4]                                    @ KERNEL + 3MB
    /*
     *  结果:
     *  虚拟地址        物理地址
        0xc0000000      0x30000000
        0xc0100000      0x30100000
        0xc0200000      0x30200000
        0xc0300000      0x30300000
      */
    /*
     * Then map first 1MB of ram in case it contains our boot params.
     *
     *  映射sdram的头1M以防boot params需要(不是已经映射过了吗?)
     */
    add r0, r4, #VIRT_OFFSET >> 18                     //r0=0x30007000
    orr r6, r5, r7                                     //r6=0x30000c1e
    str r6, [r0]                                       //[0x30007000]=0x3000c1e
#ifdef CONFIG_XIP_KERNEL
    /*
     * Map some ram to cover our .data and .bss areas.
     * Mapping 3MB should be plenty.
     */
    sub r3, r4, r5
    mov r3, r3, lsr #20
    add r0, r0, r3, lsl #2
    add r6, r6, r3, lsl #20
    str r6, [r0], #4
    add r6, r6, #(1 << 20)
    str r6, [r0], #4
    add r6, r6, #(1 << 20)
    str r6, [r0]
#endif
#ifdef CONFIG_DEBUG_LL
    bic r7, r7, #0x0c           @ turn off cacheable
                                @ and bufferable bits
    /*
     * Map in IO space for serial debugging.
     * This allows debug messages to be output
     * via a serial console before paging_init.
     */
    ldr r3, [r8, #MACHINFO_PGOFFIO]
    add r0, r4, r3
    rsb r3, r3, #0x4000         @ PTRS_PER_PGD*sizeof(long)
    cmp r3, #0x0800         @ limit to 512MB
    movhi   r3, #0x0800
    add r6, r0, r3
    ldr r3, [r8, #MACHINFO_PHYSIO]
    orr r3, r3, r7
1:  str r3, [r0], #4
    add r3, r3, #1 << 20
    teq r0, r6
    bne 1b
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
    /*
     * If we're using the NetWinder, we need to map in
     * the 16550-type serial port for the debug messages
     */
    teq r1, #MACH_TYPE_NETWINDER
    teqne   r1, #MACH_TYPE_CATS
    bne 1f
    add r0, r4, #0xff000000 >> 18
    orr r3, r7, #0x7c000000
    str r3, [r0]
1:
#endif
#ifdef CONFIG_ARCH_RPC
    /*
     * Map in screen at 0x02000000 & SCREEN2_BASE
     * Similar reasons here - for debug.  This is
     * only for Acorn RiscPC architectures.
     */
    add r0, r4, #0x02000000 >> 18
    orr r3, r7, #0x02000000
    str r3, [r0]
    add r0, r4, #0xd8000000 >> 18
    str r3, [r0]
#endif
#endif
    mov pc, lr
 
函数__mmap_switched介绍:
     
/*
 * The following fragment of code is executed with the MMU on, and uses
 * absolute addresses; this is not position independent.
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r9  = processor ID
 */
1:     .type   __mmap_switched, %function
2: __mmap_switched:
3:     adr r3, __switch_data + 4
4:     ldmia   r3!, {r4, r5, r6, r7}
5:     cmp r4, r5                      @ Copy data segment if needed
6: 1:  cmpne   r5, r6
7:     ldrne   fp, [r4], #4
8:     strne   fp, [r5], #4
9:     bne 1b
10:    mov fp, #0                      @ Clear BSS (and zero fp)
11:1:  cmp r6, r7
12:    strcc   fp, [r6],#4
13:    bcc 1b
14:    ldmia   r3, {r4, r5, r6, sp}
15:    str r9, [r4]                    @ Save processor ID
16:    str r1, [r5]                    @ Save machine type
17:    bic r4, r0, #CR_A               @ Clear 'A' bit
18:    stmia   r6, {r0, r4}            @ Save control register values
19:    b   start_kernel
20:    .type   __switch_data, %object
21:__switch_data:
22:    .long   __mmap_switched
23:    .long   __data_loc                          @ r4
24:    .long   __data_start                        @ r5
25:    .long   __bss_start                         @ r6
26:    .long   _end                                @ r7
27:    .long   processor_id                        @ r4
28:    .long   __machine_arch_type                 @ r5
29:    .long   cr_alignment                        @ r6
30:    .long   init_thread_union + THREAD_START_SP @ sp
程序的4行执行完成之后的结果是r4=__data_loc,r5=__data_start,r6=__bss_start,r7=_end,第10-13行将__bss_start到_end清零,定义在vmlinux.lds文件中,如下:
  .bss : {                                             
        __bss_start = .;    /* BSS */       
       *(.bss)
       *(COMMON)
  _end = . ;
  }  
 
第15-16行分别将处理器类型和机器类型存储到变量processor_id和__machine_arch_type中,这些变量以后会在start_kernel->setup_arch中使用,来得到当前处理器的struct proc_info_list结构和当前系统的machine_desc结构的数据。
第17-18将processor control register保存到cr_alignment中,19行跳转到init/main.c中的start_kernel进入内核启动的第二阶段。