Linux kernel 启动流程分析2

Linux kernel 启动流程分析2 ---基本概述

一、kernel启动准备动作


1.1、加载内核镜像到 DDR

在嵌入式系统中,常见的步骤如下:

  1. U-Boot:一个引导加载程序,用于加载和启动内核。U-Boot 通常会从闪存中(如 NAND、eMMC 或 SPI Flash)加载内核映像,并将其复制到 DDR(动态随机存取存储器)中执行。
  2. Kernel Image:内核镜像(通常是压缩格式,如 zImage 或经过 uboot 包装的 uImage),需要在启动时加载到 RAM(通常是 DDR)并解压缩。
  3. 启动过程:在 U-Boot 中,将内核镜像加载到 DDR 后,启动内核。

这里描述的过程假设你使用的是 U-Boot 和 Linux 系统。U-Boot 是嵌入式系统中常用的引导加载程序,它负责从存储设备(如 NAND、eMMC、SD 卡等)读取内核映像,并将其加载到内存中。

1. 设置内存映射

首先,需要确保系统的 DDR 内存已经被正确初始化。在 U-Boot 中,通常会看到类似的设置:

# 设定 DDR 起始地址,通常由硬件平台决定
setenv bootargs 'mem=512M'
2. 加载内核镜像

假设内核镜像存放在存储设备(如 SD 卡或 SPI Flash)中,你可以使用 U-Boot 来加载内核镜像。首先,设置相应的存储设备和文件路径,然后将内核镜像加载到 DDR。

  • SD卡 存储的内核镜像:

  • # 设定存储设备和路径
    mmc dev 0  # 选择 SD 卡设备
    load mmc 0:1 0x80008000 /boot/zImage  # 将 zImage 加载到 DDR 的 0x80008000 地址
    

    其中:

    • mmc dev 0:选择 SD 卡设备(0 是设备编号)。
    • load mmc 0:1 0x80008000 /boot/zImage:从 SD 卡的第一个分区(0:1)加载内核镜像 zImage 到 DDR 的 0x80008000 地址。
  • SPI Flash 存储的内核镜像:

  • # 设定 SPI Flash 的设备和路径
    sf probe 0  # 初始化 SPI Flash
    load spi 0:0 0x80008000 /boot/zImage  # 将内核镜像从 SPI Flash 加载到 DDR
    

    其中:

    • sf probe 0:初始化 SPI Flash。
    • load spi 0:0 0x80008000 /boot/zImage:从 SPI Flash 加载内核到 DDR 的地址 0x80008000
3. 解压并启动内核

对于 zImage(压缩内核),U-Boot 会自动解压它。加载内核镜像后,U-Boot 将执行内核并启动它。

  • 启动内核

  • # 启动内核
    bootm 0x80008000
    

    其中:

    • bootm 0x80008000:启动加载在 0x80008000 地址处的内核镜像。U-Boot 会识别这是一个内核镜像并开始启动。

对于 uImage(包含头部和校验和的压缩内核),你可以使用 bootm 命令加载并启动它:

  • 加载并启动 uImage

  • load mmc 0:1 0x80008000 /boot/uImage
    bootm 0x80008000
    
4. 配置启动参数

除了加载内核镜像外,U-Boot 还会配置内核启动参数。例如,你可以设置 bootargs 变量,指定根文件系统的位置、设备等信息:

setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw'

这会将内核启动时的命令行参数设置为:

  • console=ttyS0,115200:设置串口终端(ttyS0)和波特率(115200)。
  • root=/dev/mmcblk0p2:设置根文件系统的设备(例如 SD 卡的第二个分区)。
  • rw:根文件系统以读写模式挂载。


1.2、硬件要求

根据arch/arm/kernel/head.S的stext(kernel的入口函数)的注释头

