要了解Linux设备驱动,首先要理解linux的bus、device、driver三个概念。

Bus就是总线,除了我们通常知道的i2c、spi、usb等总线之外,Linux中还有一个很重要的总线platform总线,虽然这个总线是虚拟的,但并不妨碍它的重要性(暂不谈)。

Linux驱动模型中device和driver都挂在总线上,也就是总线上会维护一个devices链表和一个drivers的链表,当新加入一会device或的时候,linux kernel会将这个device加入它所属的bus的devices链表中,然后扫描这个bus的drivers链表,对于每一个driver调用bus指定的match函数,如果匹配的话就调驱动的probe函数,probe函数是初始化设备驱动的关键函数(初始化设备,注册设备访问接口等),同样在加载一个驱动的时候,加入到相应的bus的drivers链表的同样也会扫描bus的devices列表,match成功就调用probe。

可以看出,要将一个设备驱动加载起来,不但要实现驱动代码,还要加入相应的设备结构代码,而且他们的总线类型要匹配。例如如果设备是挂在i2c总线上,那么驱动我们需要写成struct i2c_driver的结构,而设备则应该实现成i2c_board_info(会转换成struct i2c_dev)结构;平台设备的device端要写成struct platform_device结构,驱动端需要写成struct platform_driver结构,而且这两个结构体的.name成员必须一样才能匹配上。

好了,device和driver都实现好了,但是它们什么时候成就良缘呢?这就不得不提起前面说的bus这个职业红娘了。device和driver将自己的个人信息提交给bus这个红娘之后就是默默的祈祷、等待。Bus红娘在获取到一个新的device(征婚男?)或driver(征婚女?)的信息之后,就会把新加入的这个靓男的资料加入到征婚男列表里面,然后翻出证婚女列表来看,一个一个找,看看有没有条件和这个新加入的征婚男相匹配(match)的,如果有,OK他们俩可以谈恋爱(probe)去了,给他们相互的联系方式吧,中介费bus红娘落袋为安,至于他们恋爱谈得如何是否结婚生子这个bus红娘才不会管。这么看来bus红娘还是很有职业道德的,收了钱就会认认真真的把事情干好,不会光喊为人民服务这么伟大的口号,而是实实在在的帮助很多单身解决了终生幸福问题。而且难能可贵的是,bus红娘很讲原则的不会只要有钱什么都干,你要是个靓男(device),OK,bus红娘会很负责的去靓女群(drivers)中给你找对象,不会给你也牵手一个靓仔回来,bus红娘说:“我不支持同性恋”。

为了早日过上幸福的二人生活, devices们早早的就去了bus红娘婚介所登记,盼星星盼月亮,有的device终于等到了自己的driver, 牵手而去。久久等不到另一半的device忍不住了,过来问bus红娘怎么回事,bus红娘说:“符合你条件的另一半还没有出现,我也没办法,回去继续等吧!”,能怎么办呢,继续等吧,bus红娘很有信誉的,收钱绝对办事。Device回到家中烧起了高香:“哪位大神显显灵,让我的女神早日出现吧!”

看到device如此执着、如此虔诚,bus红娘也帮不上忙,善良的我们是不是想想办法帮帮他吧(我们可以帮device早日找到他的另一半,谁来解决我们的单身问题,谁来结束我们的苦逼生活呢,shit!)

Linux中初始化程序是分等级的,在init.h中我们可以找到

#define early_initcall(fn) __define_initcall("early",fn,early)

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

一共17个等级(实际上后接_sync的7个等级是几乎没有用到的)。优先级从上往下越来越低,early > 0 > 1....> 7s(从下面的INITCALLS的链接顺序可以看出)

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是在文件定义的:

.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {

__initcall_start = .;

INITCALLS

__initcall_end = .;

}

而在Vmlinux.lds.h中我们可以找到

#define INITCALLS \

*(.initcallearly.init) \

VMLINUX_SYMBOL(__early_initcall_end) = .; \

*(.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)

可以看到优先级越来越低。

好吧,这么多的等级,怎么让driver早些出现呢,可不可以随便指定一个呢,比如直接用pure_initcall。大哥,虽然没人说你这样做伤天害理,但大家都是文明人,买车票上厕所都是要排队的。不过孕妇是可以不排队的,好吧,我们就让指定的driver享享孕妇的待遇吧。

但是查队插到什么地方好呢,是不是越靠前越好呢?当然不是,我们先来理理通常的初始化函数的顺序设定。通常设备的加载是arch_initcall,从init/main.c看起:

-->Start_kernel (kernel启动的第一个函数)

...

-->setup_arch (arch/arm/kernel/setup.c)

...

-->init_machine = mdesc->init_machine;//一般的设备都是在                                       //mdesc->init_machine中定义加载

另外我们可以在arch/arm/kernel/setup.c中看到:

static int __init customize_machine(void)

