源码基于:Linux 5.4
约定:
- 芯片架构:ARM64
- 内存架构:UMA
- CONFIG_ARM64_VA_BITS:39
- CONFIG_ARM64_PAGE_SHIFT:12
- CONFIG_PGTABLE_LEVELS :3
0. 前言
在《fixmap详解》一文中看到在 fixmap 会在内核系统中预留一段虚拟内存用以固定映射,并通过函数 setup_machine_fdt() 对 dtb 内核区域进行映射和解析,其中包括内核的启动参数boot_command_line。
本文将剖析系统启动参数的实现、解析过程,大致分为:
- 启动参数的定义;
- 启动参数的加载;
- 启动参数的配置;
1. __setup_param()
include/linux/init.h#define __setup_param(str, unique_id, fn, early) \static const char __setup_str_##unique_id[] __initconst \__aligned(1) = str; \static struct obs_kernel_param __setup_##unique_id \__used __section(.init.setup) \__attribute__((aligned((sizeof(long))))) \= { __setup_str_##unique_id, fn, early }#define __setup(str, fn) \__setup_param(str, fn, fn, 0)#define early_param(str, fn) \__setup_param(str, fn, fn, 1)
系统通过 __setup_param() 定义系统的启动参数,并对启动参数进行更细的区分:
- early 启动参数
- obsolete 启动参数
当需要在很早就需要的启动参数,需要在定义的时候使用 early_param() 函数,例如:
mm/memblock.cstatic int __init early_memblock(char *p)
{if (p && strstr(p, "debug"))memblock_debug = 1;return 0;
}
early_param("memblock", early_memblock);
这些 early 参数会在系统很早就需要使用,通常是在 dtb 区域读取完成后就会进行解析,比 memblock 初始化还要早。
其他的 非 early 启动参数,本文称其为 "obsolete" 启动参数,通常会在 setup_arch() 执行完成后解析:
mm/slub.cstatic int __init setup_slub_debug(char *str)
{...return 1;
}__setup("slub_debug", setup_slub_debug);
early 启动参数使用的是 early_param() 函数定义的,所以调用 __setup_param() 时,最后一个参数为 1,也会最终保存到 obs_kernel_param 结构体中的 early 变量中。obsolete 启动参数,对应结构体中的 early 成员为 0.
这个宏定义了两个静态变量:
- __setup_str_##unique_id: 存储启动参数字符串,类型为 const char[],并使用 __aligned(1) 确保对齐;
- __setup_##unique_id: 是一个
struct obs_kernel_param
类型的结构体,存储在.init.setup
段中,并使用 __attribute__((aligned((sizeof(long))))) 确保对齐。该结构体包含启动参数字符串指针、处理函数指针和早期处理标志。
1.1 struct obs_kernel_param
include/linux/init.hstruct obs_kernel_param {const char *str;int (*setup_func)(char *);int early;
};
每个启动参数都会定义一个 setup_func callback,当检测满足条件时就会通过 param 调用该callback。
2. 启动参数加载
如上,启动参数分为 early 和 obsolete,在不同的时机、不同的流程中加载。
2.1 parse_early_param()
该函数针对 early param:
arch/arm64/kernel/setup.cvoid __init setup_arch(char **cmdline_p)
{init_mm.start_code = (unsigned long) _text;init_mm.end_code = (unsigned long) _etext;init_mm.end_data = (unsigned long) _edata;init_mm.brk = (unsigned long) _end;*cmdline_p = boot_command_line;early_fixmap_init();early_ioremap_init();setup_machine_fdt(__fdt_pointer);/** Initialise the static keys early as they may be enabled by the* cpufeature code and early parameters.*/jump_label_init();parse_early_param();...
}
从《fixmap 详解》一文中第 5 节,可知通过 fdt 会将 /proc/devicetree/bootargs 读取到boot_command_line 中。
当读取完 dtb 之后就直接调用 parse_early_param() 函数对 early 参数进行解析。
init/main.c/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{static int done __initdata;static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;if (done)return;/* All fall through to do_early_param. */strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);parse_early_options(tmp_cmdline);done = 1;
}
最终调用 parse_early_options() 函数对boot_command_line 中的 early 参数进行配置。
parse_early_options
|-->parse_args|-->parse_one //分解参数,一个一个解析|-->do_early_param //轮询所有cmdline中的early 启动参数|-->p->setup_func //满足要求则调用定义时的setupcallback
parse_args() 函数会拆解所有的参数,并调用 parse_one() 分别解析。但对于 early 参数会直接调用 do_early_param() 函数:
init/main.cstatic int __init do_early_param(char *param, char *val,const char *unused, void *arg)
{const struct obs_kernel_param *p;for (p = __setup_start; p < __setup_end; p++) {if ((p->early && parameq(param, p->str)) ||(strcmp(param, "console") == 0 &&strcmp(p->str, "earlycon") == 0)) {if (p->setup_func(val) != 0)pr_warn("Malformed early option '%s'\n", param);}}/* We accept everything at this stage. */return 0;
}
2.2 parse_args("Booting kernel",...)
该函数针对 obsolete 参数:
init/main.casmlinkage __visible void __init start_kernel(void)
{char *command_line;char *after_dashes;...setup_arch(&command_line);setup_command_line(command_line);setup_nr_cpu_ids();setup_per_cpu_areas();smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */boot_cpu_hotplug_init();build_all_zonelists(NULL);page_alloc_init();pr_notice("Kernel command line: %s\n", boot_command_line);/* parameters may set static keys */jump_label_init();parse_early_param();after_dashes = parse_args("Booting kernel",static_command_line, __start___param,__stop___param - __start___param,-1, -1, NULL, &unknown_bootoption);...
}
可以看到 非early 启动参数会在 build_all_zonelist() 函数之后执行。调用 parse_args() 并标记此次为 "Booting kernel",详细调用流程如下:
parse_args
|-->parse_one //分解参数,一个一个解析|-->unknown_bootoption|-->obsolete_checksetup //非early 的启动参数|-->p->setup_func
parse_args() 函数会拆解所有的参数,并调用 parse_one() 分别解析。但对于 obsolete 参数会直接调用 unkown_bootoption() 函数:
init/main.cstatic int __init unknown_bootoption(char *param, char *val,const char *unused, void *arg)
{repair_env_string(param, val, unused, NULL);/* Handle obsolete-style parameters */if (obsolete_checksetup(param))return 0;/* Unused module parameter. */if (strchr(param, '.') && (!val || strchr(param, '.') < val))return 0;if (panic_later)return 0;...
}
在函数 obsolete_checksetup() 函数中对 obsolete 启动参数进行配置:
init/main.cstatic bool __init obsolete_checksetup(char *line)
{const struct obs_kernel_param *p;bool had_early_param = false;p = __setup_start;do {int n = strlen(p->str);if (parameqn(line, p->str, n)) {if (p->early) {/* Already done in parse_early_param?* (Needs exact match on param part).* Keep iterating, as we can have early* params and __setups of same names 8( */if (line[n] == '\0' || line[n] == '=')had_early_param = true;} else if (!p->setup_func) {pr_warn("Parameter %s is obsolete, ignored\n",p->str);return true;} else if (p->setup_func(line + n))return true;}p++;} while (p < __setup_end);return had_early_param;
}
3. 总结
本文主要剖析了Linux 内核启动参数的加载和配置,首先在 bootloader 会设置启动参数,并将 dtb 的物理内存地址传入到内核,在 start_kernel() 时会调用 setup_machine_fdt() 函数对 dtb 内存进行解读,本文关心的启动参数也在此时读入 boot_command_line 中。
在 setup_machine_fdt() 结束会调用 parse_early_param() 对 early 类型的启动参数进行加载并调用 p->setup_func() 函数配置到对应模块中。
在setup_arch() 之后的某个时机 (不同内核版本不一样) 会调用 parse_args() 函数继续解析 obsolete 类型的启动参数,并调用 p->setup_func() 函数配置到对应模块中。
参考:
https://justinwei.blog.csdn.net/article/details/132969069
https://justinwei.blog.csdn.net/article/details/130100621