Linux 设备驱动 | 加载顺序、匹配过程与设备树处理

注:本文为 “Linux 设备和驱动加载” 相关文章合辑

未整理去重。
如有内容异常,请看原文。


Linux设备和驱动加载的先后顺序

maopig 于 2012-03-20 22:50:04 发布

一、Linux驱动注册顺序控制

Linux 驱动先注册总线,总线上既可以先挂载设备(device),也可以先挂载驱动(driver)。那么,究竟如何控制这两者的先后顺序呢?

Linux系统使用两种方式来加载系统中的模块:动态加载和静态加载。

静态加载

静态加载是将所有模块的程序编译到Linux内核中,由do_initcall函数进行加载。核心进程(/init/main.c)的执行流程为:kernel_init -> do_basic_setup() -> do_initcalls()。在do_initcalls函数中,会将在__initcall_start__initcall_end之间定义的各个模块依次加载。

那么,在__initcall_start__initcall_end之间都包含哪些内容呢?通过查看/arch/powerpc/kernel/vmlinux.lds文件,可以找到.initcall.init段的定义如下:

.initcall.init : {
    __initcall_start = .;
    *(.(initcall0.init) *(.(initcall0s.init) *(.(initcall1.init)  *(.(initcall1s.init) *(.(initcall2.init) *(.(initcall2s.init)  *(.(initcall3.init) *(.(initcall3s.init) *(.(initcall4.init)  *(.(initcall4s.init) *(.(initcall5.init) *(.(initcall5s.init)  *(.(initcallrootfs.init) *(.(initcall6.init) *(.(initcall6s.init)  *(.(initcall7.init) *(.(initcall7s.init)
    __initcall_end = .;
}

从上述定义可以看出,在这两个宏之间依次排列了14个等级的宏。由于这些宏是按先后顺序链接的,所以这14个宏具有优先级,顺序为:0 > 1 > 1s > 2 > 2s …… > 7 > 7s

这些宏具体有什么意义呢?这需要查看/include/linux/init.h文件,其中定义了具体的宏:

#define pure_initcall(fn)       __define_initcall("0",fn,0)
#define core_initcall(fn)       __define_initcall("1",fn,1)
#define core_initcall_sync(fn)      __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)       __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)
#define arch_initcall(fn)    __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)      __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)     __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)
#define fs_initcall(fn)          __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)     __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)      __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)     __define_initcall("6",fn,6)
#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)
#define late_initcall(fn)     __define_initcall("7",fn,7)
#define late_initcall_sync(fn)       __define_initcall("7s",fn,7s)

我们平时使用的module_init在静态编译时就相当于device_initcall。例如,在2.6.24的内核中,gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init。由于arch_initcall的优先级大于module_init,所以gianfar设备驱动的device会先于driver在总线上添加。

二、系统初始化函数集(subsys_initcall)和初始化段应用

(一)系统初始化调用函数集分析(静态)

1. 函数定义

在Linux内核代码里,运用了subsys_initcall来进行各种子系统的初始化。以2.6.29内核为例,在<include/linux/init.h>下可以找到subsys_initcall的定义:

#define pure_initcall(fn)       __define_initcall("0",fn,0)
#define core_initcall(fn)       __define_initcall("1",fn,1)
#define core_initcall_sync(fn)  __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)   __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn)       __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)  __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)     __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn)         __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)    __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)      __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)     __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn)        __define_initcall("7",fn,7)
#define late_initcall_sync(fn)   __define_initcall("7s",fn,7s)

__define_initcall又被定义为:

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

所以,subsys_initcall(fn)等价于__initcall_fn4,它将被链接器放于section .initcall4.init中。(__attribute__相关内容将在另一篇文章中介绍)

2. 初始化函数集的调用过程执行过程

系统启动后,在rest_init中会创建init内核线程,执行流程为:start_kernel -> rest_init -> init -> do_basic_setup -> do_initcalls。在do_initcalls中,会把.initcall.init中的函数依次执行一遍:

for (call = __initcall_start; call < __initcall_end; call++) {
   
    // ......
    result = (*call)();
    // ........
}

这个__initcall_start是在文件<arch/xxx/kernel/vmlinux.lds.S>中定义的:

.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
    __initcall_start = .;
    INITCALLS
    __initcall_end = .;
}

INITCALLS被定义于<asm-generic/vmlinux.lds.h>

#define INITCALLS    \
    *(.initcall0.init)   \
    *(.initcall0s.init)   \
    *(.initcall1.init)   \
    *(.initcall1s.init)   \
    *(.initcall2.init)   \
    *(.initcall2s.init)   \
    *(.initcall3.init)   \
    *(.initcall3s.init)    \
    *(.initcall4.init)   \
    *(.initcall4s.init)   \
    *(.initcall5.init)   \
    *(.initcall5s.init)   \
    *(.initcallrootfs.init)   \
    *(.initcall6.init)   \
    *(.initcall6s.init)   \
    *(.initcall7.init)   \
    *(.initcall7s.init)

(二)基于模块方式的初始化函数(动态)

1. 函数定义

subsys_initcall的静态调用方式的来龙去脉已阐述清楚,下面来看动态方式的初始化函数调用(模块方式)。在<include/linux/init.h>里,如果MODULE宏被定义,说明该函数将被编译进模块里,在系统启动后以模块方式被调用:

#define core_initcall(fn)     module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn)    module_init(fn)
#define subsys_initcall(fn)  module_init(fn)
#define fs_initcall(fn)       module_init(fn)
#define device_initcall(fn)   module_init(fn)
#define late_initcall(fn)     module_init(fn)

这意味着对于驱动模块,使用subsys_initcall等价于使用module_init

2. module_init分析

下面来看看module_init宏究竟做了什么:

#define module_init(initfn)   \
    static inline initcall_t __inittest(void) \ 
    {
    return initfn; }   \
    int init_module(void) __attribute__((alias(#initfn))); 

typedef int (*initcall_t)(void); 

在以模块方式编译一个模块的时候,会自动生成一个xxx.mod.c文件,在该文件里面定义一个struct module变量,并把init函数设置为上面的init_module()。而上面的这个init_module,被alias成模块的初始化函数(参考<gcc关键字:__attribute__, alias, visibility, hidden>)。

也就是说,模块装载的时候(insmodmodprobe),sys_init_module()系统调用会调用module_init指定的函数(对于编译成模块的情况)。

3. 模块的自动加载

内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过sysfs内核虚拟文件系统导出。sysfs文件系统由系统初始化脚本挂载到/sys上。udev扫描sysfs文件系统,根据硬件设备信息生成热插拔(hotplug)事件,udev再读取这些事件,生成对应的硬件设备文件。由于没有实际的硬件插拔动作,所以这一过程被称为coldplug

udev完成coldplug操作,需要下面三个程序:

  • udevtrigger:扫描sysfs文件系统,生成相应的硬件设备hotplug事件。
  • udevd:作为deamon,记录hotplug事件,然后排队后再发送给udev,避免事件冲突(race conditions)。
  • udevsettle:查看udev事件队列,等队列内事件全部处理完毕才退出。

要规定事件怎样处理就要编写规则文件,规则文件是udev的核心,没有规则文件,udev无法自动加载硬件设备的驱动模块。规则文件一般位于<etc/udev/rules.d>

(三)初始化段的应用

这里给出一个简单的初始化段的使用例子,将a.c编译成一个动态库,其中,函数a()和函数c()放在两个不同的初始化段里,函数b()默认放置;编译main.c,链接到由a.c编译成的动态库,观察各函数的执行顺序。

// a.c
#include <stdio.h>
typedef int (*fn) (void);
int a(void)
{
   
    printf("a\n");
    return 0;
}
__attribute__((__section__(".init_array.2"))) static fn init_a = a;
int c(void)
{
   
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值