博大精深的Linux kernel,实在是一个迷人的领域,确实,对于从来没有把握过核心操作系统的中国人,这真的是一个相当有吸引力的东西。而且用计算机来控制外部设备工作实在是一个很有吸引力的工作,Device Drivers在操作系统中居于比较靠中间的位置,这一部分远比启动部分要更接近操作系统的实际工作,而有用到了大量的内核设施,实在也是一个学习Linux kernel的切入点。所以,想要学习Linux 设备驱动开发了。这是在阅读《Linux Device Drivers》时做的笔记,不为别的,全当做备忘录了吧,以方便回头需要复习的时候有迹可循。
设备驱动程序在Linux内核中扮演着特殊的角色。他们是一个个独立的“黑盒子”,一个特定的硬件相应的有一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节。用户的操作通过一组标准化的系统调用执行,而这些调用独立于特定的设备驱动程序。将这些调用映射到作用于实际硬件的设备特有操作上,则是设备驱动程序的任务。驱动也就是一组函数,一组回调函数。这个编程接口能使得驱动程序独立于内核的其他部分而建立,必要的情况下可在运行时“插入”内核。
促使我们对Linux驱动程序的编写感兴趣的原因有很多。首先,硬件发展迅速,每天都会有大量的新硬件出现;其次,个人用户可能需要了解一些驱动程序知识才能访问设备,UNIX针对文件系统节点的系统调用有它标准化语义,然而针对特定的设备,这些系统调用也会有一些专门针对于特定硬件的语义;另外,硬件厂商通过提供Linux驱动程序能为自己的产品带来数目庞大的潜在用户群——日益增长的Linux用户群;最后, Linux系统是开源的,如果驱动程序作者愿意,驱动程序源代码就可以在大量用户中间迅速流传。
每个驱动程序都不尽相同,但作为驱动程序开发者,我们应该很好地了解自己面对的具体设备。其实,也就是说,要想去编写实际的设备驱动程序,仔仔细细地去阅读设备或者SOC的数据手册是不可避免要做的工作,实际的去写一些裸机控制设备工作的代码绝对是大有好处的,比如用单片机。但是驱动程序相关的大部分原理和技巧都是相同的,内核的设施、设备模型、驱动程序的结构等等。
设备驱动程序的作用
作为驱动程序编写者,我们需要在所需的编程时间以及驱动程的灵活性之间选择一个可接受的折衷。“灵活”,实际上是强调驱动程序的作用是提供机制,而不是提供策略。“需要提供什么功能”(机制),“如何使用这些功能”(策略)。如果这两个问题由程序的不同部分来处理,或者甚至由不同的程序来处理,则这个软件包更易开发,也更容易根据需要来调整。不同环境通常需要不同的方式来使用硬件,我们应当尽可能做到让驱动程序不带策略。驱动程序应该处理如何使硬件可用的问题,而将怎样使用硬件的问题留给更上层得应用程序。
如果从另外一个角度来看驱动程序,他还可以看作是应用程序和实际设备之间的一个软件层。驱动程序的这种特权角色可以让编写者选择如何展现设备特性,也就是说,即使对于相同的设备,不同的驱动程序可能提供不同的功能,驱动程序可以解释系统调用接口的语义。实际的驱动程序设计应该在许多要考虑的因素之间做出平衡。总的来说,驱动程序设计主要还是综合考虑下面三个方面的因素:提供给用户尽量多的选项(正所谓要榨干硬件设备的每一滴油水来为用户工作)、编写驱动程序要占用的时间以及尽量保持程序简单而不至于错误丛生(简单即美,简单即善)。
不带策略的驱动程序包括一些典型的特征:同时支持同步和异步操作、驱动程序能够被多次打开、充分利用硬件特性,以及不具备用来“简化任务”的或提供与策略相关的软件层。
内核功能划分
驱动毕竟还是要同内核中的其他设施交互的,所以一开始也就需要对内核有一些了解,根据内核完成任务的不同。
1、进程管理
包括进程的创建、销毁,进程的调度,以及进程间通信机制。系统最最核心的东西。
2、内存管理
内核在有限的可用资源上为每个进程都创建了一个虚拟地址空间。内核的不同部分在和内存管理子系统交互时使用一组函数调用。
3、文件系统
Unix中的每个对象几乎都可以当做文件来看待。内核在没有结构的硬件上构造结构化的文件系统,而文件抽象在整个系统中广泛使用。Linux支持多种文件系统类型,也就是在物理介质上组织数据的不同方式。
4、设备控制
除了处理器(进程调度处理)、内存(内存管理系统)以及其他很有限的几个对象外,所有的设备控制操作都由与被控制设备相关的代码来完成。内核必须为系统中的每件外设嵌入相应的驱动程序。
5、网络功能
网络功能也必须有操作系统来管理,因为大部分网络操作和具体进程无关:数据的传入是异步事件。系统负责在应用程序和网络接口之间传递数据包,并根据网络活动控制程序的执行。另外,所有的路由和地址解析问题都由内核处理。
可装载模块
内核提供的特性可在运行时进行扩展。可在运行时添加到内核中的代码被称为“模块”。每个模块有目标代码(没有链接成一个完整的可执行程序),我们可以使用insmod或modprobe程序将模块连接到正在运行的内核,也可以使用rmmod程序移除连接。根据模块额功能可以将其划分为不同的类。
设备和模块的分类
Linux系统将设备分成三种基本类型,每个模块通常实现为其中某一类:字符模块、块模块或网络模块。(这种划分并不严格)
1、字符设备:字符(char)设备是个能够像字节流一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open、close、read和write等系统调用。字符设备可以通过文件系统的节点来访问。这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。然而,也有具有数据区特性的字符设备,访问它们时可前后移动访问位置。
2、块设备:也是通过/dev目录下的文件系统节点来访问。块设备上能够容纳文件系统。在大多数Unix系统中,进行I/O操作时块设备每次只能传输一个或多个完整的块。Linux可以让应用程序像字符设备一样的读写块设备,允许一次传递任意多字节的数据。因而,块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的(transparent)。
3、网络接口:任何网络事物都经过一个网络接口,即一个能够和其他主机交换数据的设备。通常,接口是个硬件设备,但也可能是个纯软件设备,比如环回(loopback)接口网络接口有内核中的网络子系统驱动,负责发送和接收数据包,但它不需要了解每项事物如何映射到实际传送的数据包。由于不是面向流的设备,因此将网络接口映射到文件系统中比较困难。Unix访问网络接口的方法仍然是给他们分配一个唯一的名字,比如eth0,但这个名字在文件系统中不存在对应的节点。内核通过一套和数据包传输相关的函数来和网络设备驱动程序通信。
还有另外一种划分驱动程序模块类型的方法。一般而言,某些驱动程序类型同内核用来支持某种给定类型设备的附加层一起工作。内核开发者实现整个设备类型的共同特性,然后提供给驱动程序实现。
除了设备驱动程序外,内核中的其他一些功能也都模块化了。一个文件系统类型决定了如何在块设备。例如文件系统等。
阅读(2121) | 评论(0) | 转发(0) |