Linux内核启动中驱动初始化过程

Linux内核启动时驱动初始化过程

一、驱动模块存在形式

内核源码树中大部分内容为驱动代码,那么在内核中,每个驱动在内核编译时都是以如下的两种形式进行编译的:

1、静态编译

将驱动编译进内核;

2、动态编译

将驱动编译成可以在需要时动态插入到内核中的模块,即ko的形式;

二、内核初始化时驱动是如何加载的?

整体的流程为先加载内嵌驱动,后加载模块形式的驱动;

1、内嵌驱动加载

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));//执行一个初始化函数
}

2、外部模块加载

async_synchronize_full 等待所有的内嵌驱动初始化完成后kernel_init才开始继续执行。此时1号进程还处于内核态,直到kernel_init通过run_init_process去加载用户态的init程序(centos 7以后为systemd进程:/sbin/init->systemd),此时由内核态转为用户态;

外部模块加载,此时是由systemd-udev负责监控uevent并根据规则去modporbe驱动来加载的

3、debug

cmdline中打开initcall_debug即可打印出kernel启动时的信息,可以直观的看到驱动的初始化顺序。
Linux内核启动中驱动初始化过程_第1张图片
至于为何通过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)

你可能感兴趣的:(Linux驱动,linux,驱动开发,运维)