Linux 内核启动

来源:互联网 发布:零点网络 编辑:程序博客网 时间:2024/05/16 17:48
转自:http://celinux.wikidot.com/linuxboot
Linux 内核启动
Fold
Table of Contents
Bootloader 如何加载内核?
ARM Linux 内核启动条件
Machine ID 和 内核参数如何传递
Linux 内核启动
Linux 内核入口
压缩内核的解压缩过程
start_kernel() 之前
start_kernel() 启动过程

Bootloader 如何加载内核?

ARM Linux 内核启动条件

  • 从 Bootloader 跳转到内核开始执行时,需满足一下条件:
    • MMU: off
    • D-Cache: off
    • I-Cache: don’t care
    • r0 = 0
    • r1 = Machine ID
    • r2 = Parameter tag address
// 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 pointer. *...
  • ARM11系统中, 如果要使用 D-Cache, 必须打开 MMU. 所以现在很多 bootloader 也是打开MMU,使用虚拟地址工作的。 这时候需要:
    • 建立一个简单的页表,至少保证内核加载地址空间和bootloader本省使用的空间可访问
    • 为了使用方便,可以将虚拟地址映射到和物理地址一样的地址空间;或者使用和内核空间中一样的虚拟地址

Machine ID 和 内核参数如何传递

  • 在 U-Boot 中, Machine ID 和内核参数地址是由 bootloader 指定的。 例如,XScale系统中有如下代码:
// board/lubbock/lubbock.c  int board_init (void){    /* arch number of Lubbock-Board */    gd->bd->bi_arch_number = MACH_TYPE_LUBBOCK;     /* adress of boot parameters */    gd->bd->bi_boot_params = 0xa0000100;     return 0;}
  • 启动内核时将 bi_arch_number 和 bi_boot_params 传给内核:
// lib_arm/bootm.c    do_bootm_linux ()   {    machid = bd->bi_arch_number;    ...    theKernel = (void (*)(int, int, uint))images->ep;    ...    theKernel (0, machid, bd->bi_boot_params);   }
  • u-boot 中, 内核的起始地址从uImage的文件头中得到。

Linux 内核启动

Linux 内核入口

Linux内核的入口在 arch/arm/kernel/head.S 中定义:

    .section ".text.head", "ax"ENTRY(stext)    msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode                        @ and irqs disabled    mrc    p15, 0, r9, c0, c0        @ get processor id    bl    __lookup_processor_type        @ r5=procinfo r9=cpuid    movs    r10, r5                @ invalid processor (r5=0)?    beq    __error_p            @ yes, error 'p'    bl    __lookup_machine_type        @ r5=machinfo        ...
  • 该段代码在链接的时候会被链接到 0xC0008000 地址。该链接地址是在 arch/arm/kernel/vmlinux.S 中指定的:
OUTPUT_ARCH(arm)ENTRY(stext)jiffies = jiffies_64;SECTIONS{ . = 0xC0000000 + 0x00008000;   <------ 内核起始地址,为虚拟地址 .text.head : {                                <------ text.head 段  _stext = .;  _sinittext = .;  *(.text.head) } .init : { /* Init code and data        */....

压缩内核的解压缩过程

start_kernel() 之前

在执行 C 函数 start_kernel() 之前, 内核首先需要执行一段汇编代码,完成相关的检查及初始化工作:

  • 检查CPU ID
  • 检查内核参数
  • 建立初始内核页表,该页表会被后面执行的paging_init() 覆盖掉
  • 打开 MMU,数据缓存以及命令缓存
    .section ".text.head", "ax"ENTRY(stext)    __lookup_processor_type    __lookup_machine_type    __vet_atags              --> check tag header    __create_page_tables    ldr    r13, __switch_data         --> save tag address to __atags_pointer    adr    lr, __enable_mmu    add    pc, r10, #PROCINFO_INITFUNC    ...
  • __switch_data 的地址保存在 r13, __enable_mmu 的地址保存在 lr 寄存器
  • 注意: adr 是一个虚拟指令(pseudo-instruction) , 详细说明参考: ADR ARM pseudo-instruction or ARM ADR
  • 最后一条指令: add pc, r10, #PROCINFO_INITFUNC
  • 此时,r10 保存了 xxx_proc_info 的起始地址, 由 __lookup_machine_type 函数根据CPU种类设置好的。
  • 例如,i.MX31 (ARMv6体系结构), 则 r10 保存了 __arm6_proc_info 的起始地址:
--- arch/arm/mm/proc-arm6_7.S ---__arm6_proc_info:        .long    0x41560600        .long    0xfffffff0        .long    0x00000c1e        .long   PMD_TYPE_SECT | \            PMD_BIT4 | \            PMD_SECT_AP_WRITE | \            PMD_SECT_AP_READ        b    __arm6_setup        .long    cpu_arch_name        ...
  • 也就是说,跳转到 __arm6_setup 中执行,
  • __arm6_setup 完成之后该函数执行 mov pc, lr , 由于 lr 已经保存了 _enable_mmu 的地址, 所以会跳转到 _enable_mmu 执行, 打开MMU.
__enable_mmu:    ...    b    __turn_mmu_onENDPROC(__enable_mmu)__turn_mmu_on:    ...    mov    pc, r13ENDPROC(__turn_mmu_on)
  • __enable_mmu 调用 __turn_mmu_on 打开MMU,然后调用 mov pc, r13 返回。
  • 由于r13 保存了 __switch_data 的起始地址,也就是 __mmap_switched 的地址, 因此MMU打开之后会执行 __mmap_switched 函数
---- arch/arm/kernel/head-common.S ----__switch_data:    .long    __mmap_switched    .long    __data_loc            @ r4    ....__mmap_switched:    ...    ldmia    r3, {r4, r5, r6, r7, sp}    str    r9, [r4]            @ Save processor ID    str    r1, [r5]            @ Save machine type    str    r2, [r6]            @ Save atags pointer    bic    r4, r0, #CR_A            @ Clear 'A' bit    stmia    r7, {r0, r4}            @ Save control register values    b    start_kernel
  • __mmap_switched 完成之后就跳到 C 程序中的 start_kernel() 执行了。汇编代码段到此结束。

start_kernel() 启动过程

原创粉丝点击