/*
 * 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.

所以有如下要求

  • * MMU = off

MMU用来处理物理地址到虚拟内存地址的映射,因此需要软件上需要先配置其映射表(也就是后续文章会说明的页表)。MMU关闭的情况下,CPU寻址的地址都是物理地址,也就是不需要经过转化直接访问相应的硬件。一旦打开之后,CPU寻址的所有地址都是虚拟地址,都会经过MMU映射到真正的物理地址上,即使你在代码中访问的是一个物理地址,也会被当作虚拟内存地址使用。
而映射表是由kernel自己创建的,因此,在创建映射表之前kernel访问的地址都是物理地址,所以必须保证MMU是关闭状态。

  • * D-cache = off

CACHE是CPU和内存之间的高速缓冲存储器,又分成数据缓冲器D-cache和指令缓冲器I-cache。具体意义这里不多说明。
数据Cache一定要关闭,否则可能kernel刚启动的过程中,去取数据的时候,从Cache里面取,而这时候RAM中数据还没有Cache过来,导致数据预取异常 。
自己的理解是,假设打开MMU之前,cache上存了一个项“地址0x20000000、数据0xffff0000”,打开MMU之后,读取0x20000000地址上(虚拟地址)数据,但是此时会直接从cache中读到项“地址0x20000000、数据0xffff0000”,但实际上对应物理地址上的数据并不是这个,所以会导致读取的数据错误。

  • * r0 = 0

硬性规定r0寄存器为0,没有什么意义。

  • * r1 = machine nr

r1寄存器里面存放machine ID。

  • * r2 = atags or dtb pointer

r2寄存器里面存放atags或者dtb的地址指针。


1.3、跳转到kernel镜像入口的对应位置

bootloader需要通过设置PC指针到kernel的入口代码处(也就是kernel的加载位置)来实现kernel的跳转。


二、kernel启动过程入口说明

 

2.1、kernel入口地址的指定


arch/arm/kernel/vmlinux.lds.S中

ENTRY(stext)

所以kernel启动的入口代码位于arch/arm/kernel/head.S。
这里也是我们后续要分析kernel启动流程的核心代码部分。


2.2、kernel入口地址


* 连接地址,通过System.map查看,可以看到stext的连接地址是0xc0008000

c0008000 T stext

    加载地址,因为uboot把kernel加载到0x20008000的位置上,所以kernel的入口是0x20008000
    根据连接脚本,stext是作为kernel的入口(上述一),所以stext就在kernel镜像的起始位置上,所以stext的加载地址应该0x20008000。

三、kernel启动的两个阶段说明

kernel在启动初期主要分成两个阶段。


3.1、第一阶段:从入口跳转到start_kernel之前的阶段。

对应代码arch/arm/kernel/head.S中stext的实现:

ENTRY(stext)

这个阶段主要由汇编语言实现。
这个阶段主要负责MMU打开之前的一些操作,以及打开MMU的操作。
由于这个阶段MMU还没有打开,并且kernel加载地址和连接地址并一致,所以需要使用位置无关设计。在运行过程中运行地址和加载地址一致(如果不明白的话建议先参考一下《[kernel 启动流程] 前篇——vmlinux.lds分析》)。
其主要流程如下:
* 设置为SVC模式,关闭所有中断
什么是SVC模式?为什么要设置成SVC模式?
如何关闭所有中断?为什么要关闭所有中断呢?

    @ ensure svc mode and all interrupts masked
    safe_svcmode_maskall r9

参考[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断
* 获取CPU ID,提取相应的proc info
从哪里去提取proc_info?
proc_info里面存放了什么东西?这里面的数据结构应该是很重要所以才有必要在打开MMU前就去获取。

    mrc p15, 0, r9, c0, c0      @ get processor id
    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    movs    r10, r5             @ invalid processor (r5=0)?
 THUMB( it  eq )        @ force fixup-able long branch encoding
    beq __error_p           @ yes, error 'p'


* 验证tags或者dtb
如何验证dtb的有效性?

    bl  __vet_atags

参考[kernel 启动流程] (第四章)第一阶段之——dtb的验证
* 创建临时内核页表的页表项
页表项的作用?基础知识?和MMU之间的关系?
*要为哪些内存区域创建页表项?为什么?

    bl  __create_page_tables

参考[kernel 启动流程] (第五章)第一阶段之——临时内核页表的创建
* 配置r13寄存器,也就是设置打开MMU之后要跳转到的函数。
为什么要在打开MMU之前配置呢

    ldr    r13, =__mmap_switched        @ address to jump to after
                        @ mmu has been enabled

  

  •     使能MMU

    如何使能MMU?
    swapper_pg_dir变量?

    ldr    r12, [r10, #PROCINFO_INITFUNC]
    add    r12, r12, r10

    ret    r12
1:    b    __enable_mmu


* 跳转到start_kernel
如何跳转到start_kernel?

分析代码的时候可以结合上述疑问去进行分析。
后续分析启动代码的过程中会针对上述流程和疑问进行文档整理。


3.2、第二阶段:start_kernel开始的阶段。

这个阶段主要由C语言实现。
kernel启动流程中剩余的操作都是这里实现。


四、举例说明

1. 使用 uImage 格式的内核镜像

U-Boot 环境配置

对于 uImage 格式的内核镜像,U-Boot 的操作流程通常如下:

  1. 选择 SD 卡并加载内核镜像

    假设内核镜像 uImage 存放在 SD 卡的第一个分区(mmc 0:1),并将其加载到 DDR 中:

  • mmc dev 0  # 选择 SD 卡设备
    load mmc 0:1 0x80008000 /boot/uImage  # 将 uImage 加载到 DDR 地址 0x80008000
    
  • 加载设备树(可选)

    如果使用设备树,你还需要加载设备树文件(例如 rk3568.dtb):

  • load mmc 0:1 0x88000000 /boot/rk3568.dtb  # 将设备树文件加载到 DDR 地址 0x88000000
    
  • 设置启动参数

    配置内核启动参数,例如:

  • setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw'
    
  • 启动内核

    最后,使用 bootm 命令启动内核:

  1. bootm 0x80008000 - 0x88000000  # 启动内核镜像,指定设备树地址
    
    • 0x80008000 是内核镜像 uImage 在 DDR 中的加载地址。
    • 0x88000000 是设备树文件 rk3568.dtb 的地址。

U-Boot 流程

在 U-Boot 中,bootm 命令会自动解压缩 uImage,并启动内核。启动流程如下:

  1. U-Boot 从 SD 卡加载 uImage
  2. U-Boot 解压 uImage(如果是压缩内核)并将其加载到 DDR。
  3. 启动内核并传递启动参数。

2. 使用 FIT (Flattened Image Tree) 格式的内核镜像

FIT 格式是一种更灵活的内核映像格式,包含多个内核、设备树、RAMdisk 和其他信息。FIT 格式的优点是能够将所有的启动资源(如内核、设备树、RAMdisk)打包到一个文件中,U-Boot 会解析该文件并加载相应的部分。

FIT 文件结构

一个典型的 FIT 文件结构可能包含:

  • 内核镜像kernel
  • 设备树fdt
  • RAMdiskramdisk,可选)
  • 启动命令等。

U-Boot 环境配置

对于 FIT 文件,U-Boot 会解析 FIT 文件中的各个部分,并加载相应的内核、设备树和其他组件。以下是基本的配置和流程:

  1. 选择 SD 卡并加载 FIT 文件

    假设 FIT 文件名为 rk3568-fit.itb,存储在 SD 卡的第一个分区:

  • mmc dev 0  # 选择 SD 卡设备
    load mmc 0:1 0x80008000 /boot/rk3568-fit.itb  # 将 FIT 文件加载到 DDR 地址 0x80008000
    
  • 加载设备树

    在 FIT 文件中,设备树通常与内核一起存储。U-Boot 会自动从 FIT 文件中提取设备树。因此,通常不需要单独加载设备树。

  • 设置启动参数

    设置内核启动参数:

  • setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw'
    
  • 启动内核

    启动 FIT 文件:

  1. bootm 0x80008000  # 启动 FIT 文件
    

FIT 启动流程

在使用 FIT 文件时,U-Boot 会按照以下流程启动:

  1. U-Boot 从 SD 卡加载 FIT 文件。
  2. U-Boot 解析 FIT 文件,并从中提取内核镜像和设备树。
  3. 启动内核并传递启动参数。

3. U-Boot 配置细节

  1. U-Boot 配置文件:你可以通过修改 include/configs/rk3568.hinclude/configs/rk3568_defconfig 配置文件来调整 U-Boot 的行为。例如,配置 SD 卡设备的引导方式,指定 FIT 文件或 uImage 文件的位置等。

  2. 内核配置:确保 Linux 内核配置正确,能够支持 RK3568 平台,并且支持你选择的镜像格式(uImage 或 FIT)。

  3. 启动流程调试:如果遇到启动问题,可以通过 U-Boot 的 printenvenv 命令查看和调整环境变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值