Linux内核源码分析(二)--启动汇编上篇

来源:互联网 发布:linux磁盘分区挂载 编辑:程序博客网 时间:2024/06/05 05:49

在跳入c函数start_kernel之前有一段汇编代码,从内核链接脚本可以看出内核的入口在stext代码段,这里正是汇编代码的入口。在arch/arm/kernel/目录下有两个相关的汇编源文件分别是head.S和head-common.S,stext代码包含在head.S中:


 ENTRY(stext)

 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE
 mrc p15, 0, r9, c0, c0
 bl __lookup_processor_type
 movs r10, r5
 beq __error_p
 bl __lookup_machine_type
 movs r8, r5
 beq __error_a
 bl __create_page_tables
 ldr r13, __switch_data
 adr lr, __enable_mmu
 add pc, r10, #PROCINFO_INITFUNC

先说一下大概流程,再说详细的分析过程。

(1) 设置cpu到管理模式并禁用中断;

(2) 根据cp15中的主标识查找处理器相关信息,将对应proc_info_list结构地址保存在r10寄存器中;

(3) 根据bootloader传过来的machine编号,查找machine相关信息,将对应machine_desc结构地址保存在r8寄存器中;

(4) 根据前面查找到的信息初始化页表;

(5) 开启mmu进入虚拟世界;

(6) 调用对应的proc_info_list上的__cpu_flush钩子;

(7) 从__switch_data函数转入start_kernel开启万里长征。


下面进行详细分析。

(1)  msr指令可以将立即数或寄存器值设置到状态寄存器中,指令的编码中有4位的掩码,每1位对应着状态寄存器中的8个位,也就是说32位的状态寄存器被平均分成4个域,cpsr_c正对应着控制域,不了解的话,可能会误以为是伪指令。

      msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE

上面指令的立即数定义可以参看include/asm-arm/ptrace.h头文件。

#define SVC_MODE 0x00000013

#define PSR_F_BIT 0x00000040

#define PSR_I_BIT 0x00000080


(2)  arm的协处理器cp15中的c0寄存器是个只读寄存器,可以获取arm相关的标识码。mrc指令可以将cp15中的寄存器值搬到arm通用寄存器中,通过指令mrc指令的第二个操作数可以指定获取的标识码类型,第二个操作数缺省值为0,0代表的是获取主标识。

mrc p15, 0, r9, c0, c0

对于arm7之后的处理器,主标识格式如下。 

30             24

23             20

19                     16

15              4

                   0

由生产商确定

产品子编号

ARM 体系版本号

产品主编号

处理器版本号

各部分编码的含义如下所示。

                   说     

位 [3: 0]

生产商定义的处理器版本号

位 [15:4]

生产商定义的产品主编号

其中最高 位即位 [15:12] 可能的取值为0x0~0x7 但不能是 0x0 或 0x7

因为:

0x0表示 ARM7之前的处理器

0x7 表示ARM7处理器

位 [19:16]

ARM 体系的版本号,可能的取值如 下:

0x1   ARM 体系版本 4

0x2   ARM 体系版本 4T

0x3   ARM 体系版本 5

0x4   ARM 体系版本 5T

0x5   ARM 体系版本 5TE

其他   由 ARM 公司保留将来使用

位 [23:20]

生产商定义的产品子编号。当产品主编号相同时,使用子编号来区分不同的产品子类,如产品中不 同的高速缓存的大小等

位 [31:24]

生产厂商的编号,现在已经定义的有以下值:

0x41  =A  ARM 公司

0x44  =D  DigitalEquipment 公司

0x69  =I   intel 公司

如此r9中的信息就如上面的表格所示。


(3)  __lookup_processor_type函数定义在head-common.S文件中。

__lookup_processor_type:
 adr r3, 3f
 ldmda r3, {r5 - r7}
 sub r3, r3, r7
 add r5, r5, r3
 add r6, r6, r3
1: ldmia r5, {r3, r4}
 and r4, r4, r9
 teq r3, r4
 beq 2f
 add r5, r5, #PROC_INFO_SZ
 cmp r5, r6
 blo 1b
 mov r5, #0
