内核启动参数基础:格式与传递方式

说实话,很多人一上来就研究那些复杂的启动参数,结果连最基本的格式都没搞明白。我见过不少同事,在 grub 配置文件里写参数时,少了个等号或者多了个空格,系统直接起不来。嗯,今天我们就从最基础的东西说起。

启动参数长什么样?

内核启动参数,说白了就是一组键值对。你想想看,内核启动时就像一个人刚睡醒,需要知道今天要干什么——从哪个硬盘启动?要不要安静模式?内存怎么分配?这些信息就靠启动参数传给它。

基本格式很简单:

参数名=参数值
参数名1=值1 参数名2=值2
参数名(不带值的布尔参数)

举个例子:

root=/dev/sda1 ro quiet splash

这行参数的意思是:根文件系统在 /dev/sda1,以只读方式挂载,启动时安静模式,显示开机画面。每个参数用空格隔开,就这么简单。

核心要点:参数之间用空格分隔,参数名和值之间用等号连接。布尔型参数(如 quiet)不需要等号,出现即生效。

参数的类型

我个人习惯把启动参数分成三类。这样记起来比较清楚:

类型 格式 示例 说明
键值对 key=value root=/dev/sda1 最常用,指定具体值
布尔开关 key quiet 出现即启用,不出现即禁用
带参数的开关 key=val1,val2 console=tty0,ttyS0 逗号分隔多个值

我在项目中遇到过一种情况:有人把 root=/dev/sda1 写成了 root = /dev/sda1(等号两边加了空格)。结果内核把 root 当成布尔参数,把 /dev/sda1 当成另一个参数。系统当然起不来。这种坑,踩过一次就记住了。

参数怎么传给内核?

内核本身不直接跟用户交互。它启动的时候,需要一个「中间人」来告诉它这些参数。这个中间人就是 bootloader。

目前主流的 bootloader 就两个:

  • GRUB2:Linux 世界的事实标准,几乎所有的发行版都在用
  • U-Boot:嵌入式设备、开发板上的常客

它们的传递方式不太一样,但核心逻辑是一样的——把参数字符串放在内存的某个位置,然后告诉内核去哪里找。

GRUB2 的传递方式

GRUB2 的配置文件在 /boot/grub/grub.cfg。不过我不建议你直接改这个文件,因为它是由 update-grub 命令自动生成的。正确的做法是改 /etc/default/grub。

关键变量就一个:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"

这个变量里的内容,会被 GRUB2 自动拼接到内核启动命令行上。你改完之后,运行 update-grub 让它生效。

小技巧:临时调试时,不用改配置文件。在 GRUB2 启动菜单界面,按 e 键编辑当前启动项,找到 linux 开头的那一行,直接在末尾加参数。按 Ctrl+X 或 F10 启动。这个修改只对本次启动有效,重启后恢复原样。

U-Boot 的传递方式

嵌入式开发中,U-Boot 更常见。它的方式更直接——通过环境变量:

setenv bootargs 'root=/dev/mmcblk0p2 ro console=ttyS0,115200'
bootm 0x80000000

这里 bootargs 就是 U-Boot 用来存放启动参数的环境变量。内核启动时,U-Boot 会把这个变量的内容传给内核。

我曾经调试一块 ARM 开发板,串口怎么都不输出信息。查了半天,发现是 console=ttyS0,115200 写成了 console=ttyS0,没指定波特率。内核用了默认的 9600,跟终端对不上。嗯,这种细节不注意,真的会浪费一整天。

参数传递的底层原理

你可能想问:bootloader 到底是怎么把参数传给内核的?

其实就两种方式:

  1. ATAG 列表(老方式,ARM 架构常见)
  2. 设备树(Device Tree)(新方式,现在的主流)

GRUB2 用的是 x86 特有的 setup_header 结构,内核在 arch/x86/boot/header.S 里定义了一个固定格式的内存区域,bootloader 把参数填进去,内核启动时直接读这块内存。

U-Boot 则更灵活,它把 bootargs 的内容放到设备树的 /chosen/bootargs 属性里,内核从设备树中读取。

注意:不同架构的传递方式有差异。x86 用 setup_header,ARM/ARM64 用设备树,RISC-V 也走设备树。写参数时,要确认你的平台用的是哪种方式。不过好消息是,参数本身的语法是统一的,不管底层怎么传,你写的字符串格式都一样。

一张图看懂整体流程

下面这张图展示了从你写下参数到内核真正用上它的完整路径:

启动参数传递流程 用户输入参数 配置文件 /etc/default/grub GRUB2 解析并拼接参数 写入 setup_header 内存区 内核启动时读取并解析 临时调试方式 GRUB菜单按 e 编辑 直接修改 linux 行 仅本次启动有效 嵌入式场景 U-Boot 使用 bootargs 通过设备树传递 setenv 临时修改 图:启动参数从用户输入到内核解析的完整路径

常见误区

我总结几个新手最容易犯的错误:

  • 等号两边加空格:root = /dev/sda1 是错的,必须写成 root=/dev/sda1
  • 参数名拼写错误:比如把 console 写成 consol,内核直接忽略
  • 忘记更新 grub 配置:改了 /etc/default/grub 但不跑 update-grub,重启后没变化
  • 参数顺序搞混:参数之间用空格,不是逗号也不是分号

曾经我踩过的坑:有一次我在 /etc/default/grub 里加了 GRUB_CMDLINE_LINUX_DEFAULT="quiet splash mem=4G",结果系统只认到 3.5G 内存。查了半天才发现,应该用 mem=4096M 而不是 mem=4G。内核解析 G 这个单位的方式跟我想的不一样。从那以后,我所有内存参数都老老实实写 M 为单位。

小结

启动参数的格式其实不复杂。记住三点:

  1. 参数名=值,布尔参数直接写名字
  2. 空格分隔,等号两边不要空格
  3. 通过 bootloader 传递,GRUB2 改 /etc/default/grub,U-Boot 改 bootargs

掌握了这些,你就已经迈出了第一步。后面我们会深入具体的参数,看看每个参数到底能干什么。