Chinaunix首页 | 论坛 | 博客
  • 博客访问: 356286
  • 博文数量: 167
  • 博客积分: 2867
  • 博客等级: 少校
  • 技术积分: 1306
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-12 00:08
文章分类

全部博文(167)

文章存档

2017年(10)

2016年(5)

2015年(9)

2014年(10)

2013年(5)

2012年(17)

2011年(110)

2010年(1)

我的朋友

分类: LINUX

2011-03-08 14:39:40

原文地址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宏来将参数与参数所处理的函数相关联起来的。我们接下来就来看这个宏以及相关的结构的定义: 

Java代码 
  1. #define __setup(str, fn)                    \  
  2.     __setup_param(str, fn, fn, 0)  
  3.   
  4. #define early_param(str, fn)                    \  
  5.     __setup_param(str, fn, fn, 1)  
  6.   
  7. #define __setup_param(str, unique_id, fn, early)            \  
  8.     static char __setup_str_##unique_id[] __initdata __aligned(1) = str; \  
  9.     static struct obs_kernel_param __setup_##unique_id  \  
  10.         __used __section(.init.setup)           \  
  11.         __attribute__((aligned((sizeof(long)))))    \  
  12.         = { __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结构: 

Java代码 
  1. struct obs_kernel_param {  
  2.     const char *str;  
  3.     int (*setup_func)(char *);  
  4.     int early;  
  5. };  


前两个参数很简单,一个是key,一个是handler。最后一个参数其实也就是类似于优先级的一个flag,因为传递给内核的参数中,有一些需要比另外的一些更早的解析。(这也就是为什么early_param和setup传递的最后一个参数的不同的原因了。 


接下来我们来看内核解析boot传递给内核的参数的步骤: 

先看下面的图: 



可以看到内核首先通过parse_early_param来解析优先级更高的,也就是需要被更早解析的命令行参数,然后通过parse_ares来解析一般的命令行参数. 

下面这两个调用就是parse_early_param和parse_ares调用传递给parse_args的不同的参数. 

Java代码 
  1. parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);  
  2. parse_args("Booting kernel", static_command_line, __start___param,  
  3.            __stop___param - __start___param,  
  4.            &unknown_bootoption);  



我们先来看do_early_param函数: 

Java代码 
  1. static int __init do_early_param(char *param, char *val)  
  2. {  
  3.     struct obs_kernel_param *p;  
  4. ///这里的__setup_start和_-setup_end分别是.init.setup段的起始和结束的地址。  
  5.     for (p = __setup_start; p < __setup_end; p++) {  
  6. ///如果没有early标志则跳过。  
  7.         if ((p->early && strcmp(param, p->str) == 0) ||  
  8.             (strcmp(param, "console") == 0 &&  
  9.              strcmp(p->str, "earlycon") == 0)  
  10.         ) {  
  11. ///调用处理函数  
  12.             if (p->setup_func(val) != 0)  
  13.                 printk(KERN_WARNING  
  14.                        "Malformed early option '%s'\n", param);  
  15.         }  
  16.     }  
  17.     /* We accept everything at this stage. */  
  18.     return 0;  
  19. }  



接下来下来看下,一些内置模块的参数处理的问题。传递给模块的参数都是通过module_param来实现的: 

Java代码 
  1. #define module_param(name, type, perm)              \  
  2.     module_param_named(name, name, type, perm)  
  3.   
  4. #define module_param_named(name, value, type, perm)            \  
  5.     param_check_##type(name, &(value));                \  
  6.     module_param_call(name, param_set_##type, param_get_##type, &value, perm); \  
  7.     __MODULE_PARM_TYPE(name, #type)  
  8.   
  9. #define module_param_call(name, set, get, arg, perm)                  \  
  10.     __module_param_call(MODULE_PARAM_PREFIX, name, set, get, arg, perm)  
  11.   
  12. #define __module_param_call(prefix, name, set, get, arg, perm)      \  
  13.     /* Default value instead of permissions? */         \  
  14.     static int __param_perm_check_##name __attribute__((unused)) =  \  
  15.     BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2));   \  
  16.     static const char __param_str_##name[] = prefix #name;      \  
  17.     static struct kernel_param __moduleparam_const __param_##name   \  
  18.     __used                              \  
  19.     __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \  
  20.     = { __param_str_##name, perm, set, get, { arg } }  


这里也就是填充了 struct kernel_param的结构体,并将这个变量标记为__param段,以便于链接器将此变量装载到指定的段。 

接下来我们来看struct kernel_param这个结构: 

Java代码 
  1. struct kernel_param {  
  2.     const char *name;  
  3.     unsigned int perm;  
  4.     param_set_fn set;///设置参数的函数,  
  5.     param_get_fn get;///读取参数的函数  
  6.     union {///传递给上面两个函数的参数。  
  7.         void *arg;  
  8.         const struct kparam_string *str;  
  9.         const struct kparam_array *arr;  
  10.     };  
  11. };  




接下来来看parse_one函数,其中early和一般的pase都是通过这个函数来解析: 

Java代码 
  1. static int parse_one(char *param,  
  2.              char *val,  
  3.              struct kernel_param *params,   
  4.              unsigned num_params,  
  5.              int (*handle_unknown)(char *param, char *val))  
  6. {  
  7.     unsigned int i;  
  8. ///如果是early_param则直接跳过这步,而非early的,则要通过这步来设置一些内置模块的参数。  
  9.     /* Find parameter */  
  10.     for (i = 0; i < num_params; i++) {  
  11.         if (parameq(param, params[i].name)) {  
  12.             DEBUGP("They are equal!  Calling %p\n",  
  13.                    params[i].set);  
  14. ///调用参数设置函数来设置对应的参数。  
  15.             return params[i].set(val, ¶ms[i]);  
  16.         }  
  17.     }  
  18.   
  19.     if (handle_unknown) {  
  20.         DEBUGP("Unknown argument: calling %p\n", handle_unknown);  
  21.         return handle_unknown(param, val);  
  22.     }  
  23.   
  24.     DEBUGP("Unknown argument `%s'\n", param);  
  25.     return -ENOENT;  
  26. }  


这里之所以把所有的内核初始化参数都放在一个段里,主要是为了初始化成功后,释放这些空间。 


我们下来看一下内核编译完毕后的一些段的位置: 




右边就是每个段定义的相关的宏。在__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) |
给主人留下些什么吧!~~