转载文章,来自:http://blog.csdn.net/fudan_abc/article/details/5756323
这本《Linux内核修炼之道》已经开卖(网上的链接为:卓越、当当、china-pub),虽然是严肃文学,但为了保证流畅性,大部分文字我还都是斟词灼句,反复的念几遍才写上去的,尽量考虑到写上去的每段话能够让读者产生什么疑惑,然后也都会紧接着尽量的去进行解释清楚,中间的很多概念也有反复纠结过怎么解释能够更容易的理解,力求即使对于初学者也可以有很少阻碍的一气读完。同时我也把书中一部分自己的感悟抽出来整理了精华版,share出来。当然水平有限,错漏之处有发现而修订时遗漏的,也有尚没有发现的。这本书如果对您有用,乃我之幸事,如果无用,就在此先诚惶诚恐的向大家拜个不是了。
下面仍然是之前5月份一次presentation的部分内容及讲义,不过当时的题目叫“驱动开发的方法论”,或许对大家有用吧。至于这几次讲座的视频貌似网上都能找到。
************************************************************************
第一个原因是:目前几乎所有的驱动开发方面的参考书,内容结构都是先介绍介绍什么是linux驱动,它分为哪些种类,然后是各种类型设备的驱动程序的内容细节。大都是只注重各种驱动本身的细节,而没有站在一个全局整体的角度讲解一下驱动开发的方法。这样导致的后果就是,大多数的驱动开发者虽然可以正确的编写驱动程序,但往往都是只知其一不知其二,知其然而不知其所以然。
第二个原因是:目前很多驱动开发者,即使是已经有多年经验的开发者,在开发驱动的时候也就是填充填充driver的结构体,对于比较成熟的平台,就是网上找个类似的驱动修改一下,即使写十个百个千个驱动,也就是对某些硬件比较熟,遇到全新的芯片全新的平台就束手无策。应该说这样对驱动的理解是很有限的。这也是目前linux驱动开发领域的现状。
我们首先认识一下linux驱动的基本面,我们认识一个新事物的的第一件事就是了解它的一些基本信息,就像我们人与人之间互相认识首先也是通过个人的基本信息一样。
linux驱动在本质上就是一种软件程序,上层软件可以在不用了解硬件特性的情况下,通过驱动提供的接口,和计算机硬件进行通信。
系统调用是内核和应用程序之间的接口,而驱动程序是内核和硬件之间的接口,也就是内核和硬件之间的桥梁。它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。
linux驱动程序是内核的一部分,管理着系统中的设备控制器和相应的设备。它主要完成这么几个功能:对设备初始化和释放;传送数据到硬件和从硬件读取数据;检测和处理设备出现的错误。
一般来说,一个驱动可以管理一种类型的设备。例如不同的U盘都属于mass storage设备,我们不需要为每一个U盘编写驱动,而只需要一个驱动就可以管理所有这些mass storage设备。
为方便我们加入各种驱动来支持不同的硬件,内核抽象出了很多层次结构,这些层次结构是linux设备驱动的上层。它们抽象出各种的驱动接口,驱动只需要填写相应的回调函数,就能很容易把新的驱动添加到内核。
一般来说,linux驱动可以分为三类,就是块设备驱动,字符设备驱动和网络设备驱动。块设备的读写都有缓存来支持,并且块设备必须能够随机存取。块设备驱动主要用于磁盘驱动器。
而字符设备的I/O操作没有通过缓存。字符设备操作以字节为基础,但不是说一次只能执行一个字节操作。例如对于字符设备我们可以通过mmap一次进行大量数据交换。字符设备实现比较简单和灵活。
网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD的socket机制。网络设备驱动为网络操作提供接口,管理网络数据的接送和收发。为了屏蔽网络环境中物理网络设备的多样性,Linux对所有的物理设备进行抽象并定义了一个统一的概念,称之为接口(interface)。所有对网络硬件的访问都是通过接口进行的,接口对上层协议提供一致化的操作集合来处理基本数据的发送和接收,对下层屏蔽硬件差异。它与字符设备及块设备不同之处其一就是网络接口不存在于Linux的设备文件系统/dev/中。
和前一篇的介绍一样,看完外表,我们再看内涵,就是Linux驱动的工作流程。大概有四个部分:使用insmod加载,模块的初始化,进行设备操作,使用rmmod卸载。
Linux驱动有两种存在形式,一种是直接编译进内核,就是我们在配置内核的时候,在相应选项上选Y,另外一种就是编译成模块,按需加载和卸载。通常我们使用insmod命令完成模块的加载,在加载时还可以指定模块参数。另外一个常用的加载工具是modprobe,它与insmod的不同在于它会检查模块之间的依赖关系,将该模块依赖的模块也加载到内核。
每个驱动都有自己的初始化函数,完成一些新功能的注册,这个初始化函数只是在初始化的时候被使用。在linux系统里,设备以文件的形式存在,应用程序可以通过open、read等函数操作设备,通过设备文件实现对设备的访问。设备不再使用时,我们使用rmmod命令来卸载它,卸载的过程会调用到驱动的推出函数,每个驱动都必须有一个退出函数,没有的话,内核就不会允许去卸载它。
在对linux驱动的外表和内涵都有了一个初步的认识之后,我们来看看作为一个驱动开发者,我们需要注意哪些问题。
首先,对模块机制的了解是开发linux驱动的基础,因为我们编写驱动的过程也就是在编写一个内核模块的过程。早期版本的内核是整体式的,也就是说所有的部分都静态地连接成一个很大的执行文件。但是现在的内核采用的是新的机制,即模块机制:许多功能包含在模块内,当你需要时可以使用insmod去拥抱它,将它动态地载入到内核里,当你不需要时,则可以使用rmmod将它一脚踢开。这就使得kernel的内核很小,而且在运行的时候可以不用reboot就能够载入和替代模块。
其次,我们要注重对设备模型的理解。其实从2.6内核开始,随着设备模型的出现,驱动的开发就不再是个困难的问题,毫不夸张得说,理解了设备模型,再去看那些五花八门的驱动程序,你会发现自己站在了另一个高度,从而有了一种俯视的感觉,就像凤姐俯视知音和故事会,韩峰同志俯视女下属。不过貌似大部分驱动开发者都没意识到这个问题。
最后,是要养成使用协议的spec、设备的datasheet、内核参考代码去解决问题的习惯,而不是一碰到问题就到处寻找所谓的牛人去问怎么解决。
中间的那些内容和前面精华版的博文里差不多,就不贴了,…………
前面介绍了我个人感觉开发驱动需要注意的三个方面,现在说个实际的例子。前些天一个网友在自己的csdn博客上写了篇文章,名字就叫给fudan_abc的一封信,信里说了自己的问题,我觉得应该很多人都存在这样类似的问题,这里咱们来看一下。
先说他个人的情况:有一定的linux c基础,熟悉linux内核/驱动的开发环境搭建和编译。现在想做个i2c芯片的驱动,该驱动要跑在x86平台上。
手边的资料有:
1.芯片手册看了几次,基本了解上边的资源和他的i2c地址
2.找到linux + arm平台上的对应的.c文件和.h文件。但是对应的arm平台的kernel source没有。
3. .c文件里的代码看过一遍,从函数名可以知道他的功能。
然后开始操作怎么移植这个i2c芯片到x86平台上。之后遇到了一些列的问题,比如i2c的地址信息等等,具体我就不叙述了。
然后他就去请教了一些人
请教某人A:“其实i2c很简单,你填充那个driver结构体就ok了。”我反问“我对里边的流程和调用不熟悉啊“
A回:”你把他们想象成一个黑盒就可以了”我:“无法想象,我怎么想象?我想看看里边的代码到底是怎样的。”A:"....."。对A的请教结果,无法解决我的问题:我到底该怎么办?
再请教B:
B:“其实i2c移植就是注册那几个函数,你想看内核代码实现,内核这么大你怎么可能搞清楚,我做了那么多移植,有时候连芯片手册都搞不清,直接prink吧代码给调出来的”我:“。。。”无法理解,无话可说。
其实这里边的A和B说的也没错,很多人写驱动大概就这么做的,但是这样子就是写成百上千个驱动也不能说就理解linux驱动了,面试时碰到的绝大部分人都属于这种情况,能回答自己做了什么,但一谈到一些相关的基本的问题就往往回答不上来。
我觉得首要的问题是缺乏好奇心,做技术的好奇心应该是原动力,特别对于搞linux内核和驱动的,好奇心有多强,你的水平就可能会增长到多高。
其次,对于做驱动的来说,对于2.6内核,重要的是去理解设备模型,很多人都本末倒置了,很多专门写驱动的书也不注重设备模型的理解,只去应付一种种类型的协议和设备的驱动,即使写个一万个也仅仅是对比较成熟的芯片熟了些。而且,不理解设备驱动,难道写驱动的时候不觉得很多东西很朦胧么?这也是我个人觉得很奇怪的一方面。
像我之前设备模型的文章里说的那样,理解了设备模型,对各种类型的驱动就有种俯视的感觉了。这个时候再你去看特定类型的协议和设备的实现,脉络就很清晰,比如你看i2c的实现。这个时候重要的就是i2c的协议,具体芯片的datasheet,加上看看内核里现有的i2c驱动作为参考,就是我所说的驱动开发的三件宝。
所以说如果希望做2.6的驱动话,关键还是要先去理解下设备模型,将这个比较抽象的概念在心里形象化,然后再去看具体的驱动比较好。