{

/* customizes platform devices, or adds new ones */

if (init_machine)

init_machine();

return 0;

}

arch_initcall(customize_machine);

这里对customize_machine定义的初始化级别为arch_initcall,也就是说一般device加载的优先级是arch_initcall,那么我们就没必要将driver提到arch_initcall之前吧,不然driver妹妹也是再那空等,咱们device还是要讲点绅士风度的,约会不迟到。

好吧我们就可以给开后门的这个驱动优先级设定为arch_initcall、arch_initcall_sync、subsys_initcall、ubsys_initcall_sync、fs_initcall、fs_initcall_sync、rootfs_initcall等几个等级了(有的人要问了,不还有device_initcall、device_initcall_sync、late_initcall、late_initcall_sync几个等级呢?额,正常我们设备驱动都是用device_initcall这个等级的,咱们没必要帮倒忙吧)。至于具体给他那个优先级呢,这个就要考虑些其他因素了,比如这个驱动的初始化是否依赖其他设备初始化的完成,如果要依赖于某个设备的初始化,那么就优先级就必须比另外那个的低。

好吧,下面我们给出一般设备和驱动加载的优先级列表:

初始化等级

对应的初始化设备或驱动的函数

备注

early_initcall

migration_init和spawn_ksoftirqd等

主要是register_cpu_notifier

pure_initcall

init_cpufreq_transition_notifier_list

core_initcall

netlink_proto_init、cpuidle_init、xxx_gpio_init、filelock_init、pm_init、sock_init、wakelocks_init等

主要是一些关键部分的初始化,像gpio、通信、电源管理等部分

core_initcall_sync

postcore_initcall

backlight_class_init、dma_sysclass_init、i2c_init、kobject_uevent_init、pci_driver_init、spi_init、tty_class_init等

主要是一些总线的节点的创建和链表初始化等(总线驱动的加载在后面)

postcore_initcall_sync

arch_initcall

xxx_init_device、xxx_devices_setup、customize_machine、platform_init等

主要是板级设备的加载(i2c、spi、usb、串口等以及一些外设的加载)

arch_initcall_sync

subsys_initcall

blk_ioc_ini、xxx_dma_init、xxx_i2c_init_driver、usb_init、xxx_spi_init等

块设备驱动、以及主要的bus总线驱动

subsys_initcall_sync

fs_initcall

inet_init、alignment_init、chr_dev_init、tracer_alloc_buffers等

fs_initcall_sync

rootfs_initcall

populate_rootfs、default_rootfs

Rootfs先关初始化

device_initcall

一般的外设驱动的加载函数

module_init = device_initcall,外设驱动

device_initcall_sync

late_initcall

late_resume_init 等

late_initcall_sync

不同的芯片厂家给的BSP包中具体的初始化顺序可能会有些差异,但是大概都是和这差不多。

另外一般的外设驱动加载函数都是device_initcall级别的,如果外设很多,必然导致这一等级的加载函数很多。如何在这同一等级中调整driver加载的顺序呢?

对于同一等级(device_initcall)中的driverA和driverB,driverA说我比driverB大点,我要排到前面点去,driverB也没有意见,那好吧,我们可以来改改Makefile试试。

下面贴我自己写的几个驱动看看,这几个驱动都是放在/drivers/i2c/chip目录下的,先看我最初的Makefile:

好,接着可以从System.map中看看各个驱动的链接的顺序:

看到了吧,链接中的顺序和Makefile中的先后顺序是一模一样的。接下来我们调整一下Makefile中编译的顺序,看看链接的顺序是否也会跟着变过来。

这是修改后的顺序,把bma023放到中间了,重新编译来看看System.map里面的顺序

看到了吧,真的也跑到中间去了,这样是可以修改设备驱动的加载顺序的,说明了linux kernel下链接的顺序和编译的顺序是一样的(顶层的Makefile中规则指定的吧,具体我没弄明白)。看链接的符号表我想大家应该想起了前面定义在INITCALLS里面的*(.initcall6.init)了吧,是的上面这个函数就是按顺序放在*(.initcall6.init)这个段中的。

对于不同目录的driver如果需要调整顺序,当然也可以调整/drivers目录下Makefile下各种驱动类型的编译顺序(不过这个必须谨慎,不能随便乱改)。

总结:linux设备驱动有bus、device、driver三个重要的组成部分,先有bus,然后在bus上挂device和driver,有匹配的才调驱动的probe函数真正的初始化设备并注册API;

修改设备加载的顺序可以有两种方法,一种是修改设备加载的初始化等级,另外就是修改Makefile中的顺序。

备注:在Linux中,usb、sd、i2c、spi等总线上可以挂设备,但是它们自己相对于系统来说也是设备,也要有自己的驱动,在linux中,所有的这些总线都挂在platform这个虚拟总线上,在customize_machine(arch_initcall级)的时候,我们先会加载这些总线的platform_device结构,而这些总线的驱动(platform _driver)一般是定义为subsys_initcall级。

