在Linux内核源码中多处会看到诸如__init,__initdata,__exitdata的关键字,
大部分这样的关键字定义在include/linux/init.h头文件中,它们都会在编译连接阶段被实现处理。
#define __init __section(.init.text) __cold notrace __init标记的数据被存储在.init.text节
#define __initdata __section(.init.data) __initdata标记的数据被存储在.init.data节
#define __initconst __section(.init.rodata) __initconst标记的数据被存储在.init.rodata节
#define __exitdata __section(.exit.data) __exitdata标记的数据被存储在.exit.data节
#define __exit_call __used __section(.exitcall.exit) __exit__call__used标记的数据被存储在.exitcall.exit节
我们也经常看到许多诸如subsystem_initcall(fn)的语句,fn为某个函数的名称,也可以在init.h中找到它的踪影。
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define early_initcall(fn) __define_initcall("early",fn,early)
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
......
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
可以看出每个与__define_initcall相关的注册函数都被解释为 :
static initcall_t __initcall_fn_id, 入口地址将被存储在.initcall"level".init节中。
我们常在Linux模块中用到的module_init和module_exit的真身也可以看到:
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);
init.h中还有一些代码设计boot与kernel的参数交换例如:
#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 }
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
/* NOTE: fn is as per module_param, not __setup! Emits warning if fn
* returns non-zero. */
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);
我们经常用到的early_param,__setup用于处理boot传递过来的参数
内核启动后进入组件式初始化的过程如下图所示:
组件的初始化有以下三类:
1.来着bootloader传递的参数,使用do_early_param函数从__setup_start~__setup_end区间搜索完成。
2.中断和时钟的初始化
3.普通的初始化函数,这些主要是通过do_initcalls()函数完成的。
第一类初始化中,bootloader传递给kernel的参数格式为A=B,内核通过__setup和early_param标记来识别参数对应的函数。
相当于我们所说的绑定。
#define __setup(str,fn) __setup_param(str,fn,fn,0)
#define early_param(str,fn) __setup_param(str,fn,fn,1)
两者的差别就在最后一个参数,__setup_param内容如下:
#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)\ //存放到.init.setup节
__attribute__((aligned((sizeof(long)))) \ //对其要求
={__setup_str_##unique_id, fn, early}
可以看出查遍就在 obs_kernel_param数据结构的early成员是否置1。
再看一下obs_kernel_param的细节:
struct obs_kernel_param{
const char *str;
int (*setup_func)(char *);//slot用于绑定对应的函数
int early;//执行优先级的标记
}
可见被early_param修饰的函数要先于被__setup标记修饰的函数,
以下是两种函数的调用过程:
对以early_param修饰的函数,是由do_early_param(...)
一般的函数若要在内核启动时背do_early_param调用,需使用early_param(...)"注册“
一个典型的例子是:
调试信息的的显示:
在linux_cmdline中如果加入了debug那么内核启动的信息将逐一打印出来,具体实现是这样的:
static int __init debug_kernel(char *str)
{
console_loglevel = 10;
return 0;
}
early_param("debug",debug_kernel);
于是,debug_kernel的入口地址在链接时被”挂“到了.init.setup节,供do_early_param调用。
对于__setup,使用__setup标记挂到.init.setup中的许多选项如今很少使用,除了__setup("init=",init_setup);