深入理解Linux网络技术内幕——内核基础架构和组件初始化
引导期间的内核选项
Linux允许用户把内核配置选项传给引导记录,再有引导记录传给内核,以便对内核进行调整。
start_kernel中调用两次parse_args,用于引导期间配置用户输入数据。
parse_param是一个函数,用于解析输入的内核配置选项的参数字符串。字符串的格式为:name_variable=value。寻址特定关键字,并调用对应的函数。
注册关键字:
用宏进行定义:
__setup(string, fn)
string表示关键字,fn表示关联的处理函数。__setup的宏定义分为两种情况
#ifndef MODULE #define early_param(str, fn) \ __setup_param(str, fn, fn, 0) #else /* MODULE */ #define early_param(str, fn) #endif
即一块代码编译为模块时,__setup被忽略了。
看两个例子:
//net/core/dev.c _setup("netdev=", netdev_boot_setup); //net/ethernet/eth.c __setup("ether=", netdev_boot_setup);
上面的例子也可以看出,不同的关键字可以关联到同一处理函数中。
start_kernel中调用两次parse_args原因在于:内核引导选项分为两类:
默认选项:
大多数选项属于这一类,由__setup定义,由parse_args的第一次调用处理、
初期选项:
这些选项必须必其它选项更早处理。early_param(而非__setup)进行处理。early_param与__setup区别在于early_param会设置一个标识,以便内核识别。
内核是通过__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 } early_param和__setup唯一不同的就是传递给__setup_param的最后一个参数early。_setup_param定义了一个struct obs_kernel_param类型的结构,然后通过_section宏,使这个变量在链接的时候能够放置在段.init.setup。下文有.init.setup的介绍。 struct obs_kernel_param结构如下: struct obs_kernel_param { const char *str; int (*setup_func)(char *); int early; };
str表示key,setup_func表示handler。early类似于优先级的一个flag,因为传递给内核的参数中,有一些需要比另外的一些更早的解析。 参考下面两边分析一节:
两遍分析:
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);
在start_kernel函数中第二次调用parse_args函数时,会检查有module_param宏填入的模块选项,这些选项会存储在kernel_param数据结构中,module_param宏会确保这些数据结构都会被存放在特定的内存区块中(__param),有指针__start___param
和__stop___param限定。
__setup_start……__setup_end:这个区域会在引导阶段结束时释放掉,用户在运行期间不能看到或修改这些选项;
__start___param……__stop___param:这个区域不会被释放,其内容会被输出到/sys文件系统,这些选项可以显示给用户。
.init.setup 内存区
有__setup()和early_param()宏定义的关键字会被放入__setup_start……__setup_end区域,只是early_param宏定义的关键字会被设置early标记。
struct obs_kernel_param { const char *str; int (*setup_func)(char *); int early; };
模块初始化代码:
每一个模块都要提供两个函数called init_module and cleanup_module。由下面两个宏指定:
module_init(XXX); module_exit(XXX);
当前模块设计模型:
static char version[] _ _devinitdata = DRV_NAME " ... "; static struct vortex_chip_info { ... } vortex_info_tbl[] _ _devinitdata = { {"3c590 Vortex 10Mbps", ... ... ... } static int _ _init vortex_init (void) { ... } static void _ _exit vortex_cleanup (void) { ... } module_init(vortex_init); module_exit(vortex_cleanup);
基于宏标签的优化:
Linux使用许多宏来对一些函数和数据结构设定特殊的属性。这些宏大多定义在include/linux/init.h.中。而有些宏会通知链接器把具有相同属性的代码或数据结构放到特定专用的内存区域 。
右侧为宏的名字。