linux内核启动流程分析1
- *简单分析启动过程中的初始化内容
1. 寻找执行入口
- 和之前找uboot的执行入口一样,在编译时查看编译文件的顺序和链接文件发现入口为
arch/armc/kernel/head.S
“arch/arm/kernel/vmlinux.lds”
…
. = 0xC0000000 + 0x00008000
.head.text : {
text = .;
*(.head.text)
}
…
“arch/arm/kernel/head.S”
…
.arm
__HEAD
ENTRY(stext)
…
“include/linux/init.h”
…
#define __HEAD .section “.head.text”, “ax”
#define __INIT .section “.init.text”, “ax”
#define __FINIT .previous
…
2. head.S
确定 CPU 类型和启动模式(ARM/Thumb、SVC 模式)
关闭中断
检查处理器是否支持必要功能
初始化物理地址偏移
验证 ATAGs/DTB 参数
创建初始页表(identity mapping)
初始化 CPU 特定功能
开启 MMU(启用虚拟地址空间)
跳转到 __mmap_switched 进入内核下一阶段
段和入口定义
1 |
|
- .head.text 段:用于放置启动代码。
- stext 是 ARM Linux 的 第一个执行入口(在物理地址 0xC0008000,即 PAGE_OFFSET + TEXT_OFFSET)。
- 检查 ARM/Thumb 模式并切换
1 |
|
- 如果内核是 Thumb-2 格式(一种ARM的嵌入式格式),先跳转并切换到 Thumb 状态。
- 否则继续 ARM 模式。
- 设置 CPU 为 SVC 模式,关闭 IRQ/FIQ
1 |
|
- PSR_F_BIT:屏蔽 FIQ
- PSR_I_BIT:屏蔽 IRQ
- SVC_MODE:进入管理模式(Supervisor mode)
保证启动过程不会被中断打断。
- 获取 CPU ID 并匹配处理器类型
1 |
|
- 读取 CPU ID(ARM 架构寄存器)。
- 检查是否在 内核支持的 CPU 列表里。
- LPAE(ARMv7+)检查
1 |
|
- 如果是 ARM LPAE 模式,检查是否支持 64-bit 页表。
- 计算 PHYS_OFFSET
1 |
|
- 通过位置无关代码计算 物理偏移量,用于页表映射。
- 如果 CONFIG_XIP_KERNEL(执行在闪存),则直接 ldr r8, =PHYS_OFFSET。
- 验证传入参数
1 |
|
- 检查 启动参数有效性(machine ID, atags/dtb)。
- 如果是单核系统 + SMP 配置,做补丁。
- 修补物理/虚拟地址差异。
- 创建启动页表
1 |
|
- 这是关键步骤,创建初始页表,映射:
- identity mapping:物理地址映射到相同的虚拟地址(用于开启 MMU)
- 内核虚拟基地址 (0xC0000000) 映射到物理地址
- UART IO 区域(如果开启 DEBUG_LL)
- 进入 __create_page_tables 的逻辑:
- 分配页表内存 (pgtbl)
- 清空页表
- identity map 启动代码区域(__turn_mmu_on)
- 映射内核代码区域 [KERNEL_START, KERNEL_END)
- 映射 boot 参数区(ATAGs 或 DTB)
- 如果 DEBUG_LL:映射 UART 寄存器地址
- 返回页表基地址(r4)
- 初始化 CPU
1 |
|
- 调用 CPU 特定初始化代码(在 arch/arm/mm/proc-*.S)。
- 初始化完成后返回,准备打开 MMU。
- 打开 MMU
1 |
|
- __enable_mmu:
- 配置 域访问控制寄存器(domain access control)
- 设置页表基地址(TTBR)
- 修改 CP15 控制寄存器(r0),打开 MMU(bit 0),可能还打开缓存。
然后执行:
1 |
|
- __turn_mmu_on:
- 写 CP15 控制寄存器,开启 MMU
- 切换 PC 到 __mmap_switched(位于高地址 0xC0000000 区域)
- 从此开始,虚拟地址生效。
- __mmap_switched(MMU 打开后的第一步)
- 这个在 arch/arm/kernel/head-common.S,作用:
- 它完成从低级引导环境到C 语言内核环境的最后过渡,设置栈指针到内核栈, 清空 .bss, 确保内存布局、栈和关键变量正确,然后跳转到 start_kernel()。
1 |
|
- 进入 __mmap_switched
1 |
|
- adr r3, __mmap_switched_data:获取 __mmap_switched_data 表的虚拟地址(因为 MMU 已开启,现在是虚拟地址访问)。
- 复制 .data 段
1 |
|
含义:
- r4 = __data_loc:物理内存中 .data 的位置
- r5 = _sdata:虚拟地址 .data 段起始位置
- r6 = __bss_start:.bss 段开始
- r7 = _end:内核镜像结束位置
循环:如果 .data 的物理位置和虚拟位置不同(XIP 或 relocate),复制 .data 段内容到正确位置。
- 清零 BSS
1 |
|
- 把 .bss 段清零(即 __bss_start 到 _end)。
- 原因:C 语言要求未初始化全局变量为 0。
- 加载保存关键变量的地址
1 |
|
- 现在 r3 指向 __mmap_switched_data 的剩余部分:
- r4 = processor_id 地址
- r5 = __machine_arch_type 地址
- r6 = __atags_pointer 地址
- r7 = cr_alignment 地址
- sp = init_thread_union + THREAD_START_SP(设置栈指针)
- 保存启动参数
1 |
|
- 把早期保存的 CPU ID(r9)、机器类型(r1)、atags/dtb 地址(r2)存到全局变量。
- 这样 start_kernel() 就可以用这些数据。
- 保存控制寄存器值
1 |
|
r0 是 CP15 控制寄存器值(MMU、Cache 状态)。
清掉 CR_A(Alignment fault enable),保存两个版本:
r0 原始值
r4 去掉 Alignment bit 的值
- 跳转到 C 语言世界
1 |
|
直接跳到 start_kernel()(init/main.c),进入 Linux C 初始化。
__mmap_switched_data
表
1 |
|
- 这个表提供了所有初始化需要的虚拟地址,因为现在 MMU 打开了,可以直接使用虚拟地址。
总结:__mmap_switched 主要干的事
✔ 复制 .data 段(如果需要)
✔ 清零 .bss 段
✔ 设置栈指针(SP)
✔ 保存 CPU ID、Machine ID、ATAGS/DTB 地址到全局变量
✔ 保存 CP15 控制寄存器值
✔ 跳转到 start_kernel() 进入 C 世界
linux内核启动流程分析1