Linux那些事儿之我是USB(第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-31 04:38:35

点击下载

作者:任桥伟,肖季东,肖林甫

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Linux那些事儿之我是USB(第2版)

Linux那些事儿之我是USB(第2版)试读:

前言

从写Linux那些事儿系列内容开始,到如今已有四年多了,而从整理出版第1版到现在也已经一载有余了。期间不断有认识或不认识的朋友问我,怎么会想起写这么多如此可爱的文字,我的回答都是:娱乐自己,娱乐大家而已!

或许,大家早已经默认技术本是一个沉重或者枯燥的话题,我们无法用一种娱乐的心态去看待它,甚至说很多人早已丧失了从中获取乐趣的能力。但是,一切本不该如此的,对于不管什么原因踏入这个行业的我们,愿意或不愿意,技术都已经是我们生命不可分割的一部分。

既如此,又何不放轻松些,把它当成朋友,用我们自己的方式去与它交流,而不是仅仅把它当成一堆堆死气沉沉的代码,亦或一些枯燥的名词。而针对这本书的内容,我要说的就是:把内核当朋友。笑来老师有本书,叫《把时间当做朋友》,告诉我们只有把时间当做朋友,才能更好地利用自己的时间做些有益的事情。眼睛一闭一睁,一天就过去了;眼睛一闭不睁,一辈子就过去了。只有善待时间,时间才能善待我们。同样,我们只有把内核当朋友,当成一个有生命的实体,把它放在对等的地位上,我们才能够更好地认识和理解到它的精髓。

具体到这本书,您可以把它当成一本内核源码分析的书,甚至仅仅当成内核USB实现源码分析的书,但是我更希望您把它当成展现如何学习Linux内核,展现如何与内核进行平等交流的一个范例,起码它体现了我们应该用什么样的态度去对待Linux内核源码。也就是说,分析内核源码,态度决定一切。我们很多人或许有这样的困惑,也分析浏览了很多内核的源码,可总是觉得分析、浏览后,脑子里还是空空的,并没有感觉到多大的收获。这个时候我们或许可以去看看是不是自己在分析代码时的态度出现了问题。我们在分析内核源码时,只有遵循严谨的态度,而不是抱着走马观花、得过且过的态度,最终才会有很大的收获。

然后还有一句曾小范围流传的话:技术水平的高低不是决定于C,或者C++等用得有多么熟练,而是决定于你掌握的资源有多少。所以,我们还要以内核源码为中心,坚持学习资源建设。在我们学习内核的过程中,内核源码本身就是最好的参考资料,其他任何经典或非经典的书最多只是起辅助作用,不能也不应该取代内核代码在我们学习过程中的主导地位。但是这些辅助的作用也是不可忽视的,我们需要以内核源码为中心,坚持各种学习资源的长期建设不动摇。

再次感谢孙学瑛编辑,没有她的努力,这本书的内容将会一直偏居网络一隅,将不可能被出版,从而去帮助更多需要的人。第1篇Linux那些事儿之我是USB Core1.引子

老夫子们痛心疾首地总结说,现代青年的写照——自负太高,反对太多,商议太久,行动太迟,后悔太早。上天戏弄,我不幸地混进了“80后”的革命队伍里,成了一名现代青年,前有老夫子的忧心忡忡,后有“90后”的轻蔑嘲弄,终日在“迷失”与“老土”这样的两极词汇里徘徊。

这里我就讲一讲USB,让他们看一看“80后”还知道什么叫USB。

还是要说在前面,在这里耗费青春写USB,并不是因为喜欢它,相反,对它毫无感觉可言,虽然每天都必须和它相依为伴,不离不弃,不过那可是丝毫没有办法的事情,非我所愿。是不是特说到心坎儿里去了?不过您别多想,咱这里只谈USB。

一句话总结:哥写的不是USB,是寂寞。2.它从哪里来“你从哪里来,我的朋友,好像一只蝴蝶,飞进我的窗口。”

在嘹亮的歌声中,USB好像一只蝴蝶飞进了千家万户。它从哪里来,它从Intel来。Intel不养蝴蝶,而是做CPU,它只是在蝴蝶的翅膀上烙上“Intel inside”,蝴蝶让咱们的同胞去养了,然后带着Intel飞进了千万家。

不过,与PCI、AGP属于Intel单独提出的硬件标准不同,Compaq、IBM、Microsoft等也一起参与了USB这个游戏。他们一起于1994年11月提出了USB,并于1995年11月制定了0.9版本,1996年制定了1.0版本。不过USB并没有因为有这些大佬的支持立即迎来它的春天,只怪它诞生在了冬季,生不逢时啊!

因为缺乏操作平台的良好支持和大量支持它的产品,这些标准都成了空谈。1998年,USB 1.1的出现,忽如一夜春风来,它就像春天里的一朵油菜花,终于涂上了浓重的一抹黄色。

为什么要开发USB?

在USB出现以前,电脑的接口处于“春秋战国时代”,串口、并口等多方割据,键盘、鼠标、MODEM、打印机、扫描仪等都要连接在这些不同种类的接口上,一个接口只能连接一个设备。不过咱们的电脑不可能有那么多接口,所以扩展能力不足,而且速度也确实很有限。还有关键的一点是,热插拔对它们来说也是比较危险的操作。

USB正是为了解决速度、扩展能力、易用性等问题应景而生的。3.PK

USB的一生也充满了“PK”,不过USB还不够老,说一生还太早了,发哥说得好:“我才刚上路呢!”

USB最初的设计目标就是替代串行、并行等各种低速总线,以一种单一类型的总线连接各种不同的设备。它现在几乎可以支持所有连接到PC上的设备,1999年提出的USB 2.0理论上可以达到480 MB/s的速度,2008年公布的USB 3.0标准更是提供了十倍于USB 2.0的传输速度。

因此,USB与串口、并口等的这场“PK”从一开始就是不平等的,这样的开始也注定了以什么样的结果结束,只能说命运选择了USB。我们很多人都说命运掌握在自己手里,但是从USB充满“PK”的一生中可以知道,只有变得比别人更强,命运才能掌握在自己手里。

有了USB在这场PK中的大获全胜,才有了USB键盘、USB鼠标、USB打印机、USB摄像头、USB扫描仪、USB音箱等。至于将来,“PK自己的,让别人去说吧!”USB如是说。4.漫漫辛酸路

USB的一生充满了“PK”,并在“PK”中发展,从USB 1.0、USB 1.1、USB 2.0到USB 3.0,漫漫辛酸路,一把辛酸泪。

USB 2.0的高速模式(High-Speed)最高已经达到了480 MB/s,也就是说,以这个速度,你将自己从网上下载的短片备份到自己的移动硬盘上的时间长约为一秒钟。而USB 3.0的Super-Speed模式比这个速度提高了几乎10倍,达到了4.8GB/s。

USB走过的这段辛酸路,对咱们来说,最直观的结果也就是传输速度提高了,过程很艰辛,结果很简单。

USB的各个版本都是兼容的。每个USB 2.0控制器带有3个芯片,根据设备的识别方式,将信号发送到正确的控制芯片。我们可以将USB 1.1设备连接到USB 2.0的控制器上使用,不过它只能达到USB 1.1的速度。同时也可以将USB 2.0的设备连接到USB 1.1的控制器上,不过不能指望它能以USB 2.0的速度运行。

显然,Linux对USB 1.1和USB 2.0都是支持的,并抢在Windows前,在2.6.31内核中率先对USB 3.0进行了支持。5.我型我秀

USB既然能一路“PK”走过来,也算是一个挺能“秀”的角色了,不然也不会有那么多的拥护者。

USB为所有的USB外设都提供了单一、标准的连接类型,这就简化了外设的设计,也让我们不用再去想哪个设备对应哪个插槽的问题,就像种萝卜,一个萝卜一个坑,但是哪个萝卜种到哪个坑里是不用我们关心的。

USB支持热插拔,而其他的比如SCSI设备等只有在关掉主机的前提下才能增加或移走外围设备。所以说,USB的一生不仅仅是“PK”的一生,也是丰富多彩的一生,可以不用关机就能更换不同种类的外设。

USB在设备供电方面提供了灵活性。USB设备可以通过USB电缆供电,不然移动硬盘、IPod等常备外设也用不了了。相对应,有的USB设备也可以使用普通的电源供电。

USB能够支持从每秒几十千字节到几十兆字节的传输速率,来适应不同种类的外设。它可以支持多个设备同时操作,也支持多功能的设备。多功能的设备当然指的就是一个设备同时有多个功能,比如USB扬声器。这通过在一个设备中包含多个接口来支持,一个接口支持一个功能。

USB可以支持多达127个设备。

USB可以保证固定的带宽,这个对视频/音频设备是利好。6.我是一棵树“我是一棵树,静静地站在田野里,风儿吹过,我不知它的去向,人儿走过,我不知谁会为我停留。”

如图1.6.1所示,USB子系统的拓扑也是一棵树,它并不以总线的方式来部署。图1.6.1 USB子系统的树形结构

我曾经指着路边一棵奇形怪状的树问朋友:“这是什么树?”朋友的回答让我很晕:“大树。”那图1.6.1指的是什么树?自然也是大树了,不过却是USB的大树。这棵大树主要包括USB连接、USB Host Controller(USB主机控制器)和USB设备三个部分。而USB设备还包括了Hub和功能设备(也就是图1.6.1中的Func)。

什么是USB主机控制器?控制器,顾名思义,用于控制。控制什么?控制所有的USB设备的通信。通常,计算机的CPU并不是直接和USB设备打交道,而是和控制器打交道。它要对设备做什么,它会告诉控制器,而不是直接把指令发给设备。然后控制器再去负责处理这件事情,它会去指挥设备执行命令,而CPU就不用管剩下的事情。控制器替它去完成剩下的事情,事情办完了再通知CPU。否则,让CPU去盯着每一个设备做每一件事情,那是不现实的。

那么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上。

而USB连接指的就是连接USB设备和主机(或Hub)的四线电缆。电缆中包括VBUS(电源线)、GND(地线)和两根信号线。USB系统就是通过VBUS和GND向USB设备提供电源的。主机对连接的USB设备提供电源供其使用,而每个USB设备也能够有自己的电源,如图1.6.2所示。

现在,如图1.6.3所示的USB大树里只有Compound Device还没有说。那么,Compound Device又是什么样的设备?其实,在USB的世界里,不仅仅有Compound Device,还有Composite Device,简单的中文名字已经无法形象地表达它们的区别。正如图1.6.3所示,Compound Device是将Hub和连在Hub上的设备封装在一起所组成的设备。而Composite Device则是包含彼此独立的多个接口的设备。从主机的角度看,一个Compound Device和单独的一个Hub,然后连接了多个USB设备是一样的,它里面包含的Hub和各个设备都会有自己独立的地址,而一个Composite Device里不管有多少接口,它都只有一个地址。图1.6.2 USB四线电缆

USB大树要想茁壮成长,离不开USB协议。USB总线是一种轮询式总线。协议规定所有的数据传输都必须由主机发起,由主机控制器初始化所有的数据传输,各种设备紧紧围绕在主机周围。

USB通信最基本的形式是通过USB设备中一个叫Endpoint(端点)的东西,而主机和端点之间的数据传输是通过Pipe(管道)。

端点就是通信的发送点或者接收点,要发送数据,只需把数据发送到正确的端点就可以了。而管道,实际上只是为了让我们能够找到端点,就相当于我们日常说的邮编地址。图1.6.3 Compound Device

比如一个国家,为了通信,我们必须给各个地方取名,然后给各条大大小小的路取名字。严格来说,管道的另一端应该是USB主机,USB协议也是这么说的,协议说管道代表着在主机和设备上的端点之间移动数据的能力。

端点不但是有方向的,而且这个方向还是确定的,要么是in,要么是out,没有既是in又是out的,都是生来就注定的。

有没有特殊的端点呢?看你怎么去理解0号端点了,协议规定了,所有的USB设备必须具有端点0,它可以作为in端点,也可以作为out端点。USB系统软件利用它来实现默认的控制管道,从而控制设备。

端点也是限量供应的,不是想要多少就有多少,除了端点0,低速设备最多只能拥有两个端点,高速设备也最多只能拥有15个in端点和15个out端点。这些端点在设备内部都有唯一的端点号,这个端点号是在设备设计时就已经指定的。

为什么端点0就特殊呢?这还是有内在原因的。管道的通信方式其实有两种:一种是stream的,一种是message的。message管道要求从它那儿过的数据必须具有一定的格式,不是随便传的,因为它主要就是用于主机向设备请求信息的,必须得让设备明白请求的是什么。而stream管道就没这么苛刻,随和多了,对数据没有特殊的要求。协议中规定,message管道必须对应两个相同号码的端点:一个用来in,一个用来out,默认管道就是message管道。当然,与默认管道对应的端点0就必须是两个具有同样端点号0的端点。

USB端点有四种类型,分别对应了四种不同的数据传输方式。它们是控制传输(Control Transfers)、中断传输(Interrupt Data Transfers)、批量传输(Bulk Data Transfers)和等时传输(Isochronous Data Transfers)。

·控制传输用来控制对USB设备不同部分的访问,通常用于配置设备,获取设备信息,发送命令到设备,或者获取设备的状态报告。总之,就是用来传送控制信息的,每个USB设备都会有一个名为“端点0”的控制端点,内核中的USB Core使用它在设备插入时进行设备的配置。

·中断传输用来以一个固定的速率传送少量的数据,USB键盘和USB鼠标使用的就是这种方式,USB的触摸屏也是使用这种方式,传输的数据包含了坐标信息。

·批量传输用来传输大量的数据,确保没有数据丢失,但不保证在特定的时间内完成。U盘使用的就是批量传输,用它备份数据时需要确保数据不能丢,而且也不能指望它能在一个固定的比较快的时间内复制完。

·等时传输同样用来传输大量的数据,但并不保证数据是否到达,以稳定的速率发送和接收实时的信息,对传送延迟非常敏感,显然是用于音频和视频一类的设备。这类设备期望能够有一个比较稳定的数据流,比如在用QQ视频聊天时,肯定希望每分钟传输的图像/声音速率是比较稳定的,不能说这一分钟前对方看到你在向她或向你深情表白,可是下一分钟却看见画面停滞在那里,只能看到你在那里一动不动,这不是浪费感情吗?

如图1.6.1所示的树形结构描述的是实实在在的物理拓扑,对于内核中的实现来说,没有这么复杂,所有的Hub和设备都被看做是一个个的逻辑设备(Logical Device),如图1.6.4所示,好像它们本来就直接连接在Root Hub上一样。图1.6.4 USB逻辑拓扑结构

如图1.6.5所示,一个USB逻辑设备就是一系列端点的集合,它与主机之间的通信发生在主机上的一个缓冲区和设备上的一个端点之间,通过管道来传输数据。也就是说,管道的一端是主机上的一个缓冲区,一端是设备上的端点。

那么图1.6.5中的接口又是指什么?简单地说,USB端点被捆绑为接口(Interface),一个接口代表一个基本功能。有的设备具有多个接口,像USB扬声器就包括一个键盘接口和一个音频流接口。在内核中,一个接口要对应一个驱动程序,USB扬声器在Linux里就需要两个不同的驱动程序。到目前为止,一个设备可以包括多个接口,一个接口可以具有多个端点,当然以后我们会发现并不仅仅止于此。图1.6.5 USB数据通信7.我是谁

我是谁?USB也一遍一遍地问着自己,当然它不会真的是一棵树,它也不会是太阳,Linux里没有太阳,真要有的话,也只能是Linus。USB子系统只是Linux庞大家族里的一个小部落,主机控制器是它们的族长,族里的每个USB设备都需要被系统识别,被我们识别,而sysfs就是它们对外的窗口,我们可以从sysfs里了解认识每一个USB设备。以一个仅包含一个USB接口的USB鼠标为例,如图1.7.1所示,就是该设备对应的sysfs目录树。图1.7.1 USB鼠标的sysfs目录树

其中:

表示鼠标。

下层目录:

表示鼠标的USB接口。sysfs里USB设备都是类似的表示,设备的目录下包括表示设备接口的目录。目录里的各个文件表示设备或接口的描述,大都对应了设备描述符、接口描述符等相应值,可以通过这些值获得您感兴趣的信息。什么是设备描述符和接口描述符?我们这里要暂时忽略它的存在,先关心关心USB设备在sysfs里是如何命名的,弄清它是谁,也就是说,弄清上面路径的含义。

USB系统中的第一个USB设备是Root Hub,前面已经说了它是和主机控制器绑定在一起的。这个Root Hub通常包含在PCI设备中,是连接PCI总线和USB总线的bridge,控制着连接到其上的整个USB总线。所有的Root Hub,内核的USB Core都分配有独特的编号,在上面的例子里就是USB 2。

USB总线上的每个设备都以Root Hub的编号作为其名字的第一个号码。这个号码后跟着一个“-”字符,以及设备所插入的端口号。因此,上面例子中的USB鼠标的设备名就是2-1。因为该USB鼠标具有一个接口,导致了另外一个USB设备被添加到sysfs路径中。因为物理USB设备和单独的USB接口在sysfs中都将表示为单独的设备。USB接口的命名是设备名直到该接口,上面就是2-1后面跟一个“:”和USB配置(Configuration)的编号,然后是一个“.”和该接口的编号。因此,上面的鼠标USB接口就是2-1:1.0,表示使用的是第一个配置,接口编号为0。

sysfs并没有展示USB设备的所有部分,设备可能包含的可选配置都没有显示,不过这些可以通过usbfs找到,该文件系统被挂在/proc/bus/usb目录中,从/proc/bus/usb/device文件可以知道系统中存在的所有USB设备的可选配置。

这里既然提到了USB设备的配置,还是先简要说说。一个设备可以有一种或者几种配置,这能理解吧?没见过具体的USB设备?那么手机见过吧,每部手机都会有多种配置,或者说“设定”。比如,笔者的这款Nokia 6300手机,手机语言可以设定为English、繁体中文、简体中文,一旦选择了其中一种,那么手机里显示的所有信息都是该种语言/字体。再举一个最简单的例子,手机的操作模式也有好几种,标准、无声、会议等。如果我设为“会议”模式,那么就是只振动不发声:要是设为“无声”模式,那么就什么动静也不会有。USB设备的配置也是如此,不同的USB设备当然有不同的配置了,或者说需要配置哪些东西也会不一样。8.好戏开始了

首先要去drivers/usb目录下走一走、看一看。

ls命令的结果就是上面的10个目录和4个文件。usb-skeleton.c是一个简单的USB driver的框架。那么首先应该关注什么?那就是Kconfig、Makefile、README。

README里有关于这个目录下内容的一般性描述。再说了,面对“读我吧!读我吧!”这么热情奔放的呼唤,善良的我们是不可能无动于衷的,所以先来看一看README里面都有些什么内容。

drivers/usb/README文件描述了前面使用ls命令列出的那10个文件夹的用途。那么什么是USB Core?Linux内核开发人员们专门写了一些代码,负责实现一些核心的功能,为别的设备驱动程序提供服务,比如申请内存,实现一些所有的设备都会需要的公共函数,并美其名曰为“USB Core”。

时代总在发展,早期的Linux内核,其结构并不是如今天这般有层次感,远不像今天这般错落有致,那时候drivers/usb/目录下放了很多文件,USB Core与其他各种设备驱动程序代码都堆砌在这里,后来,在drivers/usb/目录下面出来了一个core目录,就专门放一些核心的代码,比如初始化整个USB系统,初始化Root Hub,初始化主机控制器的代码,再后来甚至把主机控制器相关的代码也单独建了一个目录,叫host目录。这是因为USB主机控制器随着时代的发展,也开始有了好几种,不再像刚开始那样只有一种。所以,设计者们把一些主机控制器公共的代码仍然留在core目录下,而一些各主机控制器单独的代码则移到host目录下面负责各种主机控制器的人去维护。

那么USB gadget呢?gadget说白了就是配件的意思,主要就是一些内部运行Linux的嵌入式设备,比如PDA,设备本身有USB设备控制器(USB Device Controller),可以将PC,也就是我们的主机作为master端,将这样的设备作为slave端和主机通过USB进行通信。从主机的观点来看,主机系统的USB驱动程序控制插入其中的USB设备,而USB gadget的驱动程序控制外围设备作为一个USB设备和主机通信。比如,我们的嵌入式主板上支持SD卡,如果我们希望将主板通过USB连接到PC之后,这个SD卡被模拟成U盘,那么就要通过USB gadget架构的驱动。

gadget目录下大概可以分为两个模块:一个是udc驱动,这个驱动是针对具体CPU平台的,如果找不到现成的,就要自己实现;另外一个就是gadget驱动,主要有file_storage、ether、serial等。另外还提供了USB gadget API,即USB设备控制器硬件和gadget驱动通信的接口。PC及服务器只有USB主机控制器硬件,它们并不能作为USB gadget存在,而对于嵌入式设备,USB设备控制器常被集成到处理器中,设备的各种功能,如U盘、网卡等常依赖这种USB设备控制器来与主机连接,并且设备的各种功能之间可以切换,比如可以选择作为U盘或网卡等。

剩下的几个目录分门别类地放了各种USB设备的驱动,U盘的驱动在storage目录下,触摸屏和USB键盘鼠标的驱动在input目录下等。另外,在USB协议中,除了通用的软硬件电气接口规范等,还包含各种各样的Class协议,用来为不同的功能定义各自的标准接口和具体的总线上的数据交互格式和内容。这些Class协议的数量非常多,比如最常见的支持U盘功能的Mass Storage Class,以及通用的数据交换协议CDC Class。此外,还包括Audio Class、Print Class等。理论上讲,即使没有这些Class,通过专用驱动也能够实现各种各样的应用功能。但是,正是Mass Storage Class的使用,使得各个厂商生产的U盘都能通过操作系统自带的统一驱动程序来使用,对U盘的普及起了极大的推动作用,制定其他的Class也是同样的目的。

我们响应了README的呼唤,它给予了我们想要的,通过它,我们了解了USB目录里的那些文件夹都有着什么样的角色。到现在为止,就只剩下Kconfig和Makefile两个文件了,它们又扮演着什么样的角色?就好像我吃东西时总是喜欢把好吃的留在最后享受一样,我也习惯于将重要的内容留在最后去描述。对于一个希望能够在Linux内核的汪洋代码里看到一丝曙光的人来说,将它们放在多么重要的地位都不过分。我们在去香港通过海关时,总会有免费的地图,有了它们,我们才不至于像无头苍蝇般迷惘地行走在陌生的街道上。即使在出去旅游时,一般也总是会先找一份地图,当然了,这时就是要去买了,拿是拿不到的。不同的地方有不同的特色,别人的特色是服务,咱们的特色是索取。Kconfig、Makefile就是Linux kernel迷宫里的地图,我们每次浏览kernel寻找属于自己的那一段代码时,都应该先看一看目录下的这两个文件。

不过,这里很明显,要想了解USB协议在内核中的实现,USB Core就是我们需要关注的对象。9.不一样的Core

我们来看Core目录。关于USB,有一个很重要的模块,它的名字耐人寻味——usbcore。如果你的电脑安装了Linux操作系统,那么你用lsmod命令查看,有一个模块叫做usbcore。当然,你要是玩嵌入式系统的高手,那么也许你的电脑里没有USB模块。不过听说如今玩嵌入式的人也喜欢玩USB,因为USB设备很符合嵌入式的口味。查看lsmod命令的输出吧。

找到usbcore那一行了吗?它就是这里要说的USB子系统的核心,如果要在Linux里使用USB,这个模块是必不可少的,另外,你应该会在usbcore的最后一行看到ehci_hcd或uhci_hcd,它们就是前面说的USB主机控制器的驱动模块,你的USB设备要工作,合适的USB主机控制器模块也是必不可少的。

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

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

在写USB驱动时,只能调用Core的接口,Core会将咱们的请求发送给相应的HCD,Core为咱们完成了大部分的工作,Linux的哲学是不是和咱们生活中不太一样?

到drivers/usb/core目录里,使用ls命令看一看。

再使用wc命令统计,将近两万行的代码,Core不愧是Core,为大家默默地做这么多事,我们要用感恩的心去深刻理解你的内心,回报你的付出。

不过这么多文件中不一定都是我们所要关注的,先拿咱们的地图来看一看接下来该怎么走。先看一看Kconfig文件。

这是USB的调试tag,如果你在写USB设备驱动的话,最好还是打开它吧,不过这里它就不是我们关注的重点了。

这个选项是关于usbfs文件系统的。usbfs文件系统挂载在/proc/bus/usb上(mount-t usbfs none/proc/bus/usb),显示了当前连接的USB设备及总线的各种信息,每个连接的USB设备在其中都会有一个文件进行描述。比如文件/proc/bus/usb/xxx/yyy,xxx表示总线的序号,yyy表示设备在总线的地址,不过不能够依赖它们来稳定地访问设备,因为同一个设备两次连接对应的描述文件可能会不同。比如,第一次连接一个设备时,它可能是002/027,一段时间后再次连接,它可能就已经改变为002/048。

usbfs与咱们探讨的主题关系不大,况且也已经足可以开个专题来讨论了,所以以后不会过多地提及它。

这一项有关USB设备的挂起和恢复。开发USB的人都是节电节能的好孩子,所以协议中就规定了:所有设备都必须支持挂起状态,也就是说,为了达到节电的目的,当设备在指定的时间内(3 ms),如果没有发生总线传输,就要进入挂起状态。当它收到一个non-idle的信号时,就会被唤醒,节约用电从USB做起。不过目前来说,内核对挂起休眠的支持普遍都不太好,而且许多USB设备也没有支持它,还是暂且不提了。

剩下的还有几项,不过似乎与咱们关系也不大,还是去看一看Makefile。

Makefile可比Kconfig简略多了,所以看起来也更亲切,咱们总是拿的钱越多越好,看的代码越少越好。这里之所以会出现CONFIG_PCI,是因为USB的Root Hub通常包含在一个PCI设备中,前面也已经聊过了。hcd-pci和hcd顾名思义就是主机控制器,它们实现了主机控制器公共部分,按协议中的说法,它们就是HCDI(HCD的公共接口),host目录下则实现了各种不同的主机控制器。CONFIG_USB_DEVICEFS在前面的Kconfig文件中也见到了,关于usbfs,与咱们的主题无关,inode.c和devices.c两个文件也可以不用管了。10.从这里开始

USB Core从USB子系统的初始化开始,我们也需要从那里开始,它们位于文件drivers/usb/core/usb.c中:

我们看到一个subsys_initcall,它也是一个宏,我们可以把它理解为module_init,只不过因为这部分代码比较核心,开发人员们把它看做一个子系统,而不仅仅是一个模块。这也很好理解,usbcore这个模块代表的不是某一个设备,而是所有USB设备赖以生存的模块,在Linux中,像这样一个类别的设备驱动被归结为一个子系统。比如PCI子系统、SCSI子系统,基本上,drivers/目录下面第一层的每个目录都算一个子系统,因为它们代表了一类设备。

subsys_initcall(usb_init)的意思就是告诉我们,usb_init是USB子系统真正的初始化函数,而usb_exit()将是整个USB子系统结束时的清理函数,于是我们就从usb_init开始看起。

首先看863行的__init标记,写过驱动的人应该不会陌生,它对内核来说就是一种暗示,表明这个函数仅在初始化期间使用,在模块被装载之后,它占用的资源就会释放掉用于它处。它的暗示你懂,可你的暗示它却不懂或者懂装不懂,多么让人感伤。它在自己短暂的一生中一直从事繁重的工作,吃的是草,挤出来的是牛奶,留下的是整个USB子系统的繁荣。

受这种精神所感染,我觉得还是有必要为它说得更多一些。__init的定义在include/linux/init.h文件中:

好像这里引出了更多的疑问,__attribute__是什么?Linux内核代码使用了大量的GNU C扩展,以至于GNU C成为能够编译内核的唯一编译器,GNU C的这些扩展对代码优化、目标代码布局、安全检查等方面也提供了很强的支持。而__attribute__就是这些扩展中的一个,它主要被用来声明一些特殊的属性,这些属性主要被用来指示编译器进行特定方面的优化和更仔细的代码检查。GNU C支持十几个属性,section是其中的一个,我们查看GCC的手册可以看到下面的描述:

通常,编译器将函数放在.text节,变量放在.data节或.bss节,使用section属性,可以让编译器将函数或变量放在指定的节中。那么前面对__init的定义便表示将它修饰的代码放在.init.text节。连接器可以把相同节的代码或数据安排在一起,比如__init修饰的所有代码都会被放在.init.text节里,初始化结束后就可以释放这部分内存。

那内核又是如何调用到这些__init修饰的初始化函数?要回答这个问题,还需要回顾第938行的代码,上面已经提到subsys_initcall也是一个宏,它也在include/linux/init.h中定义:

这里又出现了一个宏__define_initcall,它用于将指定的函数指针fn放到initcall.init节里,而对于具体的subsys_initcall宏,则是把fn放到.initcall.init的子节.initcall4.init里。要弄清楚.initcall.init、.init.text和.initcall4.init,我们还需要了解一点内核可执行文件相关的概念。

内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中。换句话说,它将所有输入对象的文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。vmlinux.lds是存在于arch/<target>/目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定的偏移量处。

笔者可以负责任地告诉你,要看懂vmlinux.lds这个文件是需要花一番工夫的,不过大家都是聪明人,聪明人做聪明事,所以你需要做的只是搜索initcall.init,然后便会看到似曾相识的内容。

这里的__initcall_start指向.initcall.init节的开始,__initcall_end指向它的结尾。而.initcall.init节又被分为了7个子节,分别如下。

我们的subsys_initcall宏便是将指定的函数指针放在了.initcall4.init子节。其他的比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数指针放在了.initcall6.init子节等,都可以从include/linux/init.h文件找到它们的定义。各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针,再调用.initcall2.init中的函数指针等。__init修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init节里函数指针的顺序有关,不同的初始化函数被放在不同的子节中,因此,也就决定了它们的调用顺序。

至于实际执行函数调用的地方,就在/init/main.c文件中,内核的初始化不在那里还能在哪里?do_initcalls函数会直接用到这里的__initcall_start、__initcall_end来进行判断。不多说了,还是回到久违的usb_init函数吧。11.面纱

前面说了那么多,才接触到usb_init。当然,我们并不需要去经历爱情、背叛与死亡,所需要经历的只是忍受前面大段大段的唠叨。

因为被__init给盯上,usb_init在做牛做马的辛勤劳作之后便不得不灰飞烟灭,不可谓不高尚,但它始终只能是我们了解面纱后面内容的跳板,是起点,却不是终点,我们不会为它停留太久,有太多的精彩和苦恼在等着我们。

866行,知道C语言的人都会知道nousb是一个标志,只是不同的标志有不一样的精彩。这里的nousb是用来让我们在启动内核时通过内核参数去掉USB子系统的,Linux社会是一个很人性化的世界,它不会去逼迫我们接受USB,一切都只关乎我们自己的需要。不过我想我们一般是不会去指定nousb的吧,毕竟它那么的讨人喜爱。如果你真的指定了nousb,那它就只会幽怨地说一句“USB support disabled”,然后退出usb_init。

867行,pr_info只是一个打印信息的可变参数宏,即printk的变体,在include/linux/kernel.h中定义:

1999年的ISO C标准里规定了可变参数宏,与函数语法类似,比如:

其中的“…”就表示可变参数,调用时,它们就会替代宏体里的__VA_ARGS__。GCC总是会显得特立独行一些,它支持更复杂的形式,可以给可变参数取个名字,比如:

有了名字之后,总是容易交流一些。是不是与pr_info比较接近了?除了“##”,它主要是针对空参数的情况。既然说是可变参数,那传递空参数也总是可以的。如果没有“##”,传递空参数时,比如:

展开后,里面的字符串后面会有一个多余的逗号,这个逗号你应

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载