点击打开链接

Linux驱动先注册总线,总线上可以先挂device,也可以先挂driver,那么究竟怎么控制先后的顺序呢。

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

静态加载:将所有模块的程序编译到Linux内核中,由do_initcall函数加载

核心进程(/init/main.c)kernel_inità do_basic_setup()à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)和初始化段应用

前言:前段时间做一个项目需要设计一个动态库,并希望在加载库的同时自动执行一些初始化动作,于是联想到了linux内核众子系统的初始化,于是研 究之,并在过这程中发现了初始化段的存在,利用初始化段实现了该功能。工作一年,笔记积累多了,慢慢变得杂乱无章,于是开博,一方面整理笔记,梳理知识, 另一方面和大家交流,共同进步。

keyword:subsys_initcall, init, init_call

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

1.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将会在另一篇文章中介绍)

1.2 初始化函数集的调用过程执行过程:

start_kernel->rest_init

系统在启动后在rest_init中会创建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)

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

2.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)

这是在定义MODULE的情况下对subsys_initcall的定义,就是说对于驱动模块,使用subsys_initcall等价于使用module_init

2.2 module_init 分析下面先看看module_init宏究竟做了什么

#define module_init(initfn)     \

static inline initcall_t __inittest(void)  \ /*定义此函数用来检测传入函数的类型,并在编译时提供警告信息*/

{ return initfn; }     \

