Linux 子系统初始化

问题

我们知道,内核驱动程序有两种加载方式,分别是运行时执行insmod 加载和 随内核启动加载。那么当驱动程序配置为随内核启动加载的时候,在内核启动的时候,是如何把驱动程序加载进去的呢?

Kernel 启动时和驱动程序加载相关的代码

do_initcalls 函数

这里是用了 4.4的内核作为例子的。
首先看一下 从 kernel_init 进入的相关代码。
static void __init do_initcalls(void)
{   
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);       
}  

这段代码的意思很明确,就是执行一个循环,依次调用do_initcall_level(level); level的变化为0 到 initcall_levels 数组元素的个数。其实也就是为所有的initial level调用一次do_initcall_level(level)。
每个level的含义如下:

#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)

.init.data 段

在链接的时候,lds文件把所有的子系统的初始化函数放在.init.data 段里,这里面又按level依次存放了各个level的初始化函数。在vmlinux.lds 文件里我们可以看到下面的代码:

 .init.data : {
  *(.init.data) *(.meminit.data) *(.init.rodata) . = ALIGN(8); __start_kprobe_blacklist = .; *(_kprobe_blacklist) __stop_kprobe_blacklist = .; *(.meminit.rodata) . = ALIGN(8); __clksrc_of_table = .; *(__clksrc_of_table) *(__clksrc_of_table_end) . = ALIGN(8); __cpu_method_of_table = .; *(__cpu_method_of_table) *(__cpu_method_of_table_end) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(32); __earlycon_table = .; *(__earlycon_table) *(__earlycon_table_end) . = ALIGN(8); __earlycon_of_table = .; *(__earlycon_of_table) *(__earlycon_of_table_end)
  . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
  __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
  __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
  __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
  . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info)
 }

这里我们可以看到 .initcall0.init , .initcall1.init,…… .initcall7.init 是依次紧挨着排放的。而在代码中initcall_levels数组的元素保存了这些段的首地址。通过访问initcall_levels数组的元素,我们就能拿到这些段里的数据。那么这些段里保存了什么呢?通过map文件,我们可以看到这些段里保存的是指向各子系统或者驱动程序的初始化函数的函数指针。

./System.map:54658:c085e008 T __initcall0_start
./System.map:54659:c085e008 t __initcall_ipc_ns_init0
./System.map:54660:c085e00c t __initcall_init_mmap_min_addr0
./System.map:54661:c085e010 t __initcall_init_cpufreq_transition_notifier_list0
./System.map:54662:c085e014 t __initcall_net_ns_init0
./System.map:54663:c085e018 T __initcall1_start
./System.map:54664:c085e018 t __initcall_vfp_init1

do_initcall_level

static void __init do_initcall_level(int level)
{
    initcall_t *fn;

    strcpy(initcall_command_line, saved_command_line);
    parse_args(initcall_level_names[level],
           initcall_command_line, __start___param,
           __stop___param - __start___param,
           level, level,
           NULL, &repair_env_string);

    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        do_one_initcall(*fn);
}

这段函数将会依次执行level指定的子系统的初始化函数。例如,当level=0时,fn开始的值为0xc085e008, 它和__initcall_ipc_ns_init0的地址是一样的。fn会把__initcall_ipc_ns_init0函数指针所指向的函数地址传给do_one_initcall进行执行。当下一个循环开始时,自变量fn自增1,由于fn的数据类型是函数指针,因此fn的值会增加4,为__initcall_init_mmap_min_addr0的地址。就这样,fn每次循环时自增1,依次指向段内的各个函数指针的地址,直到指向下一个段时结束。
通过这种方法,kernel会执行所有的子系统和设备驱动程序的初始化函数。

编译时产生 _initcallx函数指针

如何为每个子系统和设备驱动程序产生一个这样的函数指针呢?先看一下__define_initcall

#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn; \
    LTO_REFERENCE_INITCALL(__initcall_##fn##id)

这段宏会产生一个指向fu函数的__initcall_fnx 函数指针,这个指针存放在.initcallx.init 段里。在各个驱动中都会有一个宏来声明驱动模块,例如 module_platform_driver 会通过下面一系列的宏展开,生成一个指向platform_driver_register的初始化函数指针

#define module_platform_driver(__platform_driver) \                                                                                                  
    module_driver(__platform_driver, platform_driver_register, \                                                                                     
    platform_driver_unregister) 
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
    return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
#define module_init(x)  __initcall(x);

经过一番查找,最终函数指针指向了下面这个函数:

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
                struct module *owner)           
{ 
    drv->driver.owner = owner; 
    drv->driver.bus = &platform_bus_type;
    drv->driver.probe = platform_drv_probe;
    drv->driver.remove = platform_drv_remove;
    drv->driver.shutdown = platform_drv_shutdown;

    return driver_register(&drv->driver);
} 
EXPORT_SYMBOL_GPL(__platform_driver_register);
阅读更多
个人分类: Arm-Linux
所属专栏: Linux 内核 驱动
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