1.3有操作系统时的设备驱动
1.2节中我们看到一个干净利落的设备驱动,它直接运行在硬件之上,不与任何操作系统关联。当系统中包含操作系统后,设备驱动会变得怎样?
首先,无操作系统时设备驱动的硬件操作工作仍然是必不可少的,没有这一部分,驱动不可能与硬件打交道。
其外,我们还需要将驱动融入内核。为了实现这种融合,必须在所有设备的驱动中设计面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备。
由此可见,当系统中存在操作系统的时候,驱动变成了连接硬件和内核的桥梁。如图1.4,操作系统的存在势必要求设备驱动附加更多的代码和功能,把单一的“驱使硬件设备行动”变成了操作系统内与硬件交互的模块,它对外呈现为操作系统的API,不再给应用软件工程师直接提供接口。
图1.4
硬件、驱动、操作系统和应用程序的关系
那么我们要问,有了操作系统之后,驱动反而变得复杂,那要操作系统干什么?
首先,一个复杂的软件系统需要处理多个并发的任务,没有操作系统,想完成多任务并发是很困难的。
其次,操作系统给我们提供内存管理机制。一个典型的例子是,对于多数含MMU的处理器而言,Windows、Linux等操作系统可以让每个进程都可以独立地访问4GB的内存空间。
上述优点似乎并没有体现在设备驱动身上,操作系统的存在给设备驱动究竟带来了什么实质的好处?
简而言之,操作系统通过给驱动制造麻烦来达到给上层应用提供便利的目的。当驱动都按照操作系统给出的独立于设备的接口而设计,那么,应用程序将可使用统一的系统调用接口来访问各种设备。对于类UNIX的VxWorks、Linux等操作系统而言,当应用程序通过write()、read()等函数读写文件就可访问各种字符设备和块设备,而不论设备的具体类型和工作方式,那将是怎样地便利?
1.4 Linux设备驱动
1.4.1设备的分类及特点
计算机系统的硬件主要由CPU、存储器和外设组成。随着IC制作工艺的发展,目前,芯片的集成度越来越高,往往在CPU内部就集成了存储器和外设适配器。譬如相当多的ARM、PowerPC、MIPS等处理器都集成了UART、I
2C控制器、USB控制器、SDRAM控制器等,有的处理器还集成了片内RAM和FLASH。
驱动针对的对象是存储器和外设(包括CPU内部集成的存储器和外设),而不是针对CPU核。Linux将存储器和外设分为3个基础大类:
·
字符设备
·
块设备
·
网络设备
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如对于FLASH设备,符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问。
字符设备和块设备的驱动设计呈现出很大的差异,但是对于用户而言,他们都使用文件系统的操作接口open()、close()、read()、write()等进行访问。
在Linux系统中,网络设备面向数据包的接收和发送而设计,它并不对应于文件系统的节点。内核与网络设备的通信与内核和字符设备、网络设备的通信方式完全不同。
另外一种设备分类方法中所称的I
2C驱动、USB驱动、PCI驱动、LCD驱动等本身可归纳入3个基础大类,但是对于这些复杂的设备,Linux也定义了独特的驱动体系结构。
1.4.2 Linux设备驱动与整个软硬件系统的关系
如图1.5所示,除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等即可访问字符设备和块设备。所有的字符设备和块设备都被统一地呈现给用户。块设备比字符设备复杂,在它上面会首先建立一个磁盘/FLASH文件系统,如FAT、EXT3、YAFFS、JFFS等。FAT、EXT3、YAFFS、JFFS规范了文件和目录在存储介质上的组织。
应用程序可以使用Linux的系统调用接口编程,但也可使用C库函数,出于代码可移植性的目的,后者更值得推荐。C库函数本身也通过系统调用接口而实现,如C库函数fopen()、fwrite()、fread()、fclose()分别会调用操作系统的API open()、write()、read()、close()。
图1.5 Linux
设备驱动与整个软硬件系统的关系
1.4.3 Linux设备驱动的重难点
Linux设备驱动的学习是一项浩繁的工程,包含如下的重难点:
·
编写Linux设备驱动要求工程师有非常好的硬件基础,懂得SRAM、FLASH、SDRAM、磁盘的读写方式,UART、I
2C、USB等设备的接口以及轮询、中断、DMA的原理,PCI总线的工作方式以及CPU的内存管理单元(MMU)等。
·
编写Linux设备驱动要求工程师有非常好的C语言基础,能灵活地运用C语言的结构体、指针、函数指针及内存动态申请和释放等。
·
编写Linux设备驱动要求工程师有一定的Linux内核基础,虽然并不要求工程师对内核各个部分有深入的研究,但至少要明白驱动与内核的接口。尤其是对于块设备、网络设备、FLASH设备、串口设备等复杂设备,内核定义的驱动体系架构本身就非常复杂。
·
编写Linux设备驱动要求工程师有非常好的多任务并发控制和同步的基础,因为在驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发与同步机制。
上述经验值的获取并非朝夕之事,因此要求我们有足够的学习恒心和毅力。对这些重难点,本书都会有相应章节进行呈现。
1.4.4 Linux设备驱动的学习方法
动手实践永远是学习任何软件开发的最好方法,学习Linux设备驱动也不例外。因此,您最好有一块可以实际练手的电路板来构造嵌入式的开发环境。如果您暂时没有,则可以选择这样的权宜之策:用VmWare搭建两台虚拟机,两台虚拟机上都运行Linux操作系统,一台作为开发主机,一台作为目标机。
目前的PC上往往只有1个串口,但是调试要求主机和目标机之间使用串口通信,这要求2个串口。在虚拟机中我们可以用管道虚拟串口。在主机端设置“终端是客户机”,并选择“其它终端是一个虚拟机”。在目标机端设置“终端是服务器”,同样选择“其它终端是一个虚拟机”,但是要启用轮询。主机和目标机的串口设置分别如图1.6和1.7。
![](http://blog.donews.com/images/blog_donews_com/21cnbao/image012.jpg?x-oss-process=image/resize,w_1400/format,webp)
图1.6 VmWare
中主机串口设置
图1.7 VmWare
中目标机串口设置
源代码是学习Linux的最权威资料,阅读Linux源代码的最佳工具是Source Insight,在其中建立一个工程,并将Linux的所有源代码加入该工程,同步这个工程之后,我们将可以非常方便地在代码之间进行关联阅读,如图1.8。
图1.8
在Source Insight
中阅读Linux
源代码
除此之外,阅读经典书籍和参与Linux社区的讨论也是非常好的学习方法。Linux内核源代码中包含了一个Documentation目录,其中包含了一批内核设计的文档,全部是文本文件。很遗憾,这些文档的组织不太好,内容也不够细致。本书的参考目录中给出了一些优秀的参考书籍和Linux网站,并进行了简单的介绍。
学习Linux设备驱动的一个注意事项是要避免管中窥豹、只见树木不见森林,因为各类Linux设备驱动都从属于一个由Linux规范的架构,单纯而片面地学习几个函数、几个数据结构是不可能理清驱动中各组成部分之间的关系的。因此,Linux驱动的分析方法是点面结合,将对函数和数据结构的理解放在整体架构的背景之中。这同样是本书各章节讲解驱动的方法。
本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120070,如需转载请自行联系原作者