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

相关推荐
HLC++3 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室5 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy7 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕8 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫9 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生9 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
维基框架10 小时前
汽车座舱开发上云 Google的Arm实例解决了什么实际问题
arm开发·人工智能·汽车
imc.1110 小时前
linux基础IO
linux·运维·服务器
BelongPanda12 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯13 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算