内核启动时,bootloader
会通过命令行传递各种参数。early_param
是内核提供的一种机制,用于在启动的早期阶段解析和处理这些参数。
这些参数通常是 bootloader(如 UEFI、GRUB)通过命令行传递给内核的,例如:
1 | # grub/grub.conf 或内核命令行 |
与普通的内核模块参数不同,early_param
处理器会在内核初始化的非常早期被调用,此时很多子系统(slab、工作队列等)还未初始化完成,因此对代码有严格限制。
应用场景
early_param
主要用于那些必须在内核启动早期就确定的配置项:
| 场景 | 说明 |
|---|---|
| 控制台配置 | 在 printk 初始化前就确定输出设备(串口、tty) |
| 内存布局调整 | 保留内存区域、设置内存大小限制 |
| 调试选项 | 开启早期调试输出、设置日志级别 |
| 设备树定位 | 指定设备树的物理地址 |
| 虚拟化支持 | earlyprintk、earlycon 等早期调试手段 |
| 架构特定配置 | ARM64 的页表配置、CPU 模式等 |
为什么需要?
如果用普通内核模块参数或 sysfs,处理时机太晚:
- 控制台:printk 初始化后才发现要改串口波特率,之前的关键启动信息就丢失了
- 内存保留:内存管理器初始化后,无法再从总内存中"挖"出一块保留区域
- 设备树:设备树解析必须在内存管理初始化之前完成
使用方法
基本用法
使用 early_param 宏注册参数处理函数:
1 |
|
命令行使用示例:
1 | # 传递值 |
参数宏对比
| 宏 | 用途 | 处理时机 | 可用函数 |
|---|---|---|---|
early_param |
通用早期参数 | parse_args() 阶段 | 有限(无 slab、kmalloc 等) |
early_param_on_off |
on/off 类型参数 | parse_args() 阶段 | 有限 |
setup_param |
延迟到稍晚阶段 | start_kernel() 中 | 稍多一点 |
module_param |
内核模块参数 | 模块加载时 | 全部可用 |
处理 on/off 类型参数
1 | static bool my_feature_enable = false; |
限制和注意点
不能使用 kmalloc/kfree
早期阶段内存分配器未初始化,只能使用: - 静态变量 - 栈上内存 - 特殊的
memblock 分配器(如果已初始化)
1 | /* 错误:早期不可用 */ |
不能使用 printk 的完整功能
早期的 printk 可能还未初始化串口,建议使用
pr_info、pr_debug
等宏,它们会缓冲输出直到控制台可用。
避免依赖其他子系统
不要依赖网络、文件系统、工作队列等功能,它们都还没初始化。
工作原理
在内核启动流程中的位置
1 | bootloader |
parse_args() 会遍历命令行,对每个参数查找注册的
early_param 处理器并调用。
底层实现
1 | /* include/linux/init.h */ |
所有 early_param 宏定义的处理器会被放入特殊的 ELF 段
.init.data 的 __param
段中,启动时由内核遍历此段。
与普通参数的对比
1 | /* early_param:在 parse_args() 阶段处理 */ |
实际案例:earlycon
1 | /* 驱动/serial/earlycon.c */ |
命令行: 1
earlycon=uart,mmio,0x9000000,115200n8
这样可以在内核完全初始化串口驱动之前,就通过简单的 MMIO 直接向 UART 寄存器写入数据,实现早期调试输出。