Linux 驱动开发方法论

有一种感动,叫泪流满面,有一种机制,叫模块机制。显然,这种模块机制给那些Linux 的发烧友们带来了方便,因为模块机制意味着人们可以把庞大的Linux内核划分为许许多多个小的模块。对于编写设备驱动程序的开发者来说,从此以后他们可 以编写设备驱动程序却不需要把她编译进内核,不用reboot机器,她只是一个模块,当你需要她的时候,你可以把她抱入怀中(insmod),当你不再需 要 她的时候,你可以把她一脚踢开(rmmod)。

  于是,忽如一夜春风来,内核处处是模块。让我们从一个伟大的例子去认识模块。这就是 传说的"Hello World!",这个梦幻般的名字我们看过无数次了,每一次她出现在眼前,就意味着我们开始接触一种新的计算机语言了。(某程序员对书法十分感兴趣,退休 后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定 神片刻,泼墨挥毫,郑重地写下一行字:hello world)

  请看下面这段代码,她就是Linux下的一个最简单的模块。当你安装 这个模块的时候,她会用她特有的语言向你表白:“Hello,world!”,而后来你卸载了这个模块,你无情抛弃了她,她很伤心,她很绝望,但她没有抱 怨,她只是淡淡地说,“Goodbye,cruel world!”(再见,残酷的世界!)

你需要使用module_init()和module_exit(),你可以称它们为函 数,不过实际上它们是一些宏,你可以不用去知道她们背后的故事,只需要知道,在Linux Kernel 2.6的世界里,你写的任何一个模块都需要使用它们来初始化或退出,或者说注册以及后来的注销。

  当你用module_init()为一 个模块注册了之后,在你使用insmod这个命令去 安装的时候,module_init()注册的函数将会被执行。而当你用rmmod这个命令去卸载一个模块的时候,module_exit()注册的函数 将会被执行。module_init()被称为驱动程序的初始化入口(driverinitialization entry point)。

   怎么样演示以上代码的运行呢?没错,你需要一个Makefile。

在lwn.net上可以找到这个例子,你可以把以上两个文件放在你的某个目录下,然后执行 make,也许你不一定能成功,因为Linux Kernel 2.6要求你编译模块之前,必须先在内核源代码目录下执行make,换之,你必须先配置过内核,执行过make,然后才能make你自己的模块。原因就不 细说了,你按着要求的这么去做就行了。在内核顶层目录make过之后,你就可以在你当前放置Makefile的目录下执行make了。make之后你就应 该看到一个叫做hello.ko的文件生成了,恭喜你,这就是你将要测试的模块。

  执行命令,#rmmod hello.ko 同时在另一个窗口,用命令tail -f /var/log/messages察看日志文件,你会看到Hello world被打印了出来。再执行命令,#rmmod hello.ko此时,在另一窗口你会看到Goodbye,cruel world!被打印了出来。

到这里,我该恭喜你,因为你已经能够编写Linux内核模块了。这种感觉很美妙,不是吗?你可以嘲笑秦皇汉武略输文采唐宗宋祖稍逊风骚,还可以嘲笑 一代天骄成吉思汗只识弯弓射大雕了。是的,阿娇姐姐告诉我们,只要我喜欢,还有什么不可以。

  日后我们会看到,2.6内核中,每个模块都 是以module_init开始,以module_exit结束。对大多数人来说没有必要知道这是为什么,记住 就可以了,对大多数人来说,这就像是1+1为什么等于2一样,就像是两点之间最短的是直线,不需要证明,如果一定要证明两点之间直线最短,可以扔一块骨头 在B点,让一条狗从A点出发,你会发现狗走的是直线,是的,狗都知道,咱还能不知道吗?对于驱动开发来说,设备模型的理解是根本,毫不夸张得说,理解了设 备模型,再去看那些五花八门的驱动程序,你会发现自己站在了另一个高度,从而有了一种俯视的感觉,就像凤姐俯视知音和故事会,韩峰同志俯视女下属。

   顾名而思义就知道设备模型是关于设备的模型,既不是任小强们的房模,也不是张导的炮模。对咱们写驱动的和不写驱动的人来说,设备的概念就是总线和与 其相连的各种设备了。电脑城的IT工作者都会知道设备是通过总线连到计算机上的,而且还需要对应的驱动才能用,可是总线是如何发现设备的,设备又是如何和 驱动对应起来的,它们经过怎样的艰辛才找到命里注定的那个他,它们的关系如何,白头偕老型的还是朝三暮四型的,这些问题就不是他们关心的了,而是咱们需要 关心的。在房市股市千锤百炼的咱们还能够惊喜的发现,这些疑问的中心思想中心词汇就是总线、设备和驱动,没错,它们就是咱们这里要聊的Linux设备模型 的名角。

  总线、设备、驱动,也就是bus、device、driver,既然是名角,在内核里都会有它们自己专属的结构,在 include/linux/device.h里定义。

