先来看下内核初始化时调用的一些函数:
这里主要的初始化有三类:
1 boot比如grub,u-boot传递给内核的参数,内核的处理。这里是调用parse_args.
2 中断和时钟的初始化。
3 初始化的函数,这里主要是通过do_initcalls标记的驱动初始化函数。一般这里的初始化函数完成后,会调用free_init_mem释放掉这块的空间。
我们这里主要关注第1和第3类。
首先来看第一类的初始化,我们知道boot传递给内核的参数都是 "name_varibale=value"这种形式的,那么内核如何知道传递进来的参数该怎么去处理呢?
内核是通过__setup宏或者early_param宏来将参数与参数所处理的函数相关联起来的。我们接下来就来看这个宏以及相关的结构的定义:
- #define __setup(str, fn) \
- __setup_param(str, fn, fn, 0)
-
- #define early_param(str, fn) \
- __setup_param(str, fn, fn, 1)
-
- #define __setup_param(str, unique_id, fn, early) \
- static char __setup_str_##unique_id[] __initdata __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)
#define __setup_param(str, unique_id, fn, early) \
static char __setup_str_##unique_id[] __initdata __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 }
看起来很复杂。
首先setup宏第一个参数是一个key。比如"netdev="这样的,而第二个参数是这个key对应的处理函数。这里要注意相同的handler能联
系到不同的key。early_param和setup唯一不同的就是传递给__setup_param的最后一个参数,这个参数我么下面会说明
而接下来_setup_param定义了一个struct obs_kernel_param类型的结构,然后通过_section宏,使这个变量在链接的时候能够放置在段.init.setup(我们后面会详细介绍内核中的这些初始化段).
我们接下来来看struct obs_kernel_param结构:
- struct obs_kernel_param {
- const char *str;
- int (*setup_func)(char *);
- int early;
- };
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
前两个参数很简单,一个是key,一个是handler。最后一个参数其实也就是类似于优先级的一个flag,因为传递给内核的参数中,有一些需要比另外的一些更早的解析。(这也就是为什么early_param和setup传递的最后一个参数的不同的原因了。
接下来我们来看内核解析boot传递给内核的参数的步骤:
先看下面的图:
可以看到内核首先通过parse_early_param来解析优先级更高的,也就是需要被更早解析的命令行参数,然后通过parse_ares来解析一般的命令行参数.
下面这两个调用就是parse_early_param和parse_ares调用传递给parse_args的不同的参数.
- parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);
- parse_args("Booting kernel", static_command_line, __start___param,
- __stop___param - __start___param,
- &unknown_bootoption);
parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
我们先来看do_early_param函数:
- static int __init do_early_param(char *param, char *val)
- {
- struct obs_kernel_param *p;
-
- for (p = __setup_start; p < __setup_end; p++) {
-
- if ((p->early && strcmp(param, p->str) == 0) ||
- (strcmp(param, "console") == 0 &&
- strcmp(p->str, "earlycon") == 0)
- ) {
-
- if (p->setup_func(val) != 0)
- printk(KERN_WARNING
- "Malformed early option '%s'\n", param);
- }
- }
-
- return 0;
- }
static int __init do_early_param(char *param, char *val)
{
struct obs_kernel_param *p;
///这里的__setup_start和_-setup_end分别是.init.setup段的起始和结束的地址。
for (p = __setup_start; p < __setup_end; p++) {
///如果没有early标志则跳过。
if ((p->early && strcmp(param, p->str) == 0) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
///调用处理函数
if (p->setup_func(val) != 0)
printk(KERN_WARNING
"Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
接下来下来看下,一些内置模块的参数处理的问题。传递给模块的参数都是通过module_param来实现的:
- #define module_param(name, type, perm) \
- module_param_named(name, name, type, perm)
-
- #define module_param_named(name, value, type, perm) \
- param_check_##type(name, &(value)); \
- module_param_call(name, param_set_##type, param_get_##type, &value, perm); \
- __MODULE_PARM_TYPE(name, #type)
-
- #define module_param_call(name, set, get, arg, perm) \
- __module_param_call(MODULE_PARAM_PREFIX, name, set, get, arg, perm)
-
- #define __module_param_call(prefix, name, set, get, arg, perm) \
- \
- static int __param_perm_check_##name __attribute__((unused)) = \
- BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)); \
- static const char __param_str_##name[] = prefix #name; \
- static struct kernel_param __moduleparam_const __param_##name \
- __used \
- __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
- = { __param_str_##name, perm, set, get, { arg } }
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
#define module_param_named(name, value, type, perm) \
param_check_##type(name, &(value)); \
module_param_call(name, param_set_##type, param_get_##type, &value, perm); \
__MODULE_PARM_TYPE(name, #type)
#define module_param_call(name, set, get, arg, perm) \
__module_param_call(MODULE_PARAM_PREFIX, name, set, get, arg, perm)
#define __module_param_call(prefix, name, set, get, arg, perm) \
/* Default value instead of permissions? */ \
static int __param_perm_check_##name __attribute__((unused)) = \
BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)); \
static const char __param_str_##name[] = prefix #name; \
static struct kernel_param __moduleparam_const __param_##name \
__used \
__attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
= { __param_str_##name, perm, set, get, { arg } }
这里也就是填充了 struct kernel_param的结构体,并将这个变量标记为__param段,以便于链接器将此变量装载到指定的段。
接下来我们来看struct kernel_param这个结构:
- struct kernel_param {
- const char *name;
- unsigned int perm;
- param_set_fn set;
- param_get_fn get;
- union {
- void *arg;
- const struct kparam_string *str;
- const struct kparam_array *arr;
- };
- };
struct kernel_param {
const char *name;
unsigned int perm;
param_set_fn set;///设置参数的函数,
param_get_fn get;///读取参数的函数
union {///传递给上面两个函数的参数。
void *arg;
const struct kparam_string *str;
const struct kparam_array *arr;
};
};
接下来来看parse_one函数,其中early和一般的pase都是通过这个函数来解析:
- static int parse_one(char *param,
- char *val,
- struct kernel_param *params,
- unsigned num_params,
- int (*handle_unknown)(char *param, char *val))
- {
- unsigned int i;
-
-
- for (i = 0; i < num_params; i++) {
- if (parameq(param, params[i].name)) {
- DEBUGP("They are equal! Calling %p\n",
- params[i].set);
-
- return params[i].set(val, ¶ms[i]);
- }
- }
-
- if (handle_unknown) {
- DEBUGP("Unknown argument: calling %p\n", handle_unknown);
- return handle_unknown(param, val);
- }
-
- DEBUGP("Unknown argument `%s'\n", param);
- return -ENOENT;
- }
static int parse_one(char *param,
char *val,
struct kernel_param *params,
unsigned num_params,
int (*handle_unknown)(char *param, char *val))
{
unsigned int i;
///如果是early_param则直接跳过这步,而非early的,则要通过这步来设置一些内置模块的参数。
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {
DEBUGP("They are equal! Calling %p\n",
params[i].set);
///调用参数设置函数来设置对应的参数。
return params[i].set(val, ¶ms[i]);
}
}
if (handle_unknown) {
DEBUGP("Unknown argument: calling %p\n", handle_unknown);
return handle_unknown(param, val);
}
DEBUGP("Unknown argument `%s'\n", param);
return -ENOENT;
}
这里之所以把所有的内核初始化参数都放在一个段里,主要是为了初始化成功后,释放这些空间。
我们下来看一下内核编译完毕后的一些段的位置:
右边就是每个段定义的相关的宏。在__init_start和__init_end之间的段严格按照顺序进行初始化,比如
core_initcall宏修饰的函数就要比arch_initcall宏定义的函数优先级高,所以就更早的调用。这个特性就可以使我们将一些优先级比
较高的初始化函数放入相应比较高的段。。
这里要注意,每个init宏,基本都会有个对应的exit宏,比如__initial和__exit等等。。
内核中修饰的宏很多,我们下面只介绍一些常用的:
__devinit 用来标记一个设备的初始化函数,比如pci设备的probe函数,就是用这个宏来修饰。
__initcall这个已被废弃,现在实现为__devinit的别名。
剩下的宏需要去看内核的相关文档。。
最后还要注意几点:
1 当模块被静态的加入到内核中时,module_init标记的函数只会被执行一次,因此当初始化成功后,内核会释放掉这块的内存。
2 当模块静态假如内核,module_exit在链接时就会被删除掉。
这里的优化还有很多,比如热拔插如果不支持的话,很多设备的初始化函数都会在执行完一遍后,被丢弃掉。
这里可以看到,如果模块被静态的编译进内核时,内核所做的内存优化会更多,虽然损失了更多的灵活性。。