您的位置:首页 > 教育 > 培训 > 广州软件制作公司_web网页代码_推广赚钱软件排行_优化设计高中

广州软件制作公司_web网页代码_推广赚钱软件排行_优化设计高中

2025/7/19 5:36:06 来源:https://blog.csdn.net/jingerppp/article/details/147025391  浏览:    关键词:广州软件制作公司_web网页代码_推广赚钱软件排行_优化设计高中
广州软件制作公司_web网页代码_推广赚钱软件排行_优化设计高中

源码基于: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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com