有没有发现它们的共性是什么?对,不是很傻很天真,而是很长很复杂。不过不妨把它们看成艺术品,既然是艺术,当然不会让你那么容易的就看懂了,不然 怎么称大师称名家。这么想想咱们就会比较的宽慰了,阿Q是鲁迅对咱们80后最大的贡献。

  我知道进入了21世纪,最缺的就是耐性,房价股 价都让咱们没有耐性,内核的代码也让人没有耐性。不过做为最没有耐性的一代人,还是要平心静气的扫一 下上面的结构,我们会发现,structbus_type中有成员struct kset drivers 和struct kset devices,同时struct device中有两个成员struct bus_type * bus和struct device_driver *driver,struct device_driver中有两个成员structbus_type * bus和struct klist klist_devices。先不说什么是klist、kset,光从成员的名字看,它们就是一个完美的三角关系。我们每个人心中是不是都有两个她?一个 梦中的她,一个现实中的她。

  凭一个男人的直觉,我们可以知道,struct device中的bus表示这个设备连到哪个总线上,driver表示这个备的驱动是什么,struct device_driver中的bus表示这个驱动属于哪个总线,klist_devices表示这个驱动都持哪些设备,因为这里device是复数, 又是list,更因为一个驱动可以支持多个设备,而一个设备只能绑定个驱动。当然,struct bus_type中的drivers和devices分别表示了这个总线拥有哪些设备和哪些驱动。

  单凭直觉,张钰红不了。我们还需要看 看什么是klist、kset。还有上面device和driver结构里出现的kobject结构是什么?作 为一个五星红旗下长大的孩子,我可以肯定的告诉你,kobject和kset都是Linux设备模型中最基本的元素,总线、设备、驱动是西 瓜,kobjcet、klist是种瓜的人,没有幕后种瓜人的汗水不会有清爽解渴的西瓜,我们不能光知道西瓜的的甜,还要知道种瓜人的辛苦。 kobject和kset不会在意自己的得失,它们存在的意义在于把总线、设备和驱动这样的对象连接到设备模型上。种瓜的人也不会在意自己的汗水,在意的 只是能不能送出甜蜜的西瓜。

  一般来说应该这么理解,整个Linux的设备模型是一个OO的体系结构,总线、设备和驱动都是其中鲜活存在 的对象,kobject是它们的基类,所 实现的只是一些公共的接口,kset是同种类型kobject对象的集合,也可以说是对象的容器。只是因为C里不可能会有C++里类的class继承、组 合等的概念,只有通过kobject嵌入到对象结构里来实现。这样,内核使用kobject将各个对象连接起来组成了一个分层的结构体系,就好像马列主义 将我们13亿人也连接成了一个分层的社会体系一样。kobject结构里包含了parent成员,指向了另一个kobject结构,也就是这个分层结构的 上一层结点。而kset是通过链表来实现的,这样就可以明白,struct bus_type结构中的成员drivers和devices表示了一条总线拥有两条链表,一条是设备链表,一条是驱动链表。我们知道了总线对应的数据结 构,就可以找到这条总线关联了多少设备,又有哪些驱动来支持这类设备。

  那么klist呢?其实它就包含了一个链表和一个自旋锁,我们 暂且把它看成链表也无妨,本来在2.6.11内核里,struct device_driver结构的devices成员就是一个链表类型。这么一说,咱们上面的直觉都是正确的,如果买股票,摸彩票时直觉都这么管用,就不 会有咱们这被压扁的一代了。

  现在的人都知道,三角关系很难处。那么总线、设备和驱动之间是如何和谐共处那?先说说总线中的那两条链表 是怎么形成的。内核要求每次出现一个设备就 要向总线汇报,或者说注册,每次出现一个驱动,也要向总线汇报,或者说注册。比如系统初始化的时候,会扫描连接了哪些设备,并为每一个设备建立起一个 struct device的变量,每一次有一个驱动程序,就要准备一个structdevice_driver结构的变量。把这些变量统统加入相应的链 表,device 插入devices 链表,driver插入drivers链表。这样通过总线就能找到每一个设备,每一个驱动。然而,假如计算机里只有设备却没有对应的驱动,那么设备无法工 作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。在他们遇见彼此之前,双方都如同路埂的野草,一个飘啊飘一个摇啊摇,谁也不知道未来在哪里, 只能在生命的风里飘摇。于是总线上的两张表里就慢慢的就挂上了那许多孤单的灵魂。

  devices开始多了,drivers开始多了,他 们像是来自两个 世界,devices们彼此取暖,drivers们一起狂欢,但他们有一点是相同的,都只是在等待属于自己的那个另一半。

   现在,总线上的两条链表已经有了,这个三角关系三个边已经有了两个,剩下的那个那?链表里的设备和驱动又是如何联系那?先有设备还是先有驱动?很久 很久以前,在那激情燃烧的岁月里,先有的是设备,每一个要用的设备在计算机启动之前就已经插好了,插放在它应该在的位置上,然后计算机启动,然后操作系统 开始初始化,总线开始扫描设备,每找到一个设备,就为其申请一个struct device结构,并且挂入总线中的devices链表中来,然后每一个驱动程序开始初始化,开始注册其struct device_driver结构,然后它去总线的devices链表中去寻找(遍历),去寻找每一个还没有绑定驱动的设备,即struct device中的struct device_driver指针仍为空的设备,然后它会去观察这种设备的特征,看是否是他所支持的设备,如果是,那么调用一个叫做 device_bind_driver的函数,然后他们就结为了秦晋之好。换句话说,把struct device中的struct device_driverdriver指向这个驱动,而struct device_driver driver把struct device加入他的那张struct klistklist_devices链表中来。就这样,bus、device和driver,这三者之间或者说他们中的两两之间,就给联系上了。知道其 中之一,就 能找到另外两个。一荣俱荣,一损俱损。

  但现在情况变了,在这红莲绽放的日子里,在这樱花伤逝的日子里,出现了一种新的名 词,叫热插拔。设备可以在计算机启动以后在插入或者拔出计算机了。 因此,很难再说是先有设备还是先有驱动了。因为都有可能。设备可以在任何时刻出现,而驱动也可以在任何时刻被加载,所以,出现的情况就是,每当一个 struct device诞生,它就会去bus的drivers链表中寻找自己的另一半,反之,每当一个一structdevice_driver诞生,它就去bus 的devices链表中寻找它的那些设备。如果找到了合适的,那么OK,和之前那种情况一下,调用 device_bind_driver绑定好。如果找不到,没有关系,等待吧,等到昙花再开,等到风景看透,心中相信,这世界上总有一个人是你所等的,只 是还没有遇到而已。

  设备模型拍得再玄幻,它也只是个模型,必须得落实在具体的子系统,否则就只能抱着个最佳技术奖空遗恨。既然前面已经 以USB子系统的实现分析示例了分析内核源码应该如何入手,那么这里就仍然以USB子系统为例,看看设备模型是如何软着陆的。

