Kernel Command Line 是 Linux 系统启动过程中 bootloader 向 内核 传递配置参数的最重要机制。它是一个以空格分隔的字符串,包含多个"键=值"或单独标志,用于在内核启动的各个阶段影响系统行为、硬件配置、调试选项以及向驱动或用户空间程序传递自定义数据。
该机制在桌面、服务器、嵌入式系统(如 Raspberry Pi、工业控制板)中都广泛使用,尤其在无法动态修改内核镜像的场景下,它是唯一可靠的"运行时配置"方式。
1. 完整工作流程图
Bootloader (GRUB / U-Boot / systemd-boot / Raspberry Pi bootloader 等)
│
▼
构造 cmdline 字符串(如 "root=/dev/mmcblk0p2 rw console=ttyS0,115200 mydata=hello")
│
▼
通过架构约定方式传递给内核
├─ x86:放在特定内存地址或寄存器(%rsi)
├─ ARM64:通常通过 x21 寄存器或 Device Tree /chosen/bootargs 节点
└─ ARM32:ATAG 或 Device Tree
│
▼
内核汇编启动代码(head.S)接收并复制到全局变量
│
▼
C 语言阶段(start_kernel())进行两次解析
├─ 第一次:parse_early_param() → 处理早期参数(内存分配前)
├─ 第二次:parse_args() → 处理所有剩余参数
└─ 未知参数 → 保存并最终传给 init 进程(如 systemd)
│
▼
参数生效:
• 内核内置参数立即生效(root、console、quiet 等)
• 驱动自定义早期参数(__setup)立即生效
• module_param 参数在模块加载时生效
• 其余参数可通过 /proc/cmdline 被用户空间程序读取
2. 内核源码关键位置(基于 Linux 6.x,旧版本如 4.4 结构类似)
(1)全局变量定义(init/main.c)
#define COMMAND_LINE_SIZE 2048
char boot_command_line[COMMAND_LINE_SIZE]; // 当前正在使用的 cmdline
char saved_command_line[COMMAND_LINE_SIZE]; // 备份,用于提供给 /proc/cmdline
char static_command_line[COMMAND_LINE_SIZE]; // 早期静态副本
(2)汇编阶段接收示例
-
x86_64 (arch/x86/kernel/head_64.S)
movq %rsi, %rbp // GRUB 将 cmdline 指针放在 %rsi ... movq boot_params_hdr_command_line(%rip), %rdi movq %rdi, saved_command_line(%rip) -
ARM64 (arch/arm64/kernel/head.S)
// 现代 bootloader 通常将 cmdline 放入 Device Tree /chosen/bootargs // 或直接通过 x21 寄存器传递 adr_l x0, boot_command_line str x21, [x0] -
Device Tree 方式 (常见于嵌入式) 在 .dts 文件中:
dts
chosen { bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw"; };
内核会自动读取该节点填充 boot_command_line。
(3)C 阶段解析(init/main.c → start_kernel())
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
setup_arch(&command_line); // 架构相关处理,可能追加参数
// 第一轮:解析早期参数(在 setup_system 前,内存管理未就绪)
parse_args("early parameters",
static_command_line, __start___param,
0, -1, -1, NULL, &do_early_param);
// 第二轮:解析所有剩余参数
parse_args("Boot options",
boot_command_line, __start___param,
0, 0, 0, NULL, &unknown_bootoption);
// 后续会将剩余未知参数(不含 '.')追加到 init 的环境变量中
}
(4)核心解析函数(kernel/params.c)
-
早期参数注册宏 __setup:
#define __setup(str, fn) \ static const struct obs_kernel_param __setup_##fn \ __used __section(".setup.data") __aligned(1) = \ { str, fn, 1 } -
普通模块参数通过 module_param 系列宏注册,最终也会被 parse_args() 处理。
-
未知参数处理:
static int __init unknown_bootoption(char *param, char *val, const char *unused) { // 如果参数不以 '.' 开头且不是内核已知参数,则保留 // 最终会传给 init 进程 return 0; }
3. 在内核中接收自定义数据的四种方式(从推荐度排序)
方式 1:早期参数(__setup)------ 最推荐用于启动阶段配置
适用于需要在内存管理、控制台等初始化前就生效的参数。
// drivers/custom/my_driver.c
static char *my_custom_data;
static int __init mydata_setup(char *str)
{
pr_alert("=== Bootloader passed mydata: %s ===\n", str);
my_custom_data = str; // 指针由内核管理,不要 kfree
// 可在此处立即配置硬件、设置全局标志等
return 1; // 返回 1 表示参数已处理
}
__setup("mydata=", mydata_setup); // 关键注册宏,必须放在文件全局作用域
方式 2:模块参数(module_param)
适用于非紧急配置,模块加载时才解析。
static char *mydata = "default_value";
static int myflag;
module_param(mydata, charp, 0444); // char 指针
module_param(myflag, int, 0644); // 整数
MODULE_PARM_DESC(mydata, "Custom string data from bootloader");
MODULE_PARM_DESC(myflag, "Custom flag (0 or 1)");
方式 3:直接读取完整 cmdline(适用于任意阶段)
#include <linux/init.h>
#include <linux/printk.h>
static int __init my_driver_init(void)
{
extern char saved_command_line[]; // 声明外部变量
pr_info("Full kernel cmdline: %s\n", saved_command_line);
// 手动查找特定参数(示例)
if (strstr(saved_command_line, "mydata=")) {
// 可用 strsep 或 sscanf 解析
}
return 0;
}
module_init(my_driver_init);
方式 4:用户空间程序读取(最通用)
# 查看完整 cmdline
cat /proc/cmdline
# Shell 脚本解析示例
#!/bin/bash
for param in $(cat /proc/cmdline); do
case $param in
mydata=*)
VALUE="${param#mydata=}"
echo "Bootloader passed mydata: $VALUE"
# 可在此处执行相应配置
;;
debug)
echo "Debug mode enabled"
;;
esac
done
4. 常见 Bootloader 配置详解
| Bootloader | 配置位置 | 永久修改命令示例(添加 mydata=hello) | 临时修改方式 |
|---|---|---|---|
| GRUB2 | /etc/default/grub | GRUB_CMDLINE_LINUX_DEFAULT="quiet splash mydata=hello" sudo update-grub | 启动菜单按 e 编辑 linux 行末尾添加参数 |
| U-Boot | 环境变量 bootargs | setenv bootargs '${bootargs} mydata=hello' saveenv | 命令行临时 setenv bootargs ... 后 boot |
| systemd-boot | /boot/loader/entries/arch.conf 等 | options root=PARTUUID=xxx rw quiet mydata=hello | 编辑 entry 文件 |
| Raspberry Pi | /boot/cmdline.txt(整行即 cmdline) | 在行末添加 mydata=hello(注意空格) | 编辑文件后重启 |
| Limine | limine.cfg | KERNEL_CMDLINE="root=UUID=xxx rw mydata=hello" | 编辑配置文件 |
5. 常见内置参数大全(部分)
| 参数 | 说明 | 示例值 |
|---|---|---|
| root= | 指定根文件系统设备 | /dev/sda1、UUID=xxxx、PARTUUID=xxxx |
| rootfstype= | 根文件系统类型 | ext4、btrfs、xfs |
| rootwait | 等待根设备就绪(常用于 USB/SD 卡) | - |
| console= | 主控制台设备(多个可指定) | ttyS0,115200、tty0 |
| quiet | 抑制大多数启动日志 | - |
| splash | 启用启动画面(plymouth) | - |
| loglevel= | 设置内核日志级别(0-8) | 3 |
| debug | 启用详细调试日志 | - |
| earlyprintk= | 启用早期串口打印(调试用) | serial,ttyS0,115200 |
| init= | 指定 init 进程路径(绕过 systemd) | /bin/sh |
| mem= | 限制内核可用内存 | 1024M |
| noresume | 禁用挂起恢复 | - |
| rd.systemd.show_status=1 | 显示 systemd 启动状态 | - |
6. 注意事项与最佳实践
- 长度限制:一般 1024~2048 字节,超过会被截断。
- 参数顺序:某些参数顺序重要(如 console= 多个时后出现的优先)。
- 引号与空格:带空格的值需用双引号包裹,但 bootloader 支持有限(推荐避免空格)。
- 大小写敏感:参数名区分大小写。
- 安全性:cmdline 内容明文可见于 /proc/cmdline,不要传递密码等敏感信息。
- 调试技巧:添加 debug loglevel=8 earlyprintk=serial 可查看参数解析全过程。
- 嵌入式开发建议:优先使用 __setup 早期参数传递板级硬件差异(如 GPIO 配置、传感器类型)。
总结:Kernel Command Line 是 Linux 生态中最简洁、最强大的运行时配置机制之一。无论是系统管理员调整启动行为,还是嵌入式工程师向驱动传递板级定制信息,都离不开它。熟练掌握其传递、解析与接收方式,能极大提升系统定制化能力和调试效率。