总线设备和驱动匹配上后就会真正的初始化总线,然后这个婚介所正式挂牌成立。这么看来platform bus就是婚介所总部了 ,platform bus设备节点的创建和相关结构初始化在INITCALLS之前。

浅谈设备、驱动的加载和匹配相关推荐

  1. 浅谈DirectX的模型加载

    浅谈DirectX的模型加载 xanxus - 2010年10月3日 - DirectX - 0 Comments 喜欢这篇文章吗?分享给你的朋友吧~  基于DirectX的游戏开发中,人物和模型由针 ...

  2. 非即插即用型设备驱动的加载过程

    非即插即用型设备驱动的加载过程 1. 非PnP总线驱动在系统启动时通过扫描注册表发现非PnP设备的存在,并向OS报告ID信息.(例如根总线驱动通过扫描 HKLM\ SYSTEM\ CurrentCon ...

  3. 浅谈前端实现页面加载进度条以及 nprogress.js 的实现

    以前在 Vue 的项目用了 nprogress 这个插件,一直对于其如何得知加载进度充满好奇,最近又看到了「前端如何实现页面加载进度条」这个问题,今天周六恰好一探究竟.以下仅为一家之言,如有异议,欢迎 ...

  4. html 中加载字体太慢,浅谈CSS字体的加载加速问题

    除了各种特定字体系列外(如 Times.Verdana.Helvetica 或 Arial),CSS定义了 5 种通用字体系列: Serif 字体    这些字体成比例,而且有上下短线.如果字体中的所 ...

  5. 浅谈《MediaPlayer》加载进度定时刷新

    这是我在CSDN的第一篇博客,也是我第一次写博客,当我踏进"码农"的世界的时候,我就要坚持往下走,学习更多的东西,废话不多说,开始进入正题. 由于才开始从入门到进阶的学习阶段,存在 ...

  6. 浅谈“领域驱动设计”

    Eric Evans所著的<领域驱动设计>(Domain-Driven Design:通常简称为"DDD")一书可以说是经典中的经典,虽然"领域"的 ...

  7. linux Pci字符驱动基本加载流程

    今天有朋友问我linux系统Pci字符驱动加载流程,简单整理了一下,顺便做个记录. 首先说下需要包含的头文件: 一个完整的字符驱动一般包含下面这些头文件: #include <linux/typ ...

  8. fedora7 WIFI驱动的加载与wpa_supplicant的配置

    1.wifi的驱动模块式ath_pci,运行加载命令modprobe ath_pci, ifconfig命令可以查看wifi驱动是否加载成功,加载成功的话,会看到ath0   /   wifi网络. ...

  9. 一个驱动无法加载的分析

    一个驱动无法加载的分析 客户反馈一个问题,原工作很好的usb key设备,安装 NCT_2000_XP 后,运行测试程序找硬件,提示没找到.检查系统 %systemroot%/system32/dir ...

最新文章

  1. pytorch源码解析:Python层 pytorchmodule源码
  2. 动态规划python_机器人是如何规划路径的?动画演示一下吧
  3. 用Java实现天天酷跑(附源码),只能用牛逼来形容了!
  4. shell 后台执行脚本
  5. 让LoadRunner再次走下神坛
  6. Unity C# Job System介绍(四) 并行化Job和故障排除(完结)
  7. java流与文件——java生成解压缩文件(夹)
  8. swiper链接href无效
  9. python设计模式总结
  10. OpenCV人脸识别之一:数据收集和预处理
  11. 【SQL练习题】case when实现按要求排序
  12. MOSSE相关滤波算法学习笔记
  13. Linux开机启动过程分析
  14. 安卓期末作品小项目_《去月球》电影版今年上映;电子竞技入选亚室会正式比赛项目...
  15. windows的文件路径文件(夹)名的分割符也可以用正斜杠
  16. 2021927学习总结
  17. PHPWord替换word模板内容时,存在表格,且不确定表格行数的处理方式
  18. 1553B数据总线用终端电连接器-DK-6211
  19. 浅析双绞线中五类线每根线的功能
  20. Jeff Dean长文展望:机器学习领域的五大潜力趋势

热门文章

  1. 网上商城系统JavaWeb购物商城 商城项目Servlet+JSP+MySQL
  2. workbench的稳态热分析的步骤
  3. c语言json结构体_C语言解析JSON源码
  4. 学术报告系列(二) - 智能自主作业机器人感知与控制技术及发展趋势
  5. 四十岁软件测试员技术支持,【软件测试工程师(成都)财务专员面试题目|面试经验】-看准网...
  6. PC-DMIS 如何更改CAD模型的坐标系
  7. STM32Cube开发方式
  8. android 动态壁纸设计
  9. python函数参数定义_Python函数定义及参数详解
  10. 初出茅庐的小李第3篇博客《5G物联网及NB-IoT技术详解》读书笔记1