linux 启动流程
CPU 上电后最先执行的启动代码,通常确实是放在 arch
目录下对应架构的启动文件里。这是因为启动代码强相关于 CPU 架构和硬件细节,不同架构差异非常大。
具体说明
1. 为什么启动代码放在 arch
目录?
- 启动代码要设置 CPU 状态(如寄存器、MMU、异常向量表等)。
- 各架构 CPU 启动流程不一样,汇编语言代码也差别大。
- 所以放在
arch/架构名/boot/
或类似目录,便于管理。
2. 以常见架构为例
架构 | 启动代码位置示例 | 说明 |
---|---|---|
x86 | arch/x86/boot/ |
包含 real mode 到 protected mode 切换等代码 |
ARM | arch/arm/boot/ |
包含内核入口汇编、initrd搬移等代码 |
ARM64 | arch/arm64/kernel/head.S |
64位 ARM 启动汇编代码 |
MIPS | arch/mips/kernel/ |
MIPS 启动相关代码 |
3. 启动流程简述
- CPU 上电后,会跳转到一个固定的物理地址(Reset Vector)。
- 该地址对应的代码就是架构相关的启动代码,一般就是
arch/.../boot/...
里的汇编。 - 启动代码初始化最基本的硬件,设置内存和CPU状态。
- 然后跳转进入 C 语言编写的内核入口函数
start_kernel()
。
总结
- CPU 上电后的第一段执行代码一定是架构相关的启动代码,放在
arch
目录下。 - 这是因为启动时的硬件初始化和上下文设置紧密依赖 CPU 架构。
- 通用内核代码则放在
kernel/
、mm/
、fs/
等目录。
核心原因是内核启动前后环境差异巨大,必须用汇编代码做早期硬件和处理器环境初始化,才能安全可靠地进入 start_kernel。
Linux 内核源码里的 arch/
目录是 不同 CPU 架构相关代码的汇总目录。
具体解释
1. Linux 内核支持多种 CPU 架构
- 比如 x86、ARM、MIPS、PowerPC、RISC-V 等。
- 每种架构底层硬件接口和启动流程差别很大。
- 因此,内核源码会根据架构分目录管理。
2. arch/
目录的作用
-
arch/
目录下每个子目录对应一个 CPU 架构,比如:arch/
├── x86/ # x86 架构相关代码
├── arm/ # 32位 ARM 架构代码
├── arm64/ # 64位 ARM 架构代码
├── mips/ # MIPS 架构代码
├── riscv/ # RISC-V 架构代码
... -
每个目录里包含启动汇编、CPU相关驱动、体系结构专属的调度、内存管理、异常处理等代码。
-
其他跟架构无关的内核通用代码则在
kernel/
、mm/
、fs/
等目录。
详细解释
1. CPU 上电后环境非常"原始"
- CPU 刚上电时,只处于物理地址模式,没有开启分页(虚拟内存)
- 寄存器值不确定
- 堆栈、内存布局未初始化
- 特权级和CPU模式尚未设置
此时无法直接跳转执行复杂的C代码,因为C代码依赖于正确的栈、数据段和运行环境。
2. 汇编入口(_start)做的事情
汇编代码位于内核最开始的入口,做以下关键工作:
- 设置 CPU 特权级(例如切换到内核态)
- 配置段寄存器、堆栈指针
- 设置分页机制(启用虚拟内存)
- 清理未初始化的BSS段
- 设置全局指针、数据指针等CPU寄存器
- 关闭中断或做其他硬件相关初始化
这些操作都只能用汇编完成,因为C环境尚未准备好。
3. start_kernel
的前提
start_kernel()
是用 C 语言写的,它假设运行环境已经稳定:
- 栈已初始化
- 全局变量可以正常访问
- CPU 处于正确模式
- 内存管理子系统已部分就绪
换句话说,start_kernel
是"内核的真正入口",但不是"CPU的入口"。
4. 总结
阶段 | 作用 | 为什么不能直接跳到 start_kernel |
---|---|---|
CPU 上电 → _start 汇编 |
初始化 CPU 寄存器、栈、分页、特权级 | 需要底层硬件初始化,C代码依赖这些,直接跳C会崩溃 |
_start → start_kernel |
进入内核 C 语言环境,启动内核子系统 | 需要完整运行环境,汇编做的准备工作必须先完成 |
简单比喻
汇编入口相当于"内核启动的引导员",负责把环境布置好;
start_kernel
是"内核真正的司机",环境准备好了才上车启动。
好的,讲讲 secondary_start_kernel
的作用和背景。
什么是 secondary_start_kernel
?
- 它是 Linux 内核在多核(SMP)系统中,非主CPU(Secondary CPU) 启动时的入口函数。
- 主CPU(CPU0)执行
start_kernel
来启动整个系统和初始化多核环境。 - 其他CPU启动后,会跳到
secondary_start_kernel
,完成自己的初始化流程。
为什么需要 secondary_start_kernel
?
- 多核CPU启动时,只有主核(CPU0)从复位向量启动,执行完整的内核启动流程。
- 其他核处于停顿状态,需要被唤醒,并单独执行一套简化的初始化。
- 这些核不会执行
start_kernel
,而是执行secondary_start_kernel
,完成自己的CPU初始化和进入调度。
secondary_start_kernel
主要做什么?
- 初始化该CPU的本地数据结构(如CPU寄存器、TSS、IDT等)
- 设置该CPU的中断和调度环境
- 把该CPU加入到内核的调度器中,开始运行任务
- 不重复执行主核完成的系统全局初始化
简单流程示意
主核 (CPU0) 启动 -> 执行 start_kernel -> 初始化系统 -> 唤醒其他核
其他核 (CPU1..N) 启动 -> 执行 secondary_start_kernel -> 初始化本地CPU环境 -> 进入调度循环
位置与调用
secondary_start_kernel
通常定义在init/main.c
- 各架构启动汇编在唤醒其他CPU时会跳转到这个函数
- 是 SMP 初始化的关键环节
总结
函数名 | 作用 | 适用CPU |
---|---|---|
start_kernel |
主核启动,系统初始化 | CPU0(主CPU) |
secondary_start_kernel |
非主核启动,局部CPU初始化和进入调度 | 其他CPU(辅助CPU) |
好的,下面详细介绍 initrd
在 ARM 和 x86 Linux 内核启动流程中的具体实现细节,帮助你理解它是怎么工作的。
一、initrd 的启动流程核心概念
- initrd 是内核启动时内存中的一个临时根文件系统映像。
- 内核启动时先挂载它作为根文件系统,执行初始化工作,之后切换到真实根文件系统。
- 在启动参数中通过内核命令行参数或 bootloader 传递 initrd 的位置和大小。
二、x86 启动流程中的 initrd
1. Bootloader 阶段
- Bootloader(如 GRUB)加载内核镜像
vmlinuz
和 initrd 镜像到内存。 - 将 initrd 起始地址和大小放入内核启动参数(例如内核命令行中的
initrd=0x地址,大小
)。 - 将控制权交给内核。
2. 内核启动汇编阶段
- 在
arch/x86/boot/header.S
等文件,内核启动汇编代码初始化后,会调用内核的 C 入口start_kernel()
。 - 内核解析启动参数,获得 initrd 地址和大小。
3. 内核 C 语言初始化阶段(init/do_mounts.c
)
- 内核根据启动参数调用
setup_initrd()
(2.6 内核及类似版本)。 setup_initrd()
负责把 initrd 映像注册为临时块设备(ramdisk),并挂载为根文件系统(initrd rootfs)。- 调用 initrd 内部的
/init
程序进行系统初始化。
4. 切换到真实根文件系统
- 初始化完成后,
pivot_root()
或switch_root()
将根文件系统切换到硬盘或其他设备的文件系统。 - initrd 被卸载,内核开始正常运行用户空间系统。
三、ARM 启动流程中的 initrd
1. Bootloader 阶段
-
Bootloader(如 U-Boot)将内核镜像(zImage/uImage)和 initrd 加载到内存。
-
U-Boot 通过寄存器(如 r0-r3)传递内核启动参数:
- r2 寄存器通常保存 initrd 的起始地址。
- r3 寄存器保存 initrd 大小。
-
设备树(DTB)也可能携带 initrd 信息。
2. ARM 汇编入口阶段 (arch/arm/boot/bootp/init.S
等)
- 内核启动汇编代码读取这些参数,做内存搬移(前面你看过的
init.S
代码就是搬移 initrd 的例子)。 - 将 initrd 拷贝到合适内存区域,确保后续内核使用。
3. 内核 C 入口 (start_kernel
)
- 解析传入的 initrd 地址和大小,调用
setup_initrd()
。 - initrd 作为临时根文件系统挂载。
- 执行 initrd 内部初始化脚本。
4. 切换根文件系统
- 跟 x86 类似,切换到真实根文件系统,完成启动。
四、内核中与 initrd 相关的关键函数
setup_arch()
:在架构相关初始化时调用,解析启动参数,设置 initrd 信息。setup_initrd()
:注册并挂载 initrd,准备临时根文件系统。mount_root()
:挂载根文件系统时优先使用 initrd,后续切换根。pivot_root()
/switch_root()
:实现根文件系统的切换。
五、简要总结流程图
Bootloader 加载内核和 initrd --> 内核启动汇编(解析initrd参数,搬移initrd)
--> start_kernel(调用setup_initrd,挂载initrd)
--> 执行initrd内的/init程序初始化
--> 切换到真实根文件系统(硬盘/网络文件系统等)
六、相关文件位置(Linux 源码)
功能 | 典型源码路径 |
---|---|
ARM 启动汇编 | arch/arm/boot/bootp/init.S |
x86 启动汇编 | arch/x86/boot/header.S |
initrd 处理 | init/do_mounts.c 、init/do_mounts.h |
根文件系统挂载 | fs/namespace.c |
当然可以,下面是将你提供的 Linux 启动流程(包括 Bootloader、主核、从核、initrd、根文件系统切换等)融合成的一个 Mermaid 序列图:
✅ Mermaid 序列图(Linux 多核启动流程 + initrd 启动)
Bootloader 主核(CPU0) 其他核(CPU1..N) Linux 内核 initrd(临时根文件系统) 真实根文件系统 上电,跳转内核入口(arch/.../head.S) 执行 start_kernel() 初始化内核子系统(时钟、中断、内存、调度器等) 唤醒其他核(SMP) 执行 secondary_start_kernel() 初始化 CPU 本地环境,进入 idle/sched 循环 解析内核参数,包括 initrd 调用 setup_initrd() 解压并挂载 initrd 执行 initrd 中的 /init 程序 完成系统初始化逻辑(例如挂载 dev tmpfs) 切换到真实根文件系统(硬盘/网络等) 执行 /sbin/init(或 systemd) 系统启动完成,进入用户空间 Bootloader 主核(CPU0) 其他核(CPU1..N) Linux 内核 initrd(临时根文件系统) 真实根文件系统
📌 说明
CPU0
是主核,从入口跳入start_kernel
,控制整个启动过程。CPU1..N
是从核,由主核在适当时机唤醒,执行secondary_start_kernel
。Bootloader
通常负责将内核、initrd 以及设备树加载到内存,并设置参数。initrd
是临时根文件系统,内核通过参数挂载它,并执行其中的/init
。/init
会负责进一步初始化和挂载真实根文件系统(如 ext4、NFS 等)。- 最终系统进入真实根文件系统的
/sbin/init
,开始正常运行。