zoukankan      html  css  js  c++  java
  • Linux内核启动流程-迅为IMX6ULL开发板(一)

    在前面的章节介绍了ubootLinux内核的一些相关内容。在来看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.omisc.opiggy.gzip.ovmlinuxdecompress.o这几个文件,head.o是内核的头部文件,负责初始设置;misc.o将主要负责内核的解压工作,它在head.o之后;piggy.gzip.o是一个中间文件,其实是一个压缩的内核(kernel/vmlinux),只不过没有和初始化文件及解压文件链接而已;vmlinux是没有(zImage是压缩过的内核)压缩过的内核,就是由piggy.gzip.ohead.omisc.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.cinflate.c是从gzip源程序中分离出来的,包含了一些对全局数据的直接引用,在使用时需要直接嵌入到代码中。gzip压缩文件时总是在前32K字节的范围内寻找重复的字符串进行编码, 在解压时需要一个至少为32K字节的解压缓冲区,它定义为window[WSIZE]inflate.c使用get_byte()读取输入文件,它被定义成宏来提高效率。输入缓冲区指针必须定义为inptrinflate.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内核入口函数stext

    stextLinux内核的入口地址,在文件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行,读处理器IDID值保存在r9寄存器中。

    95行,__lookup_processor_type调用结束返回原程序时,会将返回结果保存到寄存器中。其中r5寄存器返回一个用来描述处理器的结构体地址,并对r5进行判断,如果r5的值为0则说明不支持这种处理器,将进入__error_p procinfo  proc_info_list      Linux内核将每种处理器都抽象为一个 proc_info_list 结构体,每种处理器都对应一个procinfoproc_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开发板(二)

  • 相关阅读:
    swift 第十四课 可视化view: @IBDesignable 、@IBInspectable
    swift 第十三课 GCD 的介绍和使用
    swift 第十二课 as 的使用方法
    swift 第十一课 结构体定义model类
    swift 第十课 cocopod 网络请求 Alamofire
    swift 第九课 用tableview 做一个下拉菜单Menu
    swift 第八课 CollectView的 添加 footerView 、headerView
    swift 第七课 xib 约束的优先级
    swift 第六课 scrollview xib 的使用
    swift 第五课 定义model类 和 导航栏隐藏返回标题
  • 原文地址:https://www.cnblogs.com/liyue3/p/13328819.html
Copyright © 2011-2022 走看看