Linux kernel 启动流程分析2 ---基本概述
一、kernel启动准备动作
1.1、加载内核镜像到 DDR
在嵌入式系统中,常见的步骤如下:
- U-Boot:一个引导加载程序,用于加载和启动内核。U-Boot 通常会从闪存中(如 NAND、eMMC 或 SPI Flash)加载内核映像,并将其复制到 DDR(动态随机存取存储器)中执行。
- Kernel Image:内核镜像(通常是压缩格式,如
zImage
或经过 uboot 包装的uImage
),需要在启动时加载到 RAM(通常是 DDR)并解压缩。 - 启动过程:在 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 的操作流程通常如下:
-
选择 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
命令启动内核:
-
bootm 0x80008000 - 0x88000000 # 启动内核镜像,指定设备树地址
0x80008000
是内核镜像uImage
在 DDR 中的加载地址。0x88000000
是设备树文件rk3568.dtb
的地址。
U-Boot 流程
在 U-Boot 中,bootm
命令会自动解压缩 uImage
,并启动内核。启动流程如下:
- U-Boot 从 SD 卡加载
uImage
。 - U-Boot 解压
uImage
(如果是压缩内核)并将其加载到 DDR。 - 启动内核并传递启动参数。
2. 使用 FIT (Flattened Image Tree) 格式的内核镜像
FIT 格式是一种更灵活的内核映像格式,包含多个内核、设备树、RAMdisk 和其他信息。FIT 格式的优点是能够将所有的启动资源(如内核、设备树、RAMdisk)打包到一个文件中,U-Boot 会解析该文件并加载相应的部分。
FIT 文件结构
一个典型的 FIT 文件结构可能包含:
- 内核镜像(
kernel
) - 设备树(
fdt
) - RAMdisk(
ramdisk
,可选) - 启动命令等。
U-Boot 环境配置
对于 FIT 文件,U-Boot 会解析 FIT 文件中的各个部分,并加载相应的内核、设备树和其他组件。以下是基本的配置和流程:
-
选择 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 文件:
-
bootm 0x80008000 # 启动 FIT 文件
FIT 启动流程
在使用 FIT 文件时,U-Boot 会按照以下流程启动:
- U-Boot 从 SD 卡加载 FIT 文件。
- U-Boot 解析 FIT 文件,并从中提取内核镜像和设备树。
- 启动内核并传递启动参数。
3. U-Boot 配置细节
-
U-Boot 配置文件:你可以通过修改
include/configs/rk3568.h
或include/configs/rk3568_defconfig
配置文件来调整 U-Boot 的行为。例如,配置 SD 卡设备的引导方式,指定 FIT 文件或 uImage 文件的位置等。 -
内核配置:确保 Linux 内核配置正确,能够支持 RK3568 平台,并且支持你选择的镜像格式(uImage 或 FIT)。
-
启动流程调试:如果遇到启动问题,可以通过 U-Boot 的
printenv
和env
命令查看和调整环境变量。