写过Linux驱动的人都知道module_init宏,因为它声明了一个驱动的入口函数。
除了module_init宏,你会发现在Linux内核中有许多的驱动并没有使用module_init宏来声明入口函数,而是看到了许多诸如以下的声明:
static int __init qcom_iommu_init(void)
{
int ret;
ret = platform_driver_register(&qcom_iommu_ctx_driver);
if (ret)
return ret;
ret = platform_driver_register(&qcom_iommu_driver);
if (ret)
platform_driver_unregister(&qcom_iommu_ctx_driver);
return ret;
}
device_initcall(qcom_iommu_init);
static int __init ebsa110_init(void)
{
arm_pm_idle = ebsa110_idle;
return platform_add_devices(ebsa110_devices, ARRAY_SIZE(ebsa110_devices));
}
arch_initcall(ebsa110_init);
上述举例的两个驱动入口分别使用了device_initcall()和arch_initcall()来声明驱动入口,这些本质上都是对initcall的调用,module_init也如此。
initcall等级
Linux内核对initcall进行了等级划分,每一种类型的initcall都有对应等级,等级0-7。
路径:include/init/init.h
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*
* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.
*/
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn; \
LTO_REFERENCE_INITCALL(__initcall_##fn##id)
id越小等级越高,Linux会按照等级由高到低顺序执行:
/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall(fn, early)
/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
* Keep main.c:initcall_level_names[] in sync.
*/
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
#define __initcall(fn) device_initcall(fn)
这么做的目的主要是根据优先级依次对设备进行初始化,例如会先初始化与架构相关的,然后再初始化内核子系统。
资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
Linux对initcall的调用
在Linux启动时,会依次遍历所有等级的initcall,以完成一系列的初始化。
initcall的调用流程:
start_kernel->
kernel_init->
kernel_init_freeable->
do_basic_setup->
do_initcalls->
do_initcall_level()
在do_initcalls()函数中,会遍历所有等级的initcall,完成初始化。
static void __init do_initcalls(void)
{
int level;
size_t len = strlen(saved_command_line) + 1;
char *command_line;
command_line = kzalloc(len, GFP_KERNEL);
if (!command_line)
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
//遍历所有等级的initcall,level变量对应等级
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
/* Parser modifies command_line, restore it each time */
strcpy(command_line, saved_command_line);
do_initcall_level(level, command_line);//执行该等级下的所有函数
}
kfree(command_line);
}
do_initcall_level()会执行对应等级下的所有函数:
static void __init do_initcall_level(int level, char *command_line)
{
initcall_entry_t *fn;
parse_args(initcall_level_names[level],
command_line, __start___param,
__stop___param - __start___param,
level, level,
NULL, ignore_unknown_bootoption);
trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
module_init等级
module_init宏使用的是device_initcall,等级为6:
#define device_initcall(fn) __define_initcall(fn, 6)
......
#define __initcall(fn) device_initcall(fn)
......
#define module_init(x) __initcall(x);
在一些内核驱动中,直接使用了device_initcall()来声明驱动入口,其效果与使用module_init是一样的。