这个故事中使用的是2.6.10的内核代码.Linux内核代码目录中, 所有去设备驱动程序有关的代码都在drivers/目录下面,在这个目录中我们用ls命令可以看到很多子目录. localhost:/usr/src/linux-2.6.10/drivers # ls 其中usb目录包含了所有usb设备的驱动,而usb目录下面又有它自己的子目录,进去看一下, localhost:/usr/src/linux-2.6.10/drivers # cd usb/ 注意到每一个目录下面都有一个Kconfig文件和一个Makefile,这很重要.稍后会有介绍. 而我们的故事其实是围绕着drivers/usb/storage这个目录来展开的.实际上这里边的代码清清楚楚地展示了我们日常频繁接触的U盘是如何工作的,是如何被驱动起来的.但是这个目录里边的冬冬并不是生活在世外桃源,他们总是和外面的世界有着千丝万缕的瓜葛.可以继续进来看一下, localhost:/usr/src/linux-2.6.10/drivers/usb # cd storage/ 咋一看,着实吓了一跳,用`wc -l *`这个命令统计一下,12076行,晕死... wc [ -c | -m ] [ -l ] [ -w ] [文件] 注:缺省情况下,wc 命令对 File 参数指定的文件中的行数、字数和字节数进行计数。这个命令将换行符数、字数和字节数写到标准输出并为所有指定的文件保留一个总数。 -c 统计字节数,除非指定 -k 标志。如果指定 -k 标志,wc 命令统计字符数。 但是,也许,生活中总是充满了跌宕起伏. 认真看了一下Makefile和Kconfig之后,心情明显好了许多. 出来混,迟早要还的. 从前在复旦,混了四年,没有学到任何东西,每天就是逃课,上网,玩游戏,睡觉.毕业的时候,身边的人读研的读研,出国的出国,找工作的吧,去麦肯锡的去麦肯锡,去IBM的去IBM.而自己却一无所长,没有任何技能,直到这时候才发现那四年欠了很多债,早知今日,何必当初.幸运的是,我还有一张复旦的文凭,依靠着这张文凭,混进了Intel.然而,工作以后,更是发现当初在校期间没有好好读书其实真是在欠债,当初没学,工作以后还是要学,的确是迟早要还的,逃是逃不掉的. 毕业的时候,人家跟我说Makefile我完全不知,但是一说Make Love我就来劲了.现在想来依然觉得丢人. 基本上,Linux内核中每一个目录下边都有一个Makefile,Makefile和Kconfig就像一个城市的地图,地图带领我们去认识一个城市,而Makefile和Kconfig则可以让我们了解这个目录下面的结构.drivers/usb/storage/目录下边的Makefile内容如下: # EXTRA_CFLAGS := -Idrivers/scsi obj-$(CONFIG_USB_STORAGE) += usb-storage.o usb-storage-obj-$(CONFIG_USB_STORAGE_DEBUG) += debug.o usb-storage-objs := scsiglue.o protocol.o transport.o usb.o \ 关于Kconfig文件,在故事的最后会介绍,此刻暂且不表,Kconfig文件比较长,就不贴出来了.但是通过看Kconfig文件,我们可以知道,除了CONFIG_USB_STORAGE这个编译选项是我们真正需要的以外,别的选项我们都可以不予理睬.比如,关于CONFIG_USB_STORAGE_DATAFAB,Kconfig文件中有这么一段, config USB_STORAGE_DATAFAB 显然,这个选项和我们没有关系,首先这是专门针对Datafab公司的产品的,其次CompactFlash reader是一种flash设备,但这显然不是U盘,因为drivers/usb/storage这个目录里边的代码是针对一类设备的,不是某一种特定的设备,这一类设备就是usb mass storage设备,关于这类设备,有专门的文档进行介绍,有相应的spec,描述这类设备的通信或者物理上电特性上等方面的规范,U盘只是其中的一种,这种设备使用的通信协议被称为Bulk-Only Transport协议.再比如,关于CONFIG_USB_STORAGE_SDDR55这个选项,Kconfig文件中也有对应的一段, config USB_STORAGE_SDDR55 usb-storage-objs := scsiglue.o protocol.o transport.o usb.o \ 不过需要特别注意的是,CONFIG_USB_STORAGE_DEBUG这个编译选项,它不是我们必须的,但是如果真的要自己修改或者调试usb-storage的代码,那么打开这个选项是很有必要的,因为它会负责打印一些调试信息,以后在源代码中我们会看到它的作用. 有一种感动,叫泪流满面,有一种机制,叫模块机制,十月革命一声炮响,给Linux送来了模块机制.显然,这种模块机制给那些Linux的发烧友们带来了方便,因为模块机制意味着人们可以把庞大的Linux内核划分为许许多多个小的模块,对于编写设备驱动程序的那帮家伙来说,从此以后他们可以编写设备驱动程序却不需要把她编译进内核,不用reboot机器,她只是一个模块,当你需要她的时候,你可以把她抱入怀中(insmod),当你不再需要她的时候,你可以把她一脚踢开,甚至,你可以对她咆哮:"滚吧,贱人!"(rmmod).她不能成为你的手足,只能算你的衣服. 也许在现实世界里不会这样,但是在Linux的虚拟世界里,确实可以是如此,time and time again,我问自己,模块是否就像现实生活中的妓女一样呢?Linux内核是嫖客,当他需要这个模块的时候,他就把人家揽入怀中,当他不需要人家的时候,就把别人踢开,而且,模块总是能够逆来顺受,尽管Linux内核会一次次抛弃她,但是每当Linux内核再次需要她的时候,当内核再次执行insmod的时候,模块依然会尽自己的能力去取悦内核,这是否太可悲了些!记得孔子曾经说过,读懂Linux内核代码不难,难得是读懂Linux内核代码背后的哲学!难道这就是传说中的藏在Linux代码背后的哲学!天哪! 抛开这见鬼的哲学吧.让我们从一个伟大的例子去认识模块.这就是传说中的"Hello World!",这个梦幻般的名字我们看过无数次了,每一次她出现在眼前,就意味着我们开始接触一种新的计算机语言了,或者,如此刻,开始描述一个新的故事. 请看下面这段代码,她就是Linux下的一个最简单的模块.当你安装这个模块的时候,她会用她特有的语言向你表白,"Hello,world!",千真万确,她没有说"Honey,I love you!",虽然,她可以这么说,如果你要求她这么说.而后来你卸载了这个模块,你无情抛弃了她,她很伤心,她很绝望,但她没有抱怨,她只是淡淡地说,"Goodbye,cruel world!"(再见,残酷的世界!) ++++++++++++++++++hello.c++++++++++++++++++++ 1 #include <linux/init.h> /* Needed for the macros */ ++++++++++++++++++++++++++++++++++++++++++++++++ 你需要使用module_init()和module_exit(),你可以称她们为函数,不过实际上她们是一些宏(macro),现在你可以不用去知道她们背后的故事,只需要知道,在Linux Kernel 2.6的世界里,你写的任何一个模块都需要使用她们来初始化或退出,或者说注册以及后来的注销.当你用module_init()为一个模块注册了之后,在你使用insmod这个命令去安装的时候,module_init()注册的函数将会被执行,而当你用rmmod这个命令去卸载一个模块的时候,module_exit()注册的函数将会被执行.module_init()被称为驱动程序的初始化入口(driver initialization entry point). 怎么样演示以上代码的运行呢?没错,你需要一个Makefile. +++++++++++++++++++++Makefile+++++++++++++++++++++++++++ 1 # To build modules outside of the kernel tree, we run "make" 在lwn上可以找到这个例子,你可以把以上两个文件放在你的某个目录下,然后执行make,也许你不一定能成功,因为LK 2.6要求你编译模块之前,必须先在内核源代码目录下执行make,换言之,你必须先配置过内核,执行过make,然后才能make你自己的模块.原因我就不细说了,你按着她要求的这么去做就行了.在内核顶层目录make过之后,你就可以在你当前放置Makefile的目录下执行make了.Ok,make之后你就应该看到一个叫做hello.ko的文件生成了,恭喜你,这就是你将要测试的模块. 执行命令, #insmod hello.ko 同时在另一个窗口,用命令tail -f /var/log/messages察看日志文件,你会看到 Hello world被打印了出来. 再执行命令, #rmmod hello.ko 此时,在另一窗口你会看到Goodbye,cruel world!被打印了出来. 到这里,我该恭喜你,因为你已经能够编写Linux内核模块了.这种感觉很美妙,不是吗?你可以嘲笑秦皇汉武略输文采唐宗宋祖稍逊风骚,还可以嘲笑一代天骄成吉思汗只识弯弓射大雕了. 是的,Twins姐姐(s)告诉我们,只要我喜欢,还有什么不可以. 日后我们会看到,2.6内核中,每个模块都是以module_init开始,以module_exit结束.大多数来说没有必要知道这是为什么,记住就可以了,相信每一个对Linux有一点常识的人都会知道这一点的,对大多数人来说,这就像是1+1为什么等于2一样,就像是两点之间最短的是直线,不需要证明,如果一定要证明两点之间直线最短,可以扔一块骨头在B点,让一条狗从A点出发,你会发现狗走的是直线,是的,狗都知道,你还能不知道吗?
既然知道了怎么编写一个模块,那么编写设备驱动程序自然也就不难了.我相信,每一个会写模块的人都不会觉得写设备驱动有困难.对自己行不行不确定的话,可以去问一下葛优,他准说:"(神州行),我看行." 真的,我没说假话.写驱动不是什么难事,你完全可以很自信的说,你已经可以写Device Driver了.对,没错,飘柔,就这么自信. 前面说了每一个模块都是以module_init开始,以module_exit结束,那么我们就来看一下U盘的驱动的这个模块.在茫茫人海中,我们很容易找到这个文件:drivers/usb/storage/usb.c,在这个文件中又不难发现下面这段: /*********************************************************************** 其实,module_init/module_exit只是一个宏,通常写模块的人为了彰显自己的个性,会给自己的初始化函数和注销函数另外起个名字,比如这里module_init(usb_stor_init)以及module_exit(usb_stor_exit)实际上就是告诉这个世界,真正的函数是usb_stor_init和usb_stor_exit.这种伎俩在Linux内核代码中屡见不鲜.见多了也就不必大惊小怪了,天要下雨娘要嫁人,随她去吧.我们下面当然就从usb_stor_init正式开始我们的探索之旅.
看代码之前,我曾经认真的思考过这么一个问题,我需要关注的仅仅是drivers/usb/storage/目录下面那相关的3000多行代码吗?就是这样几个文件就能让一个个不同的U盘在Linux下面工作起来吗? 像一开始那样把这个目录比作一个小城的话,也许,城里的月光很漂亮,她能够把人的梦照亮,能够温暖人的心房.但我们真的就能厮守在这个城里,一生一世吗? 很不幸,问题远不是这样简单.外面的世界很精彩,作为U盘,她需要与usb core打交道,需要与scsi core打交道,需要与内存管理单元打交道,还有内核中许许多多其它模块打交道.外面的世界很大,远比我们想象的大. 什么是usb core?她负责实现一些核心的功能,为别的设备驱动程序提供服务,比如申请内存,比如实现一些所有的设备都会需要的公共的函数,事实上,在usb的世界里,一个普通的设备要正常的工作,除了要有设备本身以外,还需要有一个叫做控制器的冬冬,老外把它叫做host controller,和这个控制器相连接在一起的有另一个咚咚,她叫root hub,hub我们应该不会陌生,在大学里,有的宿舍里网口有限,但是我们这一代人上大学基本上是每人一台电脑,所以网口不够,于是有人会使用hub,让多个人共用一个网口,这是以太网上的hub,而usb的世界里同样有hub,其实原理是一样的,任何支持usb的电脑不会说只允许你只能一个时刻使用一个usb设备,比如你插入了u盘,你同样还可以插入usb键盘,还可以再插一个usb鼠标,因为你会发现你的电脑里并不只是一个usb接口.这些口实际上就是所谓的hub口.而现实中经常是让一个usb控制器和一个hub绑定在一起,专业一点说叫集成,而这个hub也被称作root hub,换言之,和usb控制器绑定在一起的hub就是系统中最根本的hub,其它的hub可以连接到她这里,然后可以延伸出去,外接别的设备,当然也可以不用别的hub,让usb设备直接接到root hub上.hub干嘛用的我们知道了,那么usb host controller本身是干什么用的呢?controller,控制器,顾名思义,用于控制,控制什么,控制所有的usb设备的通信.通常计算机的cpu并不是直接和usb设备打交道,而是和控制器打交道,他要对设备做什么,他会告诉控制器,而不是直接把指令发给设备,然后控制器再去负责处理这件事情,他会去指挥设备执行命令,而cpu就不用管剩下的事情,他还是该干嘛干嘛去,控制器替他去完成剩下的事情,事情办完了再通知cpu.否则让cpu去盯着每一个设备做每一件事情,那是不现实的,那就好比让一个学院的院长去盯着我们每一个本科生上课,去管理我们的出勤,只能说,不现实.所以我们就被分成了几个系,通常院长有什么指示直接跟各系领导说就可以了,如果他要和三个系主任说事情,他即使不把三个人都召集起来开个会,也可以给三个人各打一个电话,打完电话他就忙他自己的事情去了,比如去和他带的女硕士风花雪月.而三个系主任就会去安排下面的人去执行具体的任务,完了之后他们就会像院长汇报. 所以,Linux内核开发者们,专门写了一些代码,并美其名曰usb core.时代总在发展,当年胖杨贵妃照样迷死唐明皇,而如今人们欣赏的则是林志玲这样的魔鬼身材.同样,早期的Linux内核,其结构并不是如今天这般有层次感,远不像今天这般错落有致,那时候drivers/usb/这个目录下边放了很多很多文件,usb core与其他各种设备的驱动程序的代码都堆砌在这里,后来,怎奈世间万千的变幻,总爱把有情的人分两端.于是在drivers/usb/目录下面出来了一个core目录,就专门放一些核心的代码,比如初始化整个usb系统,初始化root hub,初始化host controller的代码,再后来甚至把host controller相关的代码也单独建了一个目录,叫host目录,这是因为usb host controller随着时代的发展,也开始有了好几种,不再像刚开始那样只有一种,所以呢,设计者们把一些host controller公共的代码仍然留在core目录下,而一些各host controller单独的代码则移到host目录下面让负责各种host controller的人去维护,常见的host controller有三种,分别叫做EHCI,UHCI,OHCI,所以这样,出来了三个概念,usb core,usb host,usb device,即原本是一家人,却被活生生的分成了两岸三地...的确,现实总是很无奈,然而,心若知道灵犀的方向,哪怕不能够朝夕相伴?没错,usb通信的灵魂就是usb协议. usb协议将是所有usb设备和usb主机所必须遵循的游戏规则.这种规则也很自然的体现在了代码中.于是,我们需要了解的不仅仅是drivers/usb/storage/目录下面的冬冬,还得去了解那外面的世界,虽然,只需要了解一点点. 还是回到那个初始化函数吧,usb_stor_init,看了它的代码每一个人的心中都有一种莫名的兴奋,因为它太短了,就那么几行,除了两个printk语句以外,就是一个函数的调用,usb_register. printk不用我说,每一个有志青年都该知道,就算没见过printk也该见过printf吧,否则的话,你扪心自问,你对得起谭浩强大哥吗?在谭浩强大哥的带领下我们学会了用#include<stdio.h>->main()->printf()来打印hello,world!从而向全世界展示了我们懂C语言.而stdio.h就是一个C库,printf是一个函数,来自函数库,可是内核中没有标准C库,所以开发者们自己准备了一些函数,专门用于内核代码中,所以就出来了一个printk,printk的"k"就是kernel,内核.所以我们只要把它当作printf的兄弟即可,如果感兴趣,可以去研究一下printk的特点,她和printf多少有些不同,但基本思想是一样的.所以我们就不多讲了,当然驱动程序中所有的printk语句对U盘的工作都没有什么用,她无非是打出来给我们看的,或者说打印给用户看,或者呢,打印给开发者看,特别是开发者要调试程序的时候,就会很有用. 于是我们更开心了,不用看printk的话,那就只有一个函数调用了,usb_register.这个函数是干嘛的?首先这个函数正是来自usb core.凡是usb设备驱动,都要调用这个函数来向usb core注册,从而让usb core知道有这么一个设备.这就像政府规定,一对夫妻结婚要到相关部门那里去登记是一样的,我们无需知道政府是如何管理的,只需要知道去政府那里登记即可. 这样,insmod的时候,usb_stor_init这个函数会被调用,初始化就算完成了.于是设备就开始工作了...而当我们rmmod的时候,usb_stor_exit这个函数会被调用,我们发现,这个函数也很短,我们能看出来,US_DEBUG也就是打印一些咚咚,因此,这里实际上也就是调用了一个函数usb_deregister(),她和usb_register()是一对,完成了注销的工作,从此设备就从usb core中消失了.于是我们惊人的发现,编写设备驱动竟是如此的简单,驱动程序真的就这么结束了?... 这一切,不禁让人产生了一种幻觉,让人分不清故事从哪里开始,又从哪里结束,一切都太短暂了.仿佛开始在结束的时候开始,而结束却在开始的时候就早已结束. 真的吗? 答案是否定的.孔子已经教育过我们,不光要看懂代码,更要理解代码背后的哲学. 所以我们在继续之前,先来看看这里到底有什么哲学.而这,就是伟大的Linux Kernel 2.6中的统一的设备模型. 我们并无意去详细介绍2.6中的设备模型,但是不懂设备模型又怎能说自己懂设备驱动呢?读代码的人,写代码的人,都要知道,什么是设备驱动?什么又是设备?设备和驱动之间究竟是什么关系?设备如何与计算机主机联系起来?我相信在中关村买盗版光盘的哥们儿也能回答这个问题.计算机世界里,设备有很多种类,比如PCI设备,比如ISA设备,再比如SCSI设备,再比如我们这里的USB设备.为设备联姻的是总线,是他把设备连入了计算机主机.但是与其说设备是嫁给了计算机主机,倒不如说设备是嫁给了设备驱动程序.很显然,在计算机世界里,无论风里雨里,陪伴着设备的正是驱动程序. 唯一的遗憾是,计算机中的设备和驱动程序的关系却并非如可乐和拉环的关系那样,一对一.然而世上又有多少事情总能如人愿呢. Linux设备模型中三个很重要的概念就是总线,设备,驱动.即bus,device,driver,而实际上内核中也定义了这么一些数据结构,他们是struct bus_type,struct device,struct device_driver,这三个重要的数据结构都来自一个地方,include/linux/device.h.我们知道总线有很多种,pci总线,scsi总线,usb总线,所以我们会看到Linux内核代码中出现pci_bus_type,scsi_bus_type,usb_bus_type,他们都是struct bus_type类型的变量.而struct bus_type结构中两个非常重要的成员就是struct kset drivers和struct kset devices.kset和另一个叫做kobject正是Linux Kernel 2.6中设备模型的基本元素,但此处我们却不愿多讲,因为暂时不用去认识他们.我们的生命中会遇见许许多多的人和事,但更多的人和事与我们只是擦肩而过,只是我们生命中的过客而已.在我们人生的电影中,他们也许只有一个镜头,甚至那一个镜头后来也被剪辑掉了.这里我们只需要知道,drivers和devices的存在,让struct bus_type与两个链表联系了起来,一个是devices的链表,一个是drivers的链表,也就是说,知道一条总线所对应的数据结构,就可以找到这条总线所关联的设备有哪些,又有哪些支持这类设备的驱动程序. 而要实现这些,就要求每次出现一个设备就要向总线汇报,或者说注册,每次出现一个驱动,也要向总线汇报,或者说注册.比如系统初始化的时候,会扫描连接了哪些设备,并为每一个设备建立起一个struct device的变量,每一次有一个驱动程序,就要准备一个struct device_driver结构的变量.把这些变量统统加入相应的链表,device插入devices链表,driver插入drivers链表. 这样通过总线就能找到每一个设备,每一个驱动. 然而,假如计算机里只有设备却没有对应的驱动,那么设备无法工作.反过来,倘若只有驱动却没有设备,驱动也起不了任何作用.在他们遇见彼此之前,双方都如同路埂的野草,一个飘啊飘,一个摇啊摇,谁也不知道未来在哪里,只能在生命的风里飘摇.于是总线上的两张表里就慢慢的就挂上了那许多孤单的灵魂.devices开始多了,drivers开始多了,他们像是两个来自世界,devices们彼此取暖,drivers们一起狂欢,但他们有一点是相同的,都只是在等待属于自己的那个另一半. 看代码的我,一直好奇的想知道,他们是否和我们现实中一样,有些人注定是等别人,而有些人是注定被人等的. struct bus_type中为devices和drivers准备了两个链表,而代表device的结构体struct device中又有两个成员,struct bus_type *bus和struct device_driver *driver,同样,代表driver的结构体struct device_driver同样有两个成员,struct bus_type *bus和struct list_head devices,struct device和struct device_driver的定义和struct bus_type一样,在include/linux/device.h中.凭一种男人的直觉,可以知晓,struct device中的bus记录的是这个设备连在哪条总线上,driver记录的是这个设备用的是哪个驱动,反过来,struct device_driver中的bus代表的也是这个驱动属于哪条总线,devices记录的是这个驱动支持的那些设备,没错,是devices(复数),而不是device(单数),因为一个驱动程序可以支持一个或多个设备,反过来一个设备则只会绑定给一个驱动程序.
那么drivers链表呢?这个就不用bus方面主动了,而该由每一个driver本身去bus上面登记,或者说挂牌.具体到usb系统,每一个usb设备的驱动程序都会有一个struct usb_driver结构体,其代码如下,来自include/linux/usb.h 485 /* -------------------------------------------------------------------------- */
|
|