Linux内核启动时驱动初始化过程
内核源码树中大部分内容为驱动代码,那么在内核中,每个驱动在内核编译时都是以如下的两种形式进行编译的:
将驱动编译进内核;
将驱动编译成可以在需要时动态插入到内核中的模块,即ko的形式;
整体的流程为先加载内嵌驱动,后加载模块形式的驱动;
start_kernel中会去创建1号进程,此时1号进程执行的函数为kernel_init,kernel_init负责完成大部分的初始化功能;驱动的初始化加载的函数在do_initcalls中;
kernel_init
->kernel_init_freeable
->do_basic_setup
->do_initcalls
->async_synchronize_full //需要等待所有的initcalls完成
在do_initcalls中按照顺序依次加载内嵌驱动,一共定义了如下几种level,在编写时可以自定义驱动初始化函数的level,例如scsi驱动的初始化函数的定义如下所示:
/*drivers/scsi/scsi.c*/
//定义init_scsi为subsys_initcall,level4,会优先于module_init的驱动执行
subsys_initcall(init_scsi);
*//* 数字越小,优先级越高 */
#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)
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);
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);//level 0->7依次执行
}
kfree(command_line);
}
相同的level下执行函数为do_initcall_level,那么当level相同时如何执行呢?链接顺序有关,即顺序与makefile文件中的排序有关
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));//执行一个初始化函数
}
async_synchronize_full 等待所有的内嵌驱动初始化完成后kernel_init才开始继续执行。此时1号进程还处于内核态,直到kernel_init通过run_init_process去加载用户态的init程序(centos 7以后为systemd进程:/sbin/init->systemd),此时由内核态转为用户态;
外部模块加载,此时是由systemd-udev负责监控uevent并根据规则去modporbe驱动来加载的
cmdline中打开initcall_debug即可打印出kernel启动时的信息,可以直观的看到驱动的初始化顺序。
至于为何通过modprobe形式加载的外部模块也会存在initall的信息,原因在于module_init也是一种device_initcall;调用的循序为module_init(x)->__initcall(x)->device_initcall(x);
#ifndef MODULE
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
//#define __initcall(fn) device_initcall(fn)