原文地址:http://simohayha.javaeye.com/blog/370502
先来看下内核初始化时调用的一些函数:
这里主要的初始化有三类:
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 }
看起来很复杂。 首先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;
- };
前两个参数很简单,一个是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);
我们先来看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;
- }
接下来下来看下,一些内置模块的参数处理的问题。传递给模块的参数都是通过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 } }
这里也就是填充了 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;
- };
- };
接下来来看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;
- }
这里之所以把所有的内核初始化参数都放在一个段里,主要是为了初始化成功后,释放这些空间。
我们下来看一下内核编译完毕后的一些段的位置:
右边就是每个段定义的相关的宏。在__init_start和__init_end之间的段严格按照顺序进行初始化,比如 core_initcall宏修饰的函数就要比arch_initcall宏定义的函数优先级高,所以就更早的调用。这个特性就可以使我们将一些优先级比 较高的初始化函数放入相应比较高的段。。
这里要注意,每个init宏,基本都会有个对应的exit宏,比如__initial和__exit等等。。
内核中修饰的宏很多,我们下面只介绍一些常用的:
__devinit 用来标记一个设备的初始化函数,比如pci设备的probe函数,就是用这个宏来修饰。
__initcall这个已被废弃,现在实现为__devinit的别名。
剩下的宏需要去看内核的相关文档。。
最后还要注意几点:
1 当模块被静态的加入到内核中时,module_init标记的函数只会被执行一次,因此当初始化成功后,内核会释放掉这块的内存。
2 当模块静态假如内核,module_exit在链接时就会被删除掉。
这里的优化还有很多,比如热拔插如果不支持的话,很多设备的初始化函数都会在执行完一遍后,被丢弃掉。
这里可以看到,如果模块被静态的编译进内核时,内核所做的内存优化会更多,虽然损失了更多的灵活性。。
阅读(507) | 评论(0) | 转发(0) |