一、概述本文针对arm linux的启动过程。以后陆续会更新start_kernel()中每个函数做的事情,这样就清楚linux启动过程中都做了哪些工作。本文及以后的文章代码均采用目前linux最新版本V3.17.为了启动ARM Linux,你需要在内核之前运行一个引导程序(Boot loader).Boot loader的作用是初始化各种设备,调用Linux内核并给内核传递相关的消息。依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。1、Stage1start.S代码结构 u-boot的stage1代码通常放在start.S文件中,用汇编语言写成,其主要代码部分如下(1)定义入口:该工作通过修改连接器脚本来完成。(2)设置异常向量(Exception Vector)。(3)设置CPU的速度、时钟频率及终端控制寄存器。(4)初始化内存控制器。(5)将ROM中的程序(即uboot的stage2的代码)复制到RAM中。(6)初始化堆栈。(7)转到RAM中执行stage2,该工作可使用指令ldr pc来完成。2、Stage2 C语言代码部分 lib_arm/board.c中的start arm boot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数只要完成如下操作:(1)调用一系列的初始化函数。(2)初始化Flash设备。(3)初始化系统内存分配函数。(4)如果目标系统拥有NAND设备,则初始化NAND设备。(5)如果目标系统有显示设备,则初始化该类设备。(6)初始化相关网络设备,填写IP、MAC地址等。(7)将kernl和跟文件系统映射从flash读到ram中。(8)设定内核启动参数和调用内核。二、Linux内核入口Linux 非压缩内核的入口位于文件/arch/arm/kernel/head.S 中的stext段。该段的基地址就是压缩内核解压后的跳转地址。如果系统中加载的内核是非压缩的 Image,那么bootloader将内核从 Flash中拷贝到 RAM 后将直接跳到该地址处,从而启动 Linux 内核。stext函数定义在arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.ENTRY(stext)    ARM_BE8(setend be ) //设置 CPSR 中的端标记位,大端存储。    /*    分析如下宏定义可知,内核在GCC版本大于4.0后,可通过配置宏CONFIG_THUMB2_KERNEL来选择编译成THUMB指令集内核或ARM指令集内核。在编译成ARM指令集内核时,THUMB宏定义的代码行是不可见的。同理编译成THUMB指令集内核时,ARM宏定义的代码行无效。BSYM宏是为在THUMB指令集时访问标号处理而定义的宏。#define BSYM(sym) sym。所以在arm指令集编译时,下边THUMB代码是不可见的。    */         THUMB( adr r9, BSYM(1f) ) //Kernel is always entered in ARM.    THUMB( bx r9 ) //If this is a Thumb-2 kernel,    THUMB( .thumb ) //switch to Thumb now.    THUMB(1: ) #ifdef CONFIG_ARM_VIRT_EXT    bl __hyp_stub_install#endif    //确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭    safe_svcmode_maskall r9        //从arm协处理器里面读到CPU ID存储到r9,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。    mrc p15, 0, r9, c0, c0    //通过从__lookup_processor_type_data中获取处理器信息位置,通过轮询查找该内核是否支持该本处理器。其中r9是通过processor id。    bl __lookup_processor_type    //r5保存处理器信息,若为空则出错__error_p    movs r10, r5    THUMB( it eq )                 beq __error_p    #ifdef CONFIG_ARM_LPAE    mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0    and r3, r3, #0xf @ extract VMSA support    cmp r3, #5 @ long-descriptor translation table format?    THUMB( it lo ) @ force fixup-able long branch encoding    blo __error_lpae @ only classic page table format#endif    #ifndef CONFIG_XIP_KERNEL    adr r3, 2f    ldmia r3, {r4, r8}    sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)    add r8, r8, r4 @ PHYS_OFFSET#else    ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case#endif            /*    * r1 = machine no, r2 = atags or dtb,    * r8 = phys_offset, r9 = cpuid, r10 = procinfo    */    //检查atags parameter的有效性    bl __vet_atags#ifdef CONFIG_SMP_ON_UP    bl __fixup_smp#endif#ifdef CONFIG_ARM_PATCH_PHYS_VIRT    bl __fixup_pv_table#endif    //创建临时页表,它所要做的工作就是将RAM基地址开始的4M空间的物理地址映射到0xC0000000开始的虚拟地址处    bl __create_page_tables            //将__mmap_switched这个symbol的链接地址放在sp里面    ldr r13, =__mmap_switched //address to jump to after        //把标号1处地址赋给lr //mmu has been enabled    adr lr, BSYM(1f) //return (PIC) address    mov r8, r4 //set TTBR1 to swapper_pg_dir    ARM( add pc, r10, #PROCINFO_INITFUNC )    THUMB( add r12, r10, #PROCINFO_INITFUNC )    THUMB( mov pc, r12 )1:    b __enable_mmuENDPROC(stext)//从__lookup_processor_type_data中获取处理器信息位置__lookup_processor_type:    adr r3, __lookup_processor_type_data    //R3指向类型数据指针    ////读取R3指向地址三个WORD数据到R4,R5,R6,R4=标号3处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end。    ldmia r3, {r4 - r6}        sub r3, r3, r4 //物理地址-虚拟地址get offset between virt&phys    add r5, r5, r3 //将R5的虚拟地址转换成物理地址,R5为类型数据起始地址    add r6, r6, r3 //将R6的虚拟地址转换成物理地址,R6为类型数据结束地址1:    ldmia r5, {r3, r4}    //从R5指向的地址读取两个WORD到R3,R4    and r4, r4, r9        //获取判断位    teq r3, r4            //判断本处理器类型是否已找到    beq 2f                //若已找到,跳转到2:执行    add r5, r5, #PROC_INFO_SZ    //若未找到,R5指针增到一个类型数据长度sizeof(proc_info_list)    cmp r5, r6            //是否已查找完所有处理器类型    blo 1b                //未查找完返回1标号处继续循环    mov r5, #0            //若未找到本处理器类型信息,返回值R5设置为NULL2:    mov pc, lrENDPROC(__lookup_processor_type)//__lookup_processor_type_data数据结构定义位于文件\linux-xlnx\arch\arm\kernel\head-common.S,参见汇编代码可知,该数据结构有三个数据,数据内容参见代码注释。    .align 2    .type __lookup_processor_type_data, %object__lookup_processor_type_data:    .long .                    //当前内存地址(此处为虚拟地址)    .long __proc_info_begin    //处理器类型信息起始地址    .long __proc_info_end        //处理器类型信息结束地址    .size __lookup_processor_type_data, . - __lookup_processor_type_data//_proc_info_begin,__proc_info_end定义位于\linux-xlnx\arch\arm\kernel\vmlinux.lds.S,它用于存储.proc.info.init段的起始地址及结束地址。    VMLINUX_SYMBOL(__proc_info_begin) = .; \    *(.proc.info.init) \    VMLINUX_SYMBOL(__proc_info_end) = .;//.proc.info.init段定义位于\linux-xlnx\arch\arm\mm\proc-v7.S,该文件定义了本内核版本支持的ARM v7架构下处理器类型等。//检查bootloader传入的参数列表atags的合法性__vet_atags:    tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐    bne 1f        ldr r5, [r2, #0] //获取第一个tag结构的size#ifdef CONFIG_OF_FLATTREE    ldr r6, =OF_DT_MAGIC //is it a DTB?    cmp r5, r6    beq 2f#endif    //#define??ATAG_CORE_SIZE??((2*4??+??3*4)??>>??2)??判断该tag的长度是否合法    cmp r5, #ATAG_CORE_SIZE    cmpne r5, #ATAG_CORE_SIZE_EMPTY    bne 1f    ldr r5, [r2, #4]    //??获取第一个tag结构体的标记    ldr r6, =ATAG_CORE    //判断第一个tag结构体的标记是不是ATAG_CORE    cmp r5, r6    bne 1f    2:    mov pc, lr //正常退出1:    mov r2, #0    mov pc, lr        //参数链表不正确ENDPROC(__vet_atags)__mmap_switched:    adr r3, __mmap_switched_data             ldmia {r4, r5, r6, r7}    cmp r4, r5 @ Copy data segment if needed    1: cmpne r5, r6    ldrne fp, [r4], #4    strne fp, [r5], #4    bne 1b        mov fp, #0 @ Clear BSS (and zero fp)    1: cmp r6, r7    strcc fp, [r6],#4    bcc 1b        ARM( ldmia r3, {r4, r5, r6, r7, sp})    THUMB( ldmia r3, {r4, r5, r6, r7} )    THUMB( ldr sp, [r3, #16] )    str r9, [r4] @ Save processor ID    str r1, [r5] @ Save machine type    str r2, [r6] @ Save atags pointer    cmp r7, #0    bicne r4, r0, #CR_A @ Clear 'A' bit    stmneia r7, {r0, r4} @ Save control register values    b start_kernel        //跳转到start_kernel继续初始化ENDPROC(__mmap_switched)二、start kernel初始化asmlinkage __visible void __init start_kernel(void){    char * command_line;    //地址指针,指向内核启动参数在内存中的位置(虚拟地址)    extern const struct kernel_param __start___param[], __stop___param[];            //对记录各锁之间依赖关系的结构体进行初始化???!!!    lockdep_init();    //指定当前的cpu的逻辑号,这个函数对应于对称多处理器的设置,当系统中只有一个cpu的情况,此函数为空    smp_setup_processor_id();    //用于内核的对象调试。依赖配置CONFIG_DEBUG_OBJECTS    debug_objects_early_init();            //初始化防止栈溢出攻击保护的堆栈。    boot_init_stack_canary();        //一组进程的行为控制,数据结构和其中链表的初始化    cgroup_init_early();        //关闭本地中断,因为尚未对中断代码和向量表、中断处理器进行初始化,所以必须关中断    local_irq_disable();    //用来协助调试,执行当前初始启动代码时,对无效的中断激活事件输出警告    early_boot_irqs_disabled = true;        //激活当前CPU    boot_cpu_init();    //如果有高端内存,则初始化高端内存page_address_htable数组    page_address_init();    pr_notice("%s", linux_banner);    //架构初始化相关    setup_arch(&command_line);    //初始化init_mm结构体,owner指向init_task,即init_mm->owner = init_task;    mm_init_owner(&init_mm, &init_task);    //设置init_mm的cpu_mask    mm_init_cpumask(&init_mm);    //对command_line进行备份与保存    setup_command_line(command_line);    //设置nr_cpu_ids变量,即cpu id最大值    setup_nr_cpu_ids();    //用于为每个cpu的per-cpu变量副本分配空间,注意这时alloc内存分配器还没建立起来,通过memblock为初始化期间的这些变量副本分配物理空间。    setup_per_cpu_areas();//参考per-cpu.c文档    //设置arm的TPIDRPRW寄存器    smp_prepare_boot_cpu();        //因为前边在setup_arch中已将节点描述符、管理区描述符和页描述符做了初始化,这里进一步的建立系统内存页区(zone)链表        build_all_zonelists(NULL, NULL);    //会调用hotcpu_notifier函数。在编译选项CONFIG_HOTPLUG_CPU起作用时,这个函数才有效。这个编译选项就热插拔技术,而且是CPU的热插拔    page_alloc_init();            pr_notice("Kernel command line: %s\n", boot_command_line);    //再次处理early_param定义的参数,在setup_arch调用过一次,可参考    parse_early_param();    //解析Booting kernel传进来的参数    parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,-1, -1, &unknown_bootoption);    //处理静态定义的jump label,执行跳转指令或是nop指令    jump_label_init();            //使用memblock分配一个启动信息的缓冲区    setup_log_buf(0);    //使用bootmem分配并初始化PID散列表    pidhash_init();    //虚拟文件系统的早期初始化有函数vfs_caches_init_early()实现,主要负责dentry和inode的hashtable的初始化工作。    vfs_caches_init_early();    //对内核异常表进行排序    sort_main_extable();    //对内核陷阱异常经行初始化,ARM架构中位空函数    trap_init();    //初始化内核内存分配器,过度到伙伴系统,启动slab机制,初始化非连续内存区    //以后分配内存不能使用memblock来分配了    mm_init();            //初始化调度器数据结构并创建运行队列???    sched_init();        //禁止内核抢占,关中断    preempt_disable();    if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))        local_irq_disable();    //创建idr layer的高速缓存cache,参考idr机制文档    idr_init_cache();    //初始化系统中"读-写-拷贝"同步机制???    rcu_init();    //cpu处在idle状态时关闭tick中断的优化初始化    tick_nohz_init();    //没有设置CONFIG_CONTEXT_TRACKING_FORCE宏,不会调用该函数    context_tracking_init();    //radix树的初始化,供页面查找    radix_tree_init();    //中断初始化,初始化irq_desc数组    early_irq_init();    //中断初始化,调用特定平台的中断初始化的init_irq()函数,参考early_irq_init.c    init_IRQ();    //时钟滴答的初始化,见timer_init.c    tick_init();    //初始化本CPU上的软件时钟相关的数据结构,注册时钟软中断TIMER_SOFTIRQ,见timer_init.c    init_timers();    //高分辨率定时器框架初始化,见timer_init.c    hrtimers_init();    //软中断初始化,参考软中断文档    softirq_init();    //初始化需要和时钟代码共同管理的时间相关值    timekeeping_init();    //调用平台设置的time初始化函数    time_init();    //进程调度时钟初始化    sched_clock_postinit();    //CPU性能监视机制初始化,此机制包括CPU同一时间执行指令数,cache??miss数,分支预测失败次数等性能参数???    perf_event_init();    //对内核的profile(一个内核性能调式工具)功能进行初始化,分配好存放profile信息的内存    profile_init();    //smp下跨cpu的函数传递初始化,参考smp文档    call_function_init();    WARN(!irqs_disabled(), "Interrupts were enabled early\n");    early_boot_irqs_disabled = false;    //使能本地中断    local_irq_enable();    //slab分配器后期初始化    kmem_cache_init_late();            //初始化控制台,参考console驱动文档    console_init();    //检查内核恐慌标准,如果有问题,打印信息    if (panic_later)        panic("Too many boot %s vars at `%s'", panic_later,panic_param);        //打印lockdep调试模块信息    lockdep_info();    //锁测试    locking_selftest();        //检查initrd的位置是否符合要求#ifdef CONFIG_BLK_DEV_INITRD    if (initrd_start && !initrd_below_start_ok &&        page_to_pfn(virt_to_page((void *)initrd_start)) min_low_pfn) {        pr_crit("initrd overwritten (0x%08lx ,                page_to_pfn(virt_to_page((void *)initrd_start)),min_low_pfn);        initrd_start = 0;    }#endif    //容器组的页面内存分配    page_cgroup_init();    //调试相关,参考debug_objects_early_init文档    debug_objects_mem_init();    //内存泄露检测机制的初始化    kmemleak_init();    //为zone中用于实现单一页框的特殊高速缓存设置单一页面数量    setup_per_cpu_pageset();    //一致性内存访问(NUMA)初始化,设置NUMA系统中的内存策略。arm非NUMA系统,不考虑    numa_policy_init();    //arm中为NULL,不掉用    if (late_time_init)        late_time_init();    //初始化调度时钟    sched_clock_init();    //用于BogoMIPS,该值显示1jiffy内消耗多少cpu    calibrate_delay();    //进程PID位图分配映射初始化    pidmap_init();    //创建anon_vma的slab缓存    anon_vma_init();    //acpi相关初始化,忽略    acpi_early_init();#ifdef CONFIG_X86    if (efi_enabled(EFI_RUNTIME_SERVICES))        efi_enter_virtual_mode();#endif#ifdef CONFIG_X86_ESPFIX64    /* Should be run before the first non-init thread is created */    init_espfix_bsp();#endif    //创建进程thread_info的slab高速缓存    thread_info_cache_init();    //为对象的每个用户赋予资格    cred_init();    //进程创建机制初始化    fork_init(totalram_pages);    //创建进程所需的各结构体slab缓存    proc_caches_init();    //为buffer_head结构体创建slab高速缓存    buffer_init();    //准备密钥    key_init();    //内核安全框架初始化    security_init();    //内核调试系统后期初始化,kgdb初始化    dbg_late_init();    //初始化VFS中使用的多种缓存    vfs_caches_init(totalram_pages);    //创建sigqueue结构高速缓存    signals_init();    //页回写机制初始化    page_writeback_init();    //注册并初始化proc文件系统    proc_root_init();    //注册未能初始化的cgroup子系统    cgroup_init();    //注册cpuset文件系统    cpuset_init();    //初始化任务统计信息接口    taskstats_init_early();    //为管理延迟信息做准备    delayacct_init();    //检查写缓冲一致性    check_bugs();        //simple firmware interface    sfi_init_late();        if (efi_enabled(EFI_RUNTIME_SERVICES)) {        efi_late_init();        efi_free_boot_services();    }        //trace初始化    ftrace_init();        //剩余的初始化    rest_init();}static inline void mm_init_cpumask(struct mm_struct *mm){#ifdef CONFIG_CPUMASK_OFFSTACK    mm->cpu_vm_mask_var = &mm->cpumask_allocation;#endif}static void __init setup_command_line(char *command_line){    //把刚才在setup_arch()中拷贝进来的command_line,拷贝到全局变量saved_command_line和static_command_line所指向的内存单元中。    //通过memblock分配内存空间,参考memblock文档    saved_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);    initcall_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);    static_command_line = memblock_virt_alloc(strlen(command_line) + 1, 0);    strcpy (saved_command_line, boot_command_line);    strcpy (static_command_line, command_line);}//nr_cpu_ids是一个特殊的值,在单CPU情况下是1;而SMP情况下,又是一个全局变量,被find_last_bit函数设置.    void __init setup_nr_cpu_ids(void){    nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;}//根据smp和cpu架构void __init smp_prepare_boot_cpu(void){    set_my_cpu_offset(per_cpu_offset(smp_processor_id()));}#if defined(CONFIG_SMP) && !defined(CONFIG_CPU_V6)static inline void set_my_cpu_offset(unsigned long off){    /* Set TPIDRPRW */    asm volatile("mcr p15, 0, %0, c13, c0, 4" : : "r" (off) : "memory");}#else#define set_my_cpu_offset(x) do {} while(0)#endif /* CONFIG_SMP */
11-04 11:44