int init_module(void) __attribute__((alias(#initfn))); /*声明init_modlue为 initfn的别名,insmod只查找名字为init_module函数并调用*/

typedef int (*initcall_t)(void); /*函数类型定义*/

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

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

2.3 module的自动加载内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过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>

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

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

{

printf("c\n");

return 0;

}

__attribute__((__section__(".init_array.1"))) static fn init_c = c;

int b()

{

printf("b\n");

return 0;

}

# cat main.c

#include<stdio.h>

int b();

int main()

{

printf("main\n");

b();

}

# cat mk.sh

gcc -fPIC -g -c a.c

gcc -shared -g -o liba.so a.o

cp liba.so /lib/ -fr

gcc main.c liba.so

ldconfig

./a.out

# gcc -fPIC -g -c a.c

# gcc -shared -g -o liba.so a.o

# cp liba.so /lib/

# gcc main.c liba.so

# ldconfig

# ./a.out

a

c

main

b

在类unix操作系统中,驱动加载方式一般分为:动态加载和静态加载,下面分别对其详细论述。

一、动态加载

动态加载是将驱动模块加载到内 核中,而不能放入/lib/modules/下。

    在2.4内核中,加载驱动命令为:insmod ,删除模块为:rmmod;

    在2.6以上内核中,除了insmod与rmmod外,加载命令还有modprobe;

    insmod与modprobe不同之处:

    insmod 绝对路径/××.o,而modprobe ××即可,不用加.ko或.o后缀,也不用加路径;最重要的一点是:modprobe同时会加载当前模块所依赖的其它模块;

    lsmod查看当前加载到内核中的所有驱动模块,同时提供其它一些信息,比如其它模块是否在使用另一个模块。

二、静态加载

(一)概念

    在执行make menuconfig命令进行内核配置裁剪时,在窗口中可以选择是否编译入内核,还是放入/lib/modules/下相应内核版本目录中,还是不选。

(二) 操作步骤

    linux设备一般分为:字符设备、块设备和网络设备,每种设备在内核源代码目录树drivers/下都有对应的目录,其加载方法类似,以下以字符设备静 态加载为例,假设驱动程序源代码名为ledc.c,具体操作步骤如下:

    第一步:将ledc.c源程序放入内核源码drivers/char/下;

    第二步:修改drivers/char/Config.in文件,具体修改如下:

         按照打开文件中的格式添加即可;

         在文件的适当位置(这个位置随便都可以,但这个位置决定其在make menuconfig窗口中所在位置)加入以下任一段代码:

         

         tristate 'LedDriver' CONFIG_LEDC

         if [ "$CONFIG_LEDC" = "y" ];then

         bool '   Support for led on h9200 board' CONFIG_LEDC_CONSOLE

         fi

         说明:以上代码使用tristate来定义一个宏,表示此驱动可以直接编译至内核(用*选择),也可以编制至/lib/modules/下(用M选择), 或者不编译(不选)。



         bool 'LedDriver' CONFIG_LEDC

         if [ "$CONFIG_LEDC" = "y" ];then

         bool '   Support for led on h9200 board' CONFIG_LEDC_CONSOLE

         fi

         说明:以上代码使用tristate来定义一个宏,表示此驱动只能直接编译至内核(用*选择)或者不编译(不选),不能编制至/lib/modules/ 下(用M选择)。

   

    第三步:修改drivers/char/Makefile文件

         在适当位置加入下面一行代码:

         obj-$(CONFIG_LEDC)   +=   ledc.o

         或者在obj-y一行中加入ledc.o,如:

         obj-y += ledc.o mem.o 后面不变;



    OK,经过以上的设置就可以在执行make menuconfig命令后的窗口中的character devices---> 中进行选择配置了。选择后重新编译就ok了。

linux设备和驱动加载的先后顺序的更多相关文章

  1. linux 设备驱动加载的先后顺序

    Linux驱动先注册总线,总线上可以先挂device,也可以先挂driver,那么究竟怎么控制先后的顺序呢. 1.初始化宏 Linux系统使用两种方式去加载系统中的模块:动态和静态. 静态加载:将所有 ...

  2. 如何调整Linux内核启动中的驱动初始化顺序-驱动加载优先级

    Linux内核为不同驱动的加载顺序对应不同的优先级,定义了一些宏: include\linux\init.h #define pure_initcall(fn) __define_initcall(& ...

  3. EDK II之USB设备驱动程序的加载与运行

    本文简单介绍一下USB设备的驱动程序是如何匹配设备以及被加载的: 上文(UDK中USB总线驱动的实现框架)提到USB总线枚举设备的最后一步是调用gBS->ConnectController()去 ...

  4. (DT系列四)驱动加载中, 如何取得device tree中的属性

    本文以At91rm9200平台为例,从源码实现的角度来分析驱动加载时,Device tree的属性是如何取得的.一:系统级初始化DT_MACHINE_START 主要是定义"struct m ...

  5. 【转】(DT系列四)驱动加载中, 如何取得device tree中的属性

    原文网址:http://www.cnblogs.com/biglucky/p/4057488.html 本文以At91rm9200平台为例,从源码实现的角度来分析驱动加载时,Device tree的属 ...

  6. 老调重弹:JDBC系列之<驱动加载原理全面解析) ----转

      最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读者 ...

  7. 关于Eclipse在servlet中连接数据库时出现驱动加载失败的解决

    问题:在队友发来的项目中想将他获取到的数据通过数据库储存,出现驱动加载失败问题 解决:首先百度了下相关情况,大多数都是说下载mysql-connector-java-5.1.39-bin.jar包,然 ...

  8. 【ESXI6.0】 ESXI6.0安装时无法安装网卡驱动的解决方法及将网卡驱动加载进ISO

    http://blog.163.com/xifanliang@yeah/blog/static/115078488201571584321787/ 若安装时提示如下图所示 之后安装无法完成,会提示没有 ...

  9. Linux下文件 ~/.bashrc 和 ~/.bash_profile 和 /etc/bashrc 和 /etc/profile 的区别 | 用户登录后加载配置文件的顺序

    转自 https://blog.csdn.net/secondjanuary/article/details/9206151 文件说明: /ect/profile 此文件为系统的每个用户设置环境信息, ...

随机推荐

  1. python语言学习

    前段时间要做视频直播需要编写自动模块,就考虑使用python脚本语言,python的好多语法都是很独特的,比如数据类型不需要预定义,缩进的方式等,另外功能也很强大,豆瓣就是用python写的.我写的部 ...

  2. Ajax PHP JavaScript MySQL实现简易的无刷新在线聊天室

    思路 消息显示区 发消息 板块 消息显示 消息发送 优化 显示非重复性的数据 优化显示 加上滚动条 每次都显示最新消息 完整代码 前端代码 数据库表结构 服务器端代码 总结与展望 总结 展望 为更好的 ...

  3. 【安卓开发】Android为什么选择binder

    Binder (Android技术内幕): 在上面这些可供选择的方式中,Android使用得最多也最被认可的还是Binder机制. 为什么会选择Binder来作为进程之间的通信机制呢?因为Binder ...

  4. EBS技术开发之VPD策略

    VPD (虚拟专用数据库的简称),主要作用是根据运行环境的上下文,隐式的添加条 件. 好处是在数据库层解决细粒度的角色权限访问,避免在中间层写大量代码:坏处 是数据屏蔽的逻辑太隐蔽了,对于分析查找问题 ...

  5. Effective C++ ——构造/析构/赋值运算符

    条款五:了解C++默认编写并调用那些函数 是否存在空的类? 假设定义类为class Empty{}:当C++编译器处理过后会变成如下的形式: class Empty{ Empty(){} ~Empty ...

  6. Ubuntu下配置Telnet服务器

    1. 首先介绍linux中的守护进程 在Linux系统中有一个特殊的守护进程inetd(InterNET services Daemon),它用于Internet标准服务,通常在系统启动时启动.通过命 ...

  7. 1091. Acute Stroke (30)

    题目如下: One important factor to identify acute stroke (急性脑卒中) is the volume of the stroke core. Given ...

  8. Linux常用网络命令整理

    Linux上有一些非常常用的命令,来帮助我们监控网络状况. 1.Tcpdump命令 tcpdump可以将网络中传送的数据包的"头"完全截获下来提供分析.它支持针对网络层.协议.主机 ...

  9. there was no endpoint listening at net.pipe://localhost/PreviewProcessingService/ReportProcessing

    当你在开发reporting service报表时,进行报表的preview时报下图中的错误,以下方法可以让你直接跳过这个错误,继续查看报表的运行结果. 直接选择你需要运行查看的报表右击run就可以, ...

  10. Cocos2D iOS之旅:如何写一个敲地鼠游戏(十一):完善游戏逻辑

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...