cmdline使用详解

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 生态中最简洁、最强大的运行时配置机制之一。无论是系统管理员调整启动行为,还是嵌入式工程师向驱动传递板级定制信息,都离不开它。熟练掌握其传递、解析与接收方式,能极大提升系统定制化能力和调试效率。

相关推荐
千百元6 分钟前
centos如何删除恶心定时任务
linux·运维·centos
oMcLin2 小时前
如何在Manjaro Linux上配置并优化Caddy Web服务器,确保高并发流量下的稳定性与安全性?
linux·服务器·前端
济6172 小时前
linux(第七期)--gcc编译软件-- Ubuntu20.04
linux·运维·服务器
corpse20103 小时前
Linux监控软件Monitorix 安装部署
linux·安全
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [fs]super
linux·笔记·学习
姚青&3 小时前
四.文件处理命令-文本编辑
linux
oMcLin3 小时前
如何在 Red Hat Linux 8 上实现 Kubernetes 自定义资源管理器(CRD)扩展,支持微服务架构
linux·架构·kubernetes
济6173 小时前
linux(第十一期)--Makefile 语法简述-- Ubuntu20.04
linux
hwlfly3 小时前
Linux内核TCP网络模块深度分析
linux
杜文龙4 小时前
gitlab系统搭建AI代码自动审查多项目可复用架构
linux