随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。
想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。
很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。
同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。
既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来
,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜。
本系列博客所述资料均来自互联网资料
,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法
。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。
目前各平台默认关闭dump设置,如果要抓dump,需要通过AT+QCFG=”ApRstLevel“,0和AT+QCFG=“ModemRstLevel”,0命令设置使能;有些情况下模块在启动过程中发生dump,而由于此时AT不通无法通过AT设置dump使能,因此需要有手段在系统启动早期可以使能dump,方便问题调试。
想要做到不修改代码,并且需要在系统启动的早期灵活的配置DUMP功能。可以在bootload启动的过程中通过检测keypad的ctrl+c中断开机过程,随后键盘输入dump命令。随后在kernel加载的过程中通过cmdline传入相关的启动参数,开启模块的dump功能。
cmdline是uboot引导内核启动时传递给内核的参数序列,作用是指导内核启动。内核启动阶段会去内存中读取并解析cmdline,并根据cmdline去指导内核启动。
cmdline是一系列启动参数组成的参数序列,参数之间用空格隔开依次排列,每个参数中都是以key=value这种键值对的方式进行描述; 。参数中一般包含启动存储介质、文件系统分区及挂载方式和终端串口等参数。
linux内核的cmdline配置有两种方式,分别是:
chosen 针对第二点描述的直接修改内核参数,可参阅设备树中的bootargs的定义,bootargs参数被包含在chosen节点中。kernel加载时就是读取chosen中的bootargs,该文件中定义的bootargs和bootload传递过来的bootargs数据最终会合并在一起。
//设备树文件位于\kernel\msm-5.4\arch\arc\boot\dts\nsim_700.dts
chosen {
bootargs = "earlycon=arc_uart,mmio32,0xc0fc1000,115200n8 console=ttyARC0,115200n8 print-fatal-signals=1";
};
我们常用的方式是在bootload中通过bootargs直接传递。
在shell中我们可以通过cat /proc/cmdline
来查看 bootload给 kernel 传递的 cmdline参数内容。
/ # cat /proc/cmdline
console=ttyMSM0,115200,n8
androidboot.hardware=qcom
msm_rtb.filter=0x237
androidboot.console=ttyMSM0
lpm_levels.sleep_disabled=1
firmware_class.path=/lib/firmware/updates
service_locator.enable=1
net.ifnames=0
atlantic_fwd.rx_ring_size=1024
pci=pcie_bus_perf
rootfstype=ubifs
rootflags=bulk_read
root=ubi0:rootfs
ubi.mtd=28 ubi.mtd=25
androidboot.serialno=4792e506
androidboot.baseband=msm
data_interface=[0,0]
ql_pcie_mode=0
ql_crash_mode=0,0
在本例描述的应用中只要在cmdline中包含ql_crash_mode=0,0
字段,kernel启动后就会使用相应的函数设置标志位,使得模块在出现dump现象时,自动进入dump文件上载模式。
关于上述功能开发代码中的处理很简单,只需要在BootLinux函数中修改即可,往BootParamlistPtr.CmdLine
变量后面粘贴一个字符串(ql_crash_mode=0,0
)。在UpdateCmdLine函数中会对一系列(很多很多)的cmdline进行预处理。
EFI_STATUS BootLinux (BootInfo *Info)
{
xxxxxxxxxx
#ifdef QL_DEBUG_MODE
if ( Info->BootWithDebug == TRUE){
AsciiStrCatS (BootParamlistPtr.CmdLine, BOOT_ARGS_SIZE, " debug=1");
}
#endif
#ifdef QL_DUMP_MODE
if ( Info->BootWithDump == TRUE){
AsciiStrCatS (BootParamlistPtr.CmdLine, BOOT_ARGS_SIZE, " ql_crash_mode=0,0");
}
#endif
Status = UpdateCmdLine (BootParamlistPtr.CmdLine, FfbmStr, Recovery,
AlarmBoot, Info->VBCmdLine, &BootParamlistPtr.FinalCmdLine,
&BootParamlistPtr.FinalBootConfig,
&BootParamlistPtr.FinalBootConfigLen,
Info->HeaderVersion,
(VOID *)BootParamlistPtr.DeviceTreeLoadAddr);
xxxxxxxxxx
Status = LoadAddrAndDTUpdate (Info, &BootParamlistPtr);
if (Status != EFI_SUCCESS) {
return Status;
}
xxxxxxxxxx
}
UpdateCmdLine处理完成后,由LoadAddrAndDTUpdate携带BootParamlistPtr参数经过后续一系列调用后,最终会在UpdateDeviceTree 函数中将cmdline中记录的所有数据全部添加到bootargs的设备节点中。
EFI_STATUS UpdateDeviceTree (VOID *fdt,CONST CHAR8 *cmdline,VOID *ramdisk,UINT32 RamDiskSize,BOOLEAN BootWith32Bit)
{
xxxxxxxxxxxx
if (cmdline) {
/* Adding the cmdline to the chosen node */
FdtPropUpdateFunc (fdt, offset, (CONST char *)"bootargs",
(CONST VOID *)cmdline, fdt_appendprop_string, ret);
if (ret) {
DEBUG ((EFI_D_ERROR,
"ERROR: Cannot update chosen node [bootargs] - 0x%x\n", ret));
return EFI_LOAD_ERROR;
}
}
xxxxxxxxxxxx
}
bootload传输过来cmdline之后,我们只需要在适当的位置添加内核参数的处理函数。假如bootloader在启动kernel时传递的参数是"ql_crash_mode=xxx"
,kernel运行的时候就会自动调用set_crash_mode函数,通过s参数
将cmdline携带的参数带入函数。
static int __init set_crash_mode(char *s)
{
xxxxxxxxxx
}
__setup("ql_crash_mode=", set_crash_mode);
__setup
是一个宏定义,可以简单的理解为在编译的时候在程序中注册了set_crash_mode
函数,并将cmdline字符"ql_crash_mode="
和set_crash_mode
绑定在了一起。
程序在加载内核的时候会自动解析并遍历cmdline命令列表,一旦在cmdline参数列表中找到"ql_crash_mode="
字符串,内核就会自动调用set_crash_mode函数。具体怎么实现的在本文的后续章节中会有详细描述,这里不多做解释。
宏定义__setup
被定义在kernel/msm-4.14/include/linux/init.h
文件中。
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
#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) \ //early_param 宏注册的内核选项必须要在其他内核选项之前被处理。
__setup_param(str, fn, fn, 1)
将上述__setup("ql_crash_mode=", set_crash_mode);
直接展开,可得到下面的声明。
static const char __setup_str_set_crash_mode[]
__initconst __aligned(1)
= "ql_crash_mode=";
static struct obs_kernel_param __setup_set_crash_mode
__used __section(.init.setup) __attribute__((aligned((sizeof(long)))))
= {
__setup_str_set_crash_mode,
set_crash_mode,
0
}
__section
关键字会将被修饰的变量或函数编译到特定的一块位置,不是物理存储器上的特定位置,而是在可执行文件的特定段内。.init.setup
就是一个kernel定义的很普通的代码段,里面存放的都是obs_kernel_param
类型的结构体变量。
__attribute__
关键词和 aligned
关键词一起使用,可以指定对象的对齐格式。该结构体变量内部成员的长度全部被强制设定为4字节对齐。
简单来讲上面的宏展开后,就是定义了一个 obs_kernel_param
结构体变量,它的名字叫做__setup_set_crash_mode
,里面有三个成员,
分别是名为__setup_str_set_crash_mode
的字符数组 、函数指针set_crash_mode
、 和一个叫做early的变量。在完成编译后该变量被存放进了.init.setup
段。
Linux内核在加载(start_kernel)的过程中setup_arch
函数会调用到setup_machine_fdt
函数传入__atags_pointer
参数。__atags_pointer
这个全局变量存储的就是r2的寄存器值,是设备树在内存中的起始地址。
//函数位于kernel\msm-5.4\arch\arm\kernel\devtree.c
const struct machine_desc * __init setup_machine_fdt(void *dt_virt)
{
xxxxxxxxxxxxxx
if (!dt_virt || !early_init_dt_verify(dt_virt))
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
xxxxxxxxxxxxxx
early_init_dt_scan_nodes();
}
//函数位于kernel\msm-5.4\arch\arm\kernel\setup.c
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc = NULL;
void *atags_vaddr = NULL;
if (__atags_pointer)
atags_vaddr = FDT_VIRT_BASE(__atags_pointer);
setup_processor();
if (atags_vaddr) {
mdesc = setup_machine_fdt(atags_vaddr);
if (mdesc)
memblock_reserve(__atags_pointer,fdt_totalsize(atags_vaddr));
}
xxxxxxxxxxx
parse_early_param();
xxxxxxxxxxx
}
//函数位于kernel\msm-5.4\init\main.c
asmlinkage __visible void __init start_kernel(void)
{
xxxxxxxxxxxxx
setup_arch(&command_line);
setup_command_line(command_line);
xxxxxxxxxxxxx
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);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
xxxxxxxxxxxxx
}
随后跳转到early_init_dt_scan_nodes函数,调用of_scan_flat_dt函数完成对设备树地址(dt_virt)的扫描,扫描过程中查找chosen节点,找到后在节点内提取bootargs参数。并将其拷贝到cmdline。(chosen节点就表示bootloader传递给kernel的cmdline)
//函数位于kernel\msm-5.4\drivers\of\fdt.c
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname, int depth, void *data)
{
xxxxxxxx
if (depth != 1 || !cmdline ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
/* Put CONFIG_CMDLINE in if forced or if data had nothing in it to start */
if (overwrite_incoming_cmdline || !cmdline[0])
strlcpy(cmdline, config_cmdline, COMMAND_LINE_SIZE);
/* Retrieve command line unless forcing */
if (read_dt_cmdline)
p = of_get_flat_dt_prop(node, "bootargs", &l);
xxxxxxxx
if (p != NULL && l > 0) {
if (concat_cmdline) {
int cmdline_len;
int copy_len;
strlcat(cmdline, " ", COMMAND_LINE_SIZE);
cmdline_len = strlen(cmdline);
copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
copy_len = min((int)l, copy_len);
strncpy(cmdline + cmdline_len, p, copy_len);
cmdline[cmdline_len + copy_len] = '\0';
} else {
strlcpy(cmdline, p, min(l, COMMAND_LINE_SIZE));
}
}
pr_debug("Command line is: %s\n", (char*)data);
}
int __init of_scan_flat_dt(int (*it),void *data)
{
const void *blob = initial_boot_params;
const char *pathp;
int offset, rc = 0, depth = -1;
if (!blob)
return 0;
for (offset = fdt_next_node(blob, -1, &depth);offset >= 0 && depth >= 0 && !rc;offset = fdt_next_node(blob, offset, &depth))
{
pathp = fdt_get_name(blob, offset, NULL);
if (*pathp == '/')
pathp = kbasename(pathp);
rc = it(offset, pathp, depth, data);
}
return rc;
}
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
/* Retrieve various information from the /chosen node */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
if (!rc)
pr_warn("No chosen node found, continuing without\n");
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
cmdline指向一个全局变量boot_command_line需要注意的是boot_command_line是一个静态数组,我们的代码在arm的环境下是4096。也就是说bootloader传递给kernel的cmdline超过4096就要修改kernel源代码中COMMAND_LINE_SIZE的定义 。一旦出现cmdline长度大于boot_command_line数组长度的情况,轻则会存在参数无法正常传递的现象,严重的话可能会导致kernel dump(具体什么现象我没有测试)。
//定义位于\kernel\msm-5.4\init\main.c
/* Untouched command line saved by arch-specific code. */
char __initdata boot_command_line[COMMAND_LINE_SIZE];
//定义位于kernel\msm-5.4\arch\arm\include\uapi\asm\setup.h
#ifndef _UAPI__ASMARM_SETUP_H
#define _UAPI__ASMARM_SETUP_H
#include <linux/types.h>
#define COMMAND_LINE_SIZE 4096
xxxxxx
#endif
linux5.4.180的kennel代码中arm定义为1024字节。
//
//定义位于arch\arm\include\uapi\asm\setup.h
#ifndef _UAPI__ASMARM_SETUP_H
#define _UAPI__ASMARM_SETUP_H
#include <linux/types.h>
#define COMMAND_LINE_SIZE 1024
xxxxxx
#endif
在内核加载的过程中会通过setup_arch调用到parse_early_param函数,在随后函数中会调用parse_args对boot_command_line变量进行解析。
//函数位于kernel\msm-5.4\init\main.c
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
do_early_param);
}
/* 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)//注意这个done flag,在一次启动过程中,该函数可能会被多次调用,但只会执行一次
return;//因为结尾将done设为1,再次执行时会直接return
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
//函数位于kernel\msm-5.4\arch\arm\kernel\setup.c
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc = NULL;
void *atags_vaddr = NULL;
if (__atags_pointer)
atags_vaddr = FDT_VIRT_BASE(__atags_pointer);
setup_processor();
if (atags_vaddr) {
mdesc = setup_machine_fdt(atags_vaddr);
if (mdesc)
memblock_reserve(__atags_pointer,fdt_totalsize(atags_vaddr));
}
xxxxxxxxxxx
parse_early_param();
xxxxxxxxxxx
}
//函数位于kernel\msm-5.4\init\main.c
asmlinkage __visible void __init start_kernel(void)
{
xxxxxxxxxxxxx
setup_arch(&command_line);
setup_command_line(command_line);
xxxxxxxxxxxxx
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);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
xxxxxxxxxxxxx
}
parse_args函数中会使用next_arg以此提取cmdline中的成员(以空格分割),在next_arg会对cmdline进行参数和值分离(以等号=分割)。由于从parse_early_options
传入的num_params=0,所以parse_one是直接走的最后handle_unknown函数,即parse-early_options传入的do_early_param。
//函数位于kernel\msm-5.4\kernel\params.c
static int parse_one(char *param,char *val,const char *doing,const struct kernel_param *params,unsigned num_params,s16 min_level,s16 max_level,void *arg,int (*handle_unknown)(char *param, char *val,const char *doing, void *arg))
{
unsigned int i;
int err;
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {
if (params[i].level < min_level
|| params[i].level > max_level)
return 0;
/* No one handled NULL, so do it here. */
if (!val && !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG))
return -EINVAL;
pr_debug("handling %s with %p\n", param,
params[i].ops->set);
kernel_param_lock(params[i].mod);
if (param_check_unsafe(¶ms[i]))
err = params[i].ops->set(val, ¶ms[i]);
else
err = -EPERM;
kernel_param_unlock(params[i].mod);
return err;
}
}
if (handle_unknown) {
pr_debug("doing %s: %s='%s'\n", doing, param, val);
return handle_unknown(param, val, doing, arg);
}
pr_debug("Unknown argument '%s'\n", param);
return -ENOENT;
}
char *parse_args(const char *doing,char *args,const struct kernel_param *params,unsigned num,s16 min_level,s16 max_level,void *arg,int (*unknown)(char *param, char *val,const char *doing, void *arg))
{
xxxxx
while (*args) {
xxxxx
args = next_arg(args, ¶m, &val);
/* Stop at -- */
if (!val && strcmp(param, "--") == 0)
return err ?: args;
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, doing, params, num,
min_level, max_level, arg, unknown);
if (irq_was_disabled && !irqs_disabled())
pr_warn("%s: option '%s' enabled irq's!\n",
doing, param);
xxxxx
}
return err;
}
//函数位于kernel\msm-5.4\lib\cmdline.c
char *next_arg(char *args, char **param, char **val)
{
unsigned int i, equals = 0;
int in_quote = 0, quoted = 0;
char *next;
if (*args == '"') {
args++;
in_quote = 1;
quoted = 1;
}
for (i = 0; args[i]; i++) {
if (isspace(args[i]) && !in_quote)
break;
if (equals == 0) {
if (args[i] == '=')
equals = i;
}
if (args[i] == '"')
in_quote = !in_quote;
}
*param = args;
if (!equals)
*val = NULL;
else {
args[equals] = '\0';
*val = args + equals + 1;
/* Don't include quotes in value. */
if (**val == '"') {
(*val)++;
if (args[i-1] == '"')
args[i-1] = '\0';
}
}
if (quoted && args[i-1] == '"')
args[i-1] = '\0';
if (args[i]) {
args[i] = '\0';
next = args + i + 1;
} else
next = args + i;
/* Chew up trailing spaces. */
return skip_spaces(next);
}
handle_unknown在本例中就是调用parse_args时传入的_do_early_param
函数。
调用cmdline处理函数和我们平时常用的调用方式不一样。通过读取特定代码段的内容解析出来,然后通过函数指针的方式去调用处理函数。在do_early_param函数中会对.init.setup
段进行一个遍历,__setup_start
是该段的起始地址,__setup_end
是该段的结束地址。
//段定义位于kernel\msm-5.4\include\asm-generic\vmlinux.lds.h
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
__setup_start = .; \
KEEP(*(.init.setup)) \
__setup_end = .;
*init.setup
段中存储了所有obs_kernel_param
的变量,在知道段起始地址的情况下,就可以直接在地址内读取到结构体的数据。使用for循环提取obs_kernel_param
长度的数据,p->str就是当前结构体中记录的cmdline对应的参数名,p->setup_func就是cmdline处理函数。
遍历过程中,如果有obs_kernel_param的early为1,或cmdline中有console参数并且obs_kernel_param有earlycon参数,则会调用该obs_kernel_param的setup函数来解析参数。
do_early_param是为kernel中需要尽早配置的功能(如earlyprintk earlycon)做cmdline的解析并完成处理函数的调用。
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
//函数位于\kernel\msm-5.4\init\main.c
/* Check for early params. */
static 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;
}
而obs_kernel_param的early为0的,则在start_kernel的后续流程中parse_args继续完成解析。此时是start_kernel第2次执行parse_args。
//函数位于kernel\msm-5.4\init\main.c
asmlinkage __visible void __init start_kernel(void)
{
xxxxxxxxxxxxx
setup_arch(&command_line);
setup_command_line(command_line);
xxxxxxxxxxxxx
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);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
xxxxxxxxxxxxx
}
在第二次执行parse_args时,其形参parse_args不再是NULL,而是指定了.__param段
,该段中存储的时一些模块的注册信息,主要是针对加载驱动的命令行参数的,将params[i].name与param对比,同名则调用该kernel_param成员变量kernel_param_ops的set方法来设置参数值,这里不多做解释。
//代码位于kernel\msm-5.4\drivers\net\wireless\ath\wil6210\main.c
unsigned int mtu_max = TXRX_BUF_LEN_DEFAULT - WIL_MAX_MPDU_OVERHEAD;
static int mtu_max_set(const char *val, const struct kernel_param *kp)
{
int ret;
/* sets mtu_max directly. no need to restore it in case of
* illegal value since we assume this will fail insmod
*/
ret = param_set_uint(val, kp);
if (ret)
return ret;
if (mtu_max < 68 || mtu_max > WIL_MAX_ETH_MTU)
ret = -EINVAL;
return ret;
}
static const struct kernel_param_ops mtu_max_ops = {
.set = mtu_max_set,
.get = param_get_uint,
};
module_param_cb(mtu_max, &mtu_max_ops, &mtu_max, 0444);
MODULE_PARM_DESC(mtu_max, " Max MTU value.");
如果parse_args传给parse_one是kernel通用参数,如console=ttyS0,115200。则parse_one前面遍历.__param
段不会找到匹配的kernel_param。就走到后面调用handle_unknown。就是parse_args传来的unknown_bootoption。
//函数位于kernel\msm-5.4\kernel\params.c
static int parse_one(char *param,char *val,const char *doing,const struct kernel_param *params,unsigned num_params,s16 min_level,s16 max_level,void *arg,int (*handle_unknown)(char *param, char *val,const char *doing, void *arg))
{
unsigned int i;
int err;
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {
if (params[i].level < min_level
|| params[i].level > max_level)
return 0;
/* No one handled NULL, so do it here. */
if (!val && !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG))
return -EINVAL;
pr_debug("handling %s with %p\n", param,
params[i].ops->set);
kernel_param_lock(params[i].mod);
if (param_check_unsafe(¶ms[i]))
err = params[i].ops->set(val, ¶ms[i]);
else
err = -EPERM;
kernel_param_unlock(params[i].mod);
return err;
}
}
if (handle_unknown) {
pr_debug("doing %s: %s='%s'\n", doing, param, val);
return handle_unknown(param, val, doing, arg);
}
pr_debug("Unknown argument '%s'\n", param);
return -ENOENT;
}
在unknown_bootoption中,obsolete_checksetup(该函数是最终解析early=0类型param的)则遍历*init.setup
段所有obs_kernel_param,如有param->str与param匹配,则调用param_>setup进行参数值配置。
static 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;
}
static 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;
xxxxxxxxxxxxxx
}
这样一来整个cmdline的流程全部梳理清楚了,从参数传递,到参数解析,到处理函数调用。
本篇内容通过一个开发需求,选择一个cmdline实例,使用代码走读的方式。对Linux内核的cmdline机制的流程全部梳理了一遍,从参数传递,到参数解析,到处理函数的调用。通过本篇文章,按照文中所诉的流程,可以简单的了解到bootload在引导linux内核时的参数是怎么传递给kernel处理的。
那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢。