Linux 启动传参
一、启动参数来源
Linux 内核的启动参数主要来源于两个渠道:Bootloader 和 设备树(Device Tree Blob, DTB)。
Bootloader(U-Boot 为例)
在 ARM/ARM64 平台上,U-Boot 在启动内核前完成三项工作:
设置内核入口地址(
zImage
或Image
)。准备设备树(FDT)或 ATAGS 结构。
传递启动参数字符串,通常通过
bootargs
环境变量。
例如,在 U-Boot 中设置 bootargs 并启动内核:
setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait" bootz ${kernel_addr} - ${fdt_addr}
U-Boot 会把该字符串传递给内核。老式 ARM 使用 ATAGS 结构,新式 ARM64 使用 FDT 中 /chosen
节点。
Device Tree Blob(DTB)
DTB 中 /chosen
节点也可以定义启动参数,例如:
/ { chosen { bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait"; stdout-path = "serial0:115200n8"; }; };
内核启动时,drivers/of/fdt.c
中的 early_init_dt_scan_chosen()
会解析 /chosen
节点,把 bootargs 拷贝到内核命令行。DTB 还可以定义 initrd 的起止地址、early console 配置等。
总结:U-Boot 和 DTB 最终传递给内核的都是空格分隔的参数字符串,格式相同,内核解析不区分来源。
二、启动参数优先级
Linux 内核接收启动参数时遵循以下优先级:
U-Boot bootargs:如果设置了 bootargs,则覆盖 DTB 中的 bootargs。
DTB
/chosen/bootargs
:U-Boot 未传递参数时使用。内核编译时 CONFIG_CMDLINE:为默认参数,当 U-Boot 和 DTB 都没有时使用。
CONFIG_CMDLINE_FORCE:强制使用内核自带参数,覆盖 U-Boot 和 DTB。
开发阶段常在 DTS 中提供最小可启动 bootargs,产品阶段通过 U-Boot 动态传递参数更灵活。
三、内核解析流程
内核命令行存储
内核入口 init/main.c
中全局数组保存启动参数:
static char __initdata command_line[COMMAND_LINE_SIZE];
DTB 参数解析
DTB /chosen
节点的解析在 drivers/of/fdt.c
的 early_init_dt_scan_chosen()
函数中完成,包括:
拷贝 bootargs 到内核命令行
读取 initrd 起止地址
配置 early console
参数解析机制
内核解析启动参数分为三类:
早期参数(early_param):在内核初始化早期处理,如
console=
,保证日志输出可用。
普通参数(__setup):在后续初始化中解析,用于挂载根文件系统、启用模块功能等。
这里都是解析参数中心,可以定义自己关注的key
模块参数(module_param):供加载模块解析启动参数。
示例:console 参数解析
根文件系统参数 root=
的解析位于 init/do_mounts.c
的 root_dev_setup()
,initramfs 的 init 解析在 init/initramfs.c
中完成。
四、启动参数格式
空格分隔:key=value 或单独 flag
示例:
console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait earlyprintk
DTS 中为 FDT blob,字符串用双引号并以分号结束。
U-Boot 中为 shell 风格字符串。
内核解析时不区分来源。
五、常用经典参数
控制台与调试参数:
console=ttyS0,115200
:指定内核日志输出到串口
earlycon
:启用 early consoleloglevel=7
:设置内核日志等级
根文件系统参数:
root=/dev/mmcblk0p2
:指定根文件系统设备rootfstype=ext4
:指定根文件系统类型rootwait
:等待根设备准备就绪init=/sbin/init
:覆盖默认 init 程序rdinit=/linuxrc
或rdinit=/init
:指定 initramfs 内的 init
Initramfs / Initrd 参数:
initrd=<start>,<size>
:initrd 内存起止地址initrd=0x900000,9M
rdinit=/init
:initramfs 的 initnoinitrd
:禁止使用 initrd/initramfs
网络与 NFS 参数:
ip=dhcp
:通过 DHCP 获取 IPnfsroot=<server>:/path
:指定 NFS 根路径
内存与调试参数:
mem=512M
:限制可用内存panic=5
:内核崩溃后自动重启时间
六、rdinit 与 init 对比
在启动流程中,init
与 rdinit
都用于指定用户态初始化程序,但存在区别:
init
指定内核挂载完成根文件系统后执行的初始化程序
常见值
/sbin/init
或/lib/systemd/systemd
解析位置:
init/main.c
中init_task
设置,并通过do_basic_setup()
调用
rdinit
指定 initramfs / initrd 内部的初始化程序
内核在挂载 initramfs 后立即执行
rdinit
指定程序优先于根文件系统 init 执行
解析位置:
init/initramfs.c
中initramfs_load()
和populate_rootfs()
总结:rdinit
用于早期用户态(initramfs),在根文件系统可用之前执行,Linux 内核内置的一种 cpio 打包格式,会被解压到一个 ramfs 或 tmpfs 上,作为临时的 rootfs;init
用于根文件系统挂载完成后启动用户态初始化。
七、启动参数总结
Linux 启动参数形成了完整链条:U-Boot 设置 bootargs 或 DTB /chosen/bootargs
→ 内核命令行 command_line[]
→ parse_early_param()
/ __setup()
→ 各子系统应用,包括 console、rootfs、网络配置、调试等。优先级遵循 U-Boot > DTB > 内核默认,CONFIG_CMDLINE_FORCE 可覆盖一切。
rdinit
与 init
分别控制早期用户态与正式根文件系统用户态初始化,为嵌入式系统提供灵活的启动策略。
八、启动过程中切换文件系统
Linux 启动的核心点在 switch_root
/ pivot_root
:
内核初始化时,挂载 initramfs → 作为 rootfs。
执行 initramfs 中的
/init
脚本或二进制。在
/init
里面通常会做:挂载真正的根文件系统(例如 eMMC 上的 ext4,UFS,NAND,NFS root 等)。
执行
switch_root /newroot /init
,把 rootfs 从 initramfs 切换到真正的存储设备。或者执行
pivot_root
实现类似效果。
switch_root
的作用:
把新的文件系统挂到
/
。把原来的 initramfs 挂到某个临时目录(通常是
/old_root
),再卸载释放。
源码入口:
init/do_mounts_initrd.c
(旧 initrd 方案)init/do_mounts.c
(rootfs 挂载)用户态执行
/init
(busybox 里常见switch_root
工具)。
如果系统 不具备持久化存储(例如FPGA原型验证),完全可以只依赖内存里的 initramfs 来跑。
此时就不会调用 switch_root
,而是直接在 initramfs 的 /init
里面启动服务(例如 shell、应用程序)。
代价:
所有数据都在内存中,掉电即失。
内存占用不可释放,因为 ramfs 不支持 swap-out,initramfs 的文件会一直常驻内存。