IO管理器(IOManager)是系统中的全局对象之一,整个系统中只存在一个这样的对象。该对象提供了面向应用的接口,比如CreateFile,ReadFile等函数供用户线程调用,来访问具体的设备。还提供了面向设备驱动程序的接口,供设备驱动程序调用,完成诸如创建设备对象、销毁设备对象等操作。
系统中所有加载的设备驱动程序都归该对象管理,系统中所有用户可以使用的设备,也归该对象管理。因此,可以认为该对象是设备管理框架的核心对象。
1.驱动程序对象和设备对象
在Hello China的实现中,对于每个加载的设备驱动程序,系统都为之创建了一个驱动程序对象,并调用驱动程序的DriverEntry函数来初始化这个驱动程序对象。驱动程序对象保存了对设备进行操作的所有函数指针,比如对设备的读函数、对设备的写函数等。
驱动程序对象可以理解为管理设备驱动程序的数据结构,而设备对象则对应于具体的物理设备,即设备对象是对物理设备进行直接管理的数据结构。设备对象由驱动程序创建,一般情况下,是在设备驱动程序加载并初始化的时候创建,一个比较合适的时机就是在DriverEntry函数中创建。
在设备对象中,有一个指向对应于该设备的设备驱动程序对象的指针。设备的所有操作都是由驱动程序对象提供的函数完成的,通过指向驱动程序对象的指针,即可找到特定的设备操作函数,进而完成对设备的操作。
为了说明设备对象和设备驱动程序对象的关系,下面举一个磁盘驱动程序的例子。
(1)在系统启动的时候,根据配置文件或总线检测结果,加载硬盘驱动程序。其中驱动程序的加载工作,是由IOManager完成的。
(2)完成驱动程序的加载(加载过程包括读入驱动程序文件、重定位、根据文件头找到DriverEntry函数的入口地址等)后,IOManager创建一个设备驱动程序对象,并以该对象为参数调用硬盘驱动程序的DriverEntry函数。
(3)驱动程序(实际上是DriverEntry函数)对系统中的硬盘设备进行检测。比如检测硬盘的数量、每个硬盘的分区情况等,都在这个检测过程中完成,实际上,检测是一个收集数据的过程。
(4)硬盘驱动程序根据收集的数据,比如系统中的硬盘数量以及每个硬盘的分区情况等,创建相应的设备对象(通过调用IOManager提供的CreateDevice函数)。一般情况下,针对每个硬盘、每个硬盘分区分别创建设备对象。假设系统中安装了一个硬盘,该硬盘划分了四个分区,则DriverEntry创建五个设备对象(分别为硬盘设备对象、分区一设备对象、分区二设备对象、分区三设备对象和分区四设备对象)。在创建设备对象的时候,驱动程序需要为每个设备对象取一个字符串名字。在Hello China的实现中,第一个物理硬盘的名字为“PhysicalDisk0”,第二个为“PhysicalDisk1”,依此类推。对于分区设备,第一个分区的名字为“Partition0”,第二个为“Partition1”,依此类推。
(5)上述步骤完成之后,硬盘就可以供具体的应用线程使用了。因为上述几个设备对象已被IOManager插入设备链表。一旦插入设备链表,用户应用程序(用户线程)即可调用Open函数(实际上是IOManager的CreateFile函数)来打开设备并访问了。
比如,有一个用户线程读取硬盘数据,则具体的过程如下。
(1)用户调用ReadFile函数发起一个硬盘读取请求(该函数的参数提供了硬盘对象设备对象的地址)。
(2)IOManager根据ReadFile提供的设备对象的地址,找到该对象对应的驱动程序对象(设备对象保存了指向驱动程序对象的指针)。
(3)IOManager创建一个DRCB对象(参考10.2.1节)并初始化,然后调用驱动程序对象中特定的函数(DeviceRead函数)。
(4)该函数完成具体的硬盘读/写操作,并返回。
(5)IOManager根据返回的结果,填充用户缓冲区,然后返回给用户线程。
需要指出的是,在调用ReadFile函数读取设备内容的时候,需要首先打开设备(以设备名称为参数,调用CreateFile函数)。在打开设备的过程中,IOManager会根据设备名称查询整个设备链表,返回匹配的设备对象指针(在Windows操作系统中,这个返回的值叫做句柄。后续描述中,有时候也把设备对象指针叫做句柄)。
2.IOManager对设备对象和设备驱动程序的管理
在Hello China当前版本的实现中,所有驱动程序对象和设备对象都是由IOManager直接管理的。在实现中,IOManager维护了两个双向链表,一个链表把系统中所有的驱动程序对象连接在一起,另一个链表把系统中所有的设备对象连接在一起,在IOManager的定义中,有两个成员变量:
这两个变量指向两个双向链表的头节点。
整体架构可参考图10-5。
图10-5 Hello China的设备对象和设备驱动程序对象
3.IOManager对象的实现
下面正式介绍IOManager对象的实现代码。首先看该对象的定义(为了方便,删除了部分注释和无关内容):
可以看出,IOManager的定义比较复杂,涉及很多函数,但这些对外函数(或接口)总体上可以分为四类:
(1)初始化函数(Initialize),系统初始化的时候调用该函数初始化IOManager。
(2)对用户的接口,由用户调用来访问设备。如果读者对Windows的API比较熟悉,那么对IOManager定义的这些函数的功能应该不会陌生。实际上这些函数的语义,与Windows大致相同。
(3)对设备驱动程序的接口,由设备驱动程序调用来获得IOManager的服务。
(4)文件系统驱动程序专用的几个函数,主要用于文件系统驱动程序向操作系统核心注册文件系统等功能,在第12章中将做详细介绍。
在下面的部分中,分别对初始化函数和设备驱动程序服务函数进行描述,文件系统相关的全局变量和函数将在第12章中做详细介绍。
4.初始化函数(Initialize)
初始化函数(Initialize)用来进行一些初始化工作,在Hello China启动的时候调用。在目前的实现中,该函数完成下列功能:
(1)初始化设备驱动程序。Hello China当前版本的设计目标为嵌入式操作系统,这样就不需要动态地加载设备驱动程序,设备驱动程序事先已经同操作系统内核编译在一起了。但设备驱动程序所遵循的框架也与动态加载的设备驱动程序一致,不同的是少了加载的步骤(动态加载设备驱动程序包括从存储设备读入驱动程序、重定位等步骤)。在IOManager的Initialize函数中,会调用每个连接到操作系统核心的设备驱动程序的DriverEntry函数。
(2)其他相关工作。
上述所有工作顺利完成之后,Initialize函数将返回TRUE,若该函数返回FALSE,会导致系统停止引导。
另外一个问题就是,对于与操作系统核心连接在一起的驱动程序,Initialize函数如何确定其入口点(DriverEntry函数)。为了解决这个问题,当前版本的Hello China定义了一个数据结构:
(www.xing528.com)
并定义了一个全局数组:
这样,Initialize函数在实现时,就会遍历这个数组,为数组中的每个元素创建一个__DRIVER_OBJECT对象,然后调用对应的DriverEntry函数。
因此,对于每个需要静态联编并加载的设备驱动程序,程序开发者都需要在DriverEntryMap数组中手工添加一条记录。该数组的最后一条空记录({NULL,NULL})是该数组结束的标记。
Hello China目前没有实现动态设备驱动程序的加载功能,但如果将来需要,则可以按照下列思路实现这一功能。
通过另外一个帮助函数——GetDriverEntry——得到需要动态加载的设备驱动程序的入口地址,该函数原型如下:
其中,lpDevVender指向一个设备厂家ID结构,该结构描述了IOManager想要加载的设备的厂家信息,而lpDrvName则指明了加载的设备驱动程序的名字(可以为空)。GetDriverEntry根据厂家信息,查询系统的一个配置文件,并找到对应的驱动程序的文件名,然后调用ModuleManager的特定函数,ModuleManager根据文件名,在存储设备上找到合适的驱动程序,然后加载到内存(加载过程包括了重定位、名字解析、初始化等操作),并返回给加载模块的起始地址(返回给GetDriverEntry)。
其中,ModuleManager是模块管理器,用来完成把磁盘上的代码(可执行模块,比如动态链接库、应用程序可执行文件等)加载到内存中并重定位等功能。
在实现动态设备驱动程序加载的时候,IOManager的Initialize函数需要调用DeviceManager的相关函数,遍历系统中的硬件配置,对于检索到的每一个硬件,根据该硬件的__DEVICE_VENDOR标识,调用GetDriverEntry函数。
5.IOManager对应用的接口
IOManager提供了两个方向的接口:对应用程序的接口和对设备驱动程序的接口。其中,对应用程序的接口被应用线程调用,用来访问具体的设备,下列接口(函数)是对应用的接口(函数):
(1)CreateFile,用于打开一个文件或设备。在Hello China当前版本的实现中,所有的设备和文件同等对待,都是用名字来标识,该函数既可以打开某一文件系统中的特定文件,也可以打开一个特定的物理设备。
(2)ReadFile,从文件或设备中读取数据。在当前版本的实现中,该函数采用同步操作模式,即该函数一直等待设备操作完成,而不是中途返回(在Windows API中,实现了一种所谓的异步操作模式,即该函数向操作系统提交一个读取事务,然后直接返回,当操作系统完成事务指定的读/写动作后,向发起事务的进程发送一个消息,进程处理该消息,最后完成读/写操作),在这个过程中,调用该函数的线程可能被阻塞。
(3)WriteFile,向设备或文件写入数据,实现机制与ReadFile类似。
(4)CloseFile,CreateFile的反向操作,用于关闭CreateFile打开的设备或文件。在这个函数的实现中,如果操作目标是一个文件,则系统直接把相应的文件对象销毁,如果操作的对象是物理设备,则该对象不被销毁,而是递减对象的引用计数。
(5)IOControl,完成设备驱动程序独特的操作。有些操作是不能通过Read、Write等来抽象的,比如针对音频设备的快进、重复播放等,系统提供了该函数,相当于提供了一个万能的接口给用户程序,用户程序可以通过该函数调用、完成任意驱动程序特定的操作功能。
(6)SetFilePointer,移动文件的当前指针。
(7)FlushFile,把位于缓冲区中的文件内容写入磁盘。一般情况下,文件系统的实现大量地使用了缓冲机制,即对文件的写操作先在内存中完成,积累到一定的程度后,再由设备驱动程序统一递交到物理设备,这样可以大大提高操作效率。但有的情况下,应用程序可能需要立即把改写的文件内容写到物理存储设备上,比如应用程序关闭的时候,这样就需要调用该函数来主动地同步缓存和物理存储介质。需要说明的是,CloseFile在实际关闭文件对象前,总是调用FlushFile来同步缓冲区和物理存储介质。
有的操作系统提供了LockFile函数,该函数用于把打开的文件加锁,实现互斥的访问。在Hello China当前的实现中,没有提供该函数功能,主要是考虑到该函数用途可能不是很大,而且可以通过一些替代方式来完成,比如应用程序可以独占地打开一个文件,也可以在打开文件的时候,指定另外的打开标志,只允许其他应用程序只读地打开文件,等等。
这些函数的实现以及使用方法在第12章中有详细介绍,在此不作赘述。
6.IOManager对设备驱动程序的接口
下列函数供设备驱动程序调用。
(1)CreateDevice,该函数创建一个设备对象,并根据函数参数完成初步的初始化功能。一般情况下,设备驱动程序加载完毕,进入初始化阶段(DriverEntry函数)之后,设备驱动程序会检测设备,根据检测结果来创建相应的设备对象。比如,网卡驱动程序被加载之后,驱动程序会检测系统上是否安装了网卡,如果能够检测到网卡,那么驱动程序会创建一个网卡设备对象。
(2)DestroyDevice,该函数销毁CreateDevice函数创建的设备对象。
为了进一步理解上述几个函数的功能,下面描述一个比较典型的设备驱动程序加载、初始化过程,假设设备驱动程序为硬盘驱动程序。
(1)操作系统加载硬盘驱动程序文件,并完成诸如重定位等工作。
(2)IOManager调用硬盘驱动程序的入口函数(DriverEntry),硬盘驱动程序进入初始化工作。
(3)在硬盘驱动程序的DriverEntry函数内部检测系统的硬盘安装情况,比如安装硬盘的个数、每个硬盘的分区情况等。
(4)根据检测结果预留系统资源(调用DeviceManager对象提供的ReserveResource函数),有的情况下,IOManager会通过DriverEntry函数传递给驱动程序相应的设备资源,这种情况下,驱动程序必须使用系统分配的资源,但也必须显式地通过ReserveResource预留系统分配的资源,算是一个资源确认操作。
(5)根据检测的结果调用CreateDevice创建相应的设备对象。
(6)如果上述过程一切顺利,则初始化结束,设备可以使用。
7.驱动程序入口(DriverEntry)
驱动程序被加载到内存后,IOManager首先通过某种方式(具体参考下面的章节),找到一个所谓“入口函数”的地址,然后调用这个函数,这个函数就是所谓的驱动程序“入口”。驱动程序的入口原型如下。
其中,lpDriverObject是IOManager创建的一个驱动程序对象,而lpResDesc则是描述系统资源的数据结构指针。
该函数的具体实现是由驱动程序本身完成的,一般情况下,驱动程序可以在这个函数内初始化全局数据结构,创建设备对象,并设置驱动程序对象的一些变量(比如各个函数指针等),如果该函数成功执行,那么返回TRUE,这个时候,IOManager就认为驱动程序初始化成功,否则,返回FALSE,那么IOManager就会认为驱动程序初始化失败,于是就卸载掉该驱动程序,释放创建的驱动程序对象。
8.设备驱动程序的卸载
所谓设备驱动程序卸载,指的是把不再使用的驱动程序从内存中删掉,以释放内存,供其他应用程序使用。设备驱动程序的卸载发生在操作系统关闭、设备消失(被拔出等)等情况下,在设备驱动程序被卸载的时候,系统(IOManager)调用设备驱动程序的UnloadEntry函数,该函数释放驱动程序申请的资源。
从UnloadEntry返回后,IOManager会删除该驱动程序对应的驱动程序对象。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。