内核中USB子系统的结构

  我们已经知道了USB子系统的代码都位于drivers/usb目录下面,也认识了一个 很重要的目录——core子目录。现在,我们再来看一个很重要的模块——usbcore。你可以使用“lsmod”命令看一下,在显示的结果里能够找到有 一个模块叫做usbcore。

找到了usbcore那一行吗?core就是核心,基本上你要在你的电脑里用USB设备,那么两个模块是必须的:一个是usbcore,这就是核心 模块;另一个是主机控制器的驱动程序,比如这里usbcore那一行我们看到的ehci_hcd和uhci_hcd,你的USB设备要工作,合适的USB 主机控制器模块也是必不可少的。

  usbcore负责实现一些核心的功能,为别的设备驱动程序提供服务,提供一个用于访问和控制USB 硬件的口,而不用去考虑系统当前存在哪种主机控制器。至于core、主机控制器和USB驱动三者之间的关系,如下图所示。

USB 驱动和主机控制器就像core的两个保镖,协议里也说了,主机控制器的驱动(HCD)必须位于USB软件的最下一层。HCD提供主机控制器硬件的抽象,隐 藏硬件的细节,在主机控制器之下是物理的USB及所有与之连接的USB设备。而HCD只有一个客户,对一个人负责,就是usbcore。usbcore将 用户的请求映射到相关的HCD,用户不能直接访问HCD。

  core为咱们完成了大部分的工作,因此咱们写USB驱动的时候,只能调用 core的接口,core会将咱们的请求发送给相应的HCD。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值