下述宏定义以及函数声明位于内核源码kernel-5.10/include/linux/init.h
__setup_param
宏#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 }
str
: 是一个字符串,表示内核启动参数。unique_id
: 是一个唯一的标识符,用于生成内部变量名。fn
: 是一个函数指针,指向处理该启动参数的函数。early
: 是一个布尔值,表示该参数是否在早期处理。这个宏定义了两个静态变量:
__setup_str_##unique_id
: 存储启动参数字符串,类型为 const char[]
,并使用 __aligned(1)
确保对齐。__setup_##unique_id
: 是一个 struct obs_kernel_param
类型的结构体,存储在 .init.setup
段中,并使用 __attribute__((aligned((sizeof(long)))))
确保对齐。该结构体包含启动参数字符串指针、处理函数指针和早期处理标志。struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
__setup
宏#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
str
: 是一个启动参数字符串。fn
: 是处理该启动参数的函数。 这个宏调用 __setup_param
宏,并将 early
参数设置为 0
,表示该参数不是早期处理的。
early_param
宏#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
str
: 是一个启动参数字符串。fn
: 是处理该启动参数的函数。 这个宏调用 __setup_param
宏,并将 early
参数设置为 1
,表示该参数是早期处理的。
early_param_on_off
宏#define early_param_on_off(str_on, str_off, var, config) \
\
int var = IS_ENABLED(config); \
\
static int __init parse_##var##_on(char *arg) \
{ \
var = 1; \
return 0; \
} \
__setup_param(str_on, parse_##var##_on, parse_##var##_on, 1); \
\
static int __init parse_##var##_off(char *arg) \
{ \
var = 0; \
return 0; \
} \
__setup_param(str_off, parse_##var##_off, parse_##var##_off, 1)
str_on
: 是一个表示启用的启动参数字符串。str_off
: 是一个表示禁用的启动参数字符串。var
: 是一个变量,用于存储是否启用的标志。config
: 是一个配置选项,用于初始化 var
的值。 这个宏定义了两个处理函数 parse_##var##_on
和 parse_##var##_off
,分别用于处理启用和禁用的启动参数,并将它们注册为早期处理的参数。
parse_early_param
和 parse_early_options
函数//函数实现位于kernel-5.10/init/main.c
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);
parse_early_param
: 是一个函数,用于解析早期启动参数。parse_early_options
: 是一个函数,用于解析启动命令行中的早期选项。/**
* 解析早期启动选项
*
* @param cmdline 命令行参数字符串
*
* 此函数在系统初始化阶段解析传递给内核的命令行参数称为"early options"
* 它使用parse_args函数来处理这些参数并将它们标记为早期选项
* 这些选项通常用于配置内核的早期启动过程
*
* 注意:这个函数假定调用者已经准备好了合适的命令行参数字符串
* 并且这个函数通常在内核初始化过程中被调用
*/
void __init parse_early_options(char *cmdline)
{
// 调用parse_args函数解析早期选项参数
// "early options"是参数的描述用于调试和日志
// cmdline是指向命令行参数字符串的指针
// 后续参数为NULL和0表示默认行为和设置
// do_early_param是处理每个参数的回调函数
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL, do_early_param);
}
/**
* 解析早期启动参数。
*
* 该函数用于解析内核完全初始化之前传递给内核的启动参数。
* 它通常在内核启动过程的早期阶段调用,以处理诸如 'root=' 和 'init=' 等参数。
*
* 此函数不接受任何参数,因为它直接使用全局变量 boot_command_line。
* 没有返回值,解析结果会反映在全局状态中。
*/
void __init parse_early_param(void)
{
// 标志位,确保只解析一次
static int done __initdata;
// 临时缓冲区,用于存储启动命令行
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
// 检查是否已经完成解析,避免重复执行
if (done)
return;
// 将启动命令行复制到临时缓冲区以便解析
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
// 解析早期启动参数
parse_early_options(tmp_cmdline);
// 设置完成标志,防止再次解析
done = 1;
}
这些宏和函数主要用于在 Linux 内核初始化过程中解析和处理启动参数。通过 __setup_param
宏,可以方便地注册启动参数及其处理函数,并将这些参数分类为早期处理或非早期处理。early_param_on_off
宏则提供了一种便捷的方式来处理启用和禁用的启动参数。parse_early_param
和 parse_early_options
函数则在内核初始化过程中调用,解析这些参数。
假设我们有一个内核模块(或内核代码的一部分),需要在启动时解析一个名为 my_option
的启动参数,并根据该参数设置一个全局变量 my_value
。我们还希望有一个早期处理的参数 my_early_option
。
首先,我们定义一个全局变量 my_value
,用于存储 my_option
参数的值:
static int my_value = 0; // 默认值为 0
接下来,我们编写两个函数,分别用于处理 my_option
和 my_early_option
:
// 处理 my_option 参数的函数
static int __init parse_my_option(char *arg)
{
int value;
if (kstrtoint(arg, 10, &value) == 0) { // 将字符串转换为整数
my_value = value;
pr_info("my_option set to %d\n", my_value);
return 0; // 返回 0 表示成功处理
}
pr_err("Invalid value for my_option: %s\n", arg);
return -EINVAL; // 返回错误码表示处理失败
}
// 处理 my_early_option 参数的函数
static int __init parse_my_early_option(char *arg)
{
pr_info("my_early_option received: %s\n", arg);
return 0; // 返回 0 表示成功处理
}
__setup
和 early_param
注册启动参数现在,我们使用 __setup
和 early_param
宏来注册这两个启动参数:
__setup("my_option=", parse_my_option); // 注册 my_option 参数
early_param("my_early_option", parse_my_early_option); // 注册 my_early_option 参数
将上述代码整合到一起,完整的代码示例如下:
#include
#include
#include
#include
#include
static int my_value = 0; // 默认值为 0
// 处理 my_option 参数的函数
static int __init parse_my_option(char *arg)
{
int value;
if (kstrtoint(arg, 10, &value) == 0) { // 将字符串转换为整数
my_value = value;
pr_info("my_option set to %d\n", my_value);
return 0; // 返回 0 表示成功处理
}
pr_err("Invalid value for my_option: %s\n", arg);
return -EINVAL; // 返回错误码表示处理失败
}
// 处理 my_early_option 参数的函数
static int __init parse_my_early_option(char *arg)
{
pr_info("my_early_option received: %s\n", arg);
return 0; // 返回 0 表示成功处理
}
// 注册启动参数
__setup("my_option=", parse_my_option); // 注册 my_option 参数
early_param("my_early_option", parse_my_early_option); // 注册 my_early_option 参数
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example of using __setup and early_param macros");
可在uboot下命令行中添加这些参数,例如:
my_option=42 my_early_option=early_string
my_option=42
:内核启动后,my_value
的值会被设置为 42
,并输出一条日志信息。my_early_option=early_string
:内核启动时,会立即处理 my_early_option
,并输出一条日志信息。my_option=42
被正确解析,内核日志中会输出:my_option set to 42
my_early_option=early_string
被正确解析,内核日志中会输出:my_early_option received: early_string
通过上述示例,我们展示了如何使用 __setup
和 early_param
宏来注册启动参数,并在内核启动时解析它们。__setup
用于注册普通的启动参数,而 early_param
用于注册需要在早期处理的启动参数。这些宏极大地简化了启动参数的注册和处理流程,是内核模块开发中非常有用的工具。