2: mov pc, lr

 .long __proc_info_begin
 .long __proc_info_end

3: .long .

adr r3, 3f 执行后r3中保存的是标号3的物理地址;

ldmda r3, {r5-r7} 执行后r7中是标号3的编译链接地址,r6中存的是.proc.info.init段的链接结束地址,r5中存的是.proc.info.init段的链接起始地址;

sub r3, r3, r7 执行后r3中存放的是物理地址与链接地址的差值;

add r5, r5, r3 执行后r5中存放的是.proc.info.init段的物理起始地址;

add r6, r6, r3执行后r6中存放的是.proc.info.init段的物理结束地址;

因为内核链接的地址不一定是内核的加载地址,我们又是与位置无关的代码,所以才有了上面的操作。


从标号1出开始遍历.proc.info.init段,这个段里放的是什么样的数据,可以在arch/arm/kernel目录下grep一下段名。因为我们用s3c2440的cpu来分析,所以我们只关注arch/arm/mm/proc-arm920.S文件,其中有如下的数据结构。

__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

通过分析标号1处的代码可以看到结构的第一个成员是处理器标识,第二个成员是处理器标识掩码。在前面我们分析过r9寄存器中存放的是处理器主标识,这里将掩码与主标识做位与运算再和结构中的标识对比,若相等则找到对应处理器的数据结构,则r5中存放的便是此数据结构的地址。

在include/asm-arm/procinfo.h中可以看到此结构的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;
};

而PROC_INFO_SZ这个宏也在arch/arm/kernel/asm-offsets.c中如下定义,并转成汇编中间文件供外部引用。

DEFINE(PROC_INFO_SZ,          sizeof(struct proc_info_list));

这个结构会在后面被用到,这里该从__lookup_processor_type函数返回了,r5内容会被拷到r10中,如果r5是0表示没有找到则跳到__error_p函数打印错误信息,最后进入死循环。


(4)  一路向下该分析__lookup_machine_type函数了,这个函数也定义在head-common.S文件中。

3: .long .
 .long __arch_info_begin
 .long __arch_info_end

__lookup_machine_type:
 adr r3, 3b
 ldmia r3, {r4, r5, r6}
 sub r3, r3, r4
 add r5, r5, r3
 add r6, r6, r3
1: ldr r3, [r5, #MACHINFO_TYPE]
 teq r3, r1
 beq 2f
 add r5, r5, #SIZEOF_MACHINE_DESC
 cmp r5, r6
 blo 1b
 mov r5, #0
2: mov pc, lr


与上面的函数很像,不管3721先在include目录下grep一把.arch.info.init,发现在include/asm-arm/mach/arch.h头文件中有这样的一个定义。

#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                             \
};

看来用MACHINE_START包起来的数据结构都会放在这个段啊,想必看过板级代码的对这个宏都不陌生吧?原来被包的就是struct machine_desc结构啊。

struct machine_desc {
        /*
         * Note! The first four elements are used
         * by assembler code in head-armv.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);
};

在include/asm-arm/asm-offsets.h文件中可以看到MACHINFO_TYPE被定义为0,所以__look_machine_type匹配的是machine_desc结构的nr成员即machine编号,只不过前面一直没有操作r1寄存器的指令,这里突然和r1寄存器比较就有点懵了,先冷静一下,我们倒着分析看下,顶多是在bootloader中给r1赋值的。
从MACHINE_START宏可以看到我们的nr会被初始化为MACH_TYPE_S3C2440,在include/asm-arm/mach-types.h中其被定义为362,我们可以带着这些信息到uboot代码中搜索一下,我用的是1.1.6的uboot,在lib_arm/armlinux.c文件中可以看到往内核跳的时候是像下面这样传参的。

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);


好了,真相大白不用懵逼了。现在可以从__lookup_machine_type函数返回了。

前面忙了一通,就拿到两个信息。一个是proc_info_list结构的地址,保存在r10中;另一个是machine_desc结构的地址,保存在r8中。

一路向下便来到了__create_page_tables函数,写的有点累了,放在下篇中分析。


阅读全文
0 0