在前面的章节介绍了uboot和Linux内核的一些相关内容。在来看Linux内核的大致启动流程,Linux内核的启动流程要比uboot复杂的多,涉及到的内容也更多,因此在本章节大致简单的了解一下Linux内核的启动流程。有兴趣的用户可以参考其他书籍或资料进行深入了解。 嵌入式linux内核的启动全过程主要分为三个阶段。第一阶段为内核自解压过程,第二阶段主要工作是设置ARM处理器工作模式、使能MMU、设置一级页表等,而第三阶段则主要为C代码,包括内核初始化的全部工作。下面分别进行简单介绍。 基于迅为-IMX6ULL开发板 30.1 Linux内核启动(一):Linux内核自解压过程Linux内核有两种映像格式:一种是非压缩内核,叫Image,另一种是它的压缩版本,叫zImage。zImage是Image经过压缩形成的,所以它的大小比Image小。但为了能使用zImage,必须在它的开头加上解压缩的代码,将zImage解压缩之后才能执行,因此它的执行速度比Image要慢一些。 内核压缩和解压缩代码都在目录kernel/arch/arm/boot/compressed,编译完成后将产生head.o、misc.o、piggy.gzip.o、vmlinux、decompress.o这几个文件,head.o是内核的头部文件,负责初始设置;misc.o将主要负责内核的解压工作,它在head.o之后;piggy.gzip.o是一个中间文件,其实是一个压缩的内核(kernel/vmlinux),只不过没有和初始化文件及解压文件链接而已;vmlinux是没有(zImage是压缩过的内核)压缩过的内核,就是由piggy.gzip.o、head.o、misc.o组成的,而decompress.o是为支持更多的压缩格式而新引入的。 在uboot完成系统引导将Linux内核加载到内存之后,调用do_bootm_linux(),这个函数将跳转到kernel的起始位置。如果kernel没有被压缩,就可以启动了。如果kernel被压缩过,则要进行解压,在压缩过的kernel头部有解压程序。压缩过的kernel入口第一个文件源码位置在arch/arm/boot/compressed/head.S。它将调用函数decompress_kernel(),这个函数在文件arch/arm/boot/compressed/misc.c中,decompress_kernel()又调用arch_decomp_setup()进行设置,然后调用gunzip()将内核放于指定的位置。 函数decompress_kernel实现的功能:解压缩代码位于kernel/lib/inflate.c,inflate.c是从gzip源程序中分离出来的,包含了一些对全局数据的直接引用,在使用时需要直接嵌入到代码中。gzip压缩文件时总是在前32K字节的范围内寻找重复的字符串进行编码, 在解压时需要一个至少为32K字节的解压缓冲区,它定义为window[WSIZE]。inflate.c使用get_byte()读取输入文件,它被定义成宏来提高效率。输入缓冲区指针必须定义为inptr,inflate.c中对之有减量操作。inflate.c调用flush_window()来输出window缓冲区中的解压出的字节串,每次输出长度用outcnt变量表示。在flush_window()中,还必须对输出字节串计算CRC并且刷新crc变量。在调用gunzip()开始解压之前,调用makecrc()初始化CRC计算表。最后gunzip()返回0表示解压成功。在内核启动时一般会看到这样的输出: UncompressingLinux...done, booting the kernel. 当然有的内核没有这样的输出,是没有这一条打印语句。 30.2 Linux内核启动(二):ARM处理器相关设置当Linux内核自解压完成后,开始执行内核代码。内核的入口函数由链接脚本vmlinux.lds决定,需要编译内核源码,才会生成脚本文件。 首先分析Linux内核的链接脚本文件arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到Linux内核的第一行程序是从哪里开始执行的。vmlinux.lds文件部分代码如下: 492 OUTPUT_ARCH(arm) 493 ENTRY(stext) 494 jiffies = jiffies_64; 495 SECTIONS 496 { 497 /* 498 * XXX: The linker does not define how output sections are 499 * assigned to input sections when there are multiple statements 500 * matching the same input section name. There is no documented 501 * order of matching. 502 * 503 * unwind exit sections must be discarded before the rest of the 504 * unwind sections get included. 505 */ 506 /DISCARD/ : { 507 *(.ARM.exidx.exit.text) 508 *(.ARM.extab.exit.text) 509 ...... 645 } 第493行的 ENTRY 指明了Linux内核入口函数为stext,因此要分析Linux内核第二阶段的启动流程,就得先从文件 arch/arm/kernel/head.S 的stext处开始分析。 30.2.1 Linux内核入口函数stextstext是Linux内核的入口地址,在文件arch/arm/kernel/head.S中有如下内容: /* * Kernel startup entry point. * --------------------------- * * This is normally called from the decompressor code. The requirements * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, * r1 = machine nr, r2 = atags or dtb pointer. ..... */ 以上代码中的内容显示。Linux内核启动之前要求如下: ① 关闭MMU ② 关闭D-cache ③ 不用关心I-cache ④ r0 = 0。 ⑤ r1 = machine nr(机器ID号) ⑥ r2 = atags或者设备树(dts)首地址 Linux内核的入口点stext其实相当于内核的入口函数,stext函数代码如下: 80 ENTRY(stext) ...... 91 @ ensure svc mode and all interrupts masked 92 safe_svcmode_maskall r9 93 94 mrc p15, 0, r9, c0, c0 @ get processor id 95 bl __lookup_processor_type @ r5=procinfo r9=cpuid 96 movs r10, r5 @ invalid processor (r5=0)? 97 THUMB( it eq ) @ force fixup-able long branch encoding 98 beq __error_p @ yes, error 'p' 99 ...... 107 108 #ifndef CONFIG_XIP_KERNEL ...... 113 #else 114 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case 115 #endif 116 117 /* 118 * r1 = machine no, r2 = atags or dtb, 119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo 120 */ 121 bl __vet_atags ...... 128 bl __create_page_tables 129 130 /* 131 * The following calls CPU specific code in a position independent 132 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of 133 * xxx_proc_info structure selected by __lookup_processor_type 134 * above. On return, the CPU will be ready for the MMU to be 135 * turned on, and r0 will hold the CPU control register value. 136 */ 137 ldr r13, =__mmap_switched @ address to jump to after 138 @ mmu has been enabled 139 adr lr, BSYM(1f) @ return (PIC) address 140 mov r8, r4 @ set TTBR1 to swapper_pg_dir 141 ldr r12, [r10, #PROCINFO_INITFUNC] 142 add r12, r12, r10 143 ret r12 144 1: b __enable_mmu 145 ENDPROC(stext) 第92行,调用函数safe_svcmode_maskall确保CPU处于SVC模式,并且关闭了所有的中断。safe_svcmode_maskall定义在文件arch/arm/include/asm/assembler.h 中。 第94行,读处理器ID,ID值保存在r9寄存器中。 第95行,__lookup_processor_type调用结束返回原程序时,会将返回结果保存到寄存器中。其中r5寄存器返回一个用来描述处理器的结构体地址,并对r5进行判断,如果r5的值为0则说明不支持这种处理器,将进入__error_p。 procinfo 是 proc_info_list类 型 的 结 构 体 ,Linux内核将每种处理器都抽象为一个 proc_info_list 结构体,每种处理器都对应一个procinfo。proc_info_list在文件arch/arm/include/asm/procinfo.h 中的定义如下: 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; }; 第 121 行,调用函数__vet_atags 验证 atags 或设备树(dtb)的合法性。函数__vet_atags 定义在文件 arch/arm/kernel/head-common.S 中。 第 128 行,调用函数__create_page_tables 创建页表。 第 137 行,将函数__mmap_switched 的地址保存到 r13 寄存器中。__mmap_switched 定义在文件 arch/arm/kernel/head-common.S,__mmap_switched 最终会调用 start_kernel 函数。 第 144 行 ,调用 __enable_mmu 函 数 使 能 MMU , __enable_mmu 定 义 在 文 件arch/arm/kernel/head.S 中。__enable_mmu 最终会通过调用__turn_mmu_on 来打开 MMU,__turn_mmu_on 最后会执行 r13 里面保存的__mmap_switched 函数。 30.2.2 _mmap_switched 函数__mmap_switched 函数定义在文件arch/arm/kernel/head-common.S中,函数代码如下: 81 __mmap_switched: 82 adr r3, __mmap_switched_data 83 84 ldmia r3!, {r4, r5, r6, r7} 85 cmp r4, r5 @ Copy data segment if needed 86 1: cmpne r5, r6 87 ldrne fp, [r4], #4 88 strne fp, [r5], #4 89 bne 1b 90 91 mov fp, #0 @ Clear BSS (and zero fp) 92 1: cmp r6, r7 93 strcc fp, [r6],#4 94 bcc 1b 95 96 ARM( ldmia r3, {r4, r5, r6, r7, sp}) 97 THUMB( ldmia r3, {r4, r5, r6, r7} ) 98 THUMB( ldr sp, [r3, #16] ) 99 str r9, [r4] @ Save processor ID 100 str r1, [r5] @ Save machine type 101 str r2, [r6] @ Save atags pointer 102 cmp r7, #0 103 strne r0, [r7] @ Save control register values 104 b start_kernel 105 ENDPROC(__mmap_switched) 第 104 行最终调用 start_kernel 来启动 Linux 内核,start_kernel 函数定义在文件 init/main.c中。 从start_kernel 函数开始Linux内核启动进入到下一个阶段。 下面内容请关注:Linux内核启动流程-迅为IMX6ULL开发板(二)
|