首页 理论教育 操作系统实现之路:设备驱动程序功能函数

操作系统实现之路:设备驱动程序功能函数

时间:2023-10-21 理论教育 版权反馈
【摘要】:ReadFile函数根据用户提供的参数,创建一个DRCB对象,并初始化该对象,然后根据用户提供的设备对象的地址找到该设备对象对应的设备驱动程序,调用设备驱动程序对象的DeviceRead函数。设备驱动程序维护了一个设备请求控制对象队列,该队列中缓存了所有未完成的设备请求,当DeviceRead函数被调用后,该函数会检查队列的状态是否为空,如果是空,则该函数把该DRCB对象插入队列,然后根据DRCB提供的参数,发起一个设备操作请求。

操作系统实现之路:设备驱动程序功能函数

从上面的设备驱动程序文件组织结构中看出,设备驱动程序实现了一组标准的功能函数集合,这些功能函数由IOManager调用(设备驱动程序在初始化时,DriverEntry函数要把这些函数的地址填写到驱动程序对象对应的函数指针内),设备驱动程序对实际设备的操作就是在这些功能函数中实现的。

一般情况下,对设备的操作可以抽象为读/写操作和打开/关闭操作,对应功能函数中的DeviceRead/DeviceWrite、DeviceOpen/DeviceClose等函数,但也有一些其他的操作,比如定位当前设备位置(DeviceSeek)、特殊的控制命令(DeviceCtrl)等。其中DeviceCtrl函数最为灵活,这个函数为特殊设备的特殊功能(不能抽象为Read/Write等操作的功能)提供了支持。

对设备的打开和关闭操作相对比较简单,在设备驱动程序实现的时候,针对打开操作,一般是创建一个设备对象,初始化并注册到操作系统(确切地说是IOManger)维护的设备对象链表中;对于关闭操作,设备驱动程序释放对应的系统资源,并从系统设备链表中删除该设备对象。

比较复杂的是对设备的读/写操作和控制操作(对应DeviceRead/DeviceWrite/DeviceCtrl函数),本节对这几个操作进行比较详细的实现描述。需要说明的是,设备不同,这些函数实现的方式和具体功能也不同,在这里描述的是一个相对通用框架,作为实现具体设备驱动程序时的参考。

1.读操作(DeviceRead)的实现

读操作的发起者,可以是用户线程、系统线程,也可以是设备驱动程序(比如,文件系统驱动程序,就需要读硬盘数据),但不论是哪种方式,其入口却只有一个,即所有的读操作都通过IOManager提供的ReadFile函数来实现,当然,在读一个设备的时候,该设备必须已经打开(即建立了设备对象)。在这里,假设一个用户线程发起一个读请求操作,从串行接口读取一个字节的数据,相应的流程如下。

(1)用户线程调用IOManager提供的ReadFile函数(通过系统调用)。

(2)ReadFile函数根据用户提供的参数,创建一个DRCB(Device Request Control Block)对象,并初始化该对象,然后根据用户提供的设备对象的地址找到该设备对象对应的设备驱动程序(设备对象维护了指向设备驱动程序对象的后向指针),调用设备驱动程序对象的DeviceRead函数。到此为止,所有的操作都是由操作系统核心完成的(确切地说是IOManager),后续的操作将由设备驱动程序自己完成。

(3)设备驱动程序维护了一个设备请求控制对象队列(DRCB队列),该队列中缓存了所有未完成的设备请求,当DeviceRead函数被调用后,该函数会检查队列的状态是否为空,如果是空,则该函数把该DRCB对象插入队列,然后根据DRCB提供的参数,发起一个设备操作请求(操作实际的设备)。如果队列不为空,则说明现在仍然有一些请求正在执行中。于是会根据设备访问方式的不同(中断或轮询),采取不同的处理动作。

(4)对于中断方式,DeviceRead函数会把该DRCB对象插入队列,然后调用WaitForCompletion函数(该函数由IOManager提供,其指针保存在DRCB对象里面)。该函数的调用会阻塞当前线程。

(5)对于轮询方式,则当前线程会持续检查队列的状态,直到队列为空。这时候才发起设备读取操作。显然,这是一个忙等待的过程,非常消耗CPU资源。设备操作成功完成或失败后,DeviceRead函数填充DRCB提供的缓冲区,设置DRCB对应的状态字段,然后返回给调用者(ReadFile函数)。

(6)在中断方式下,当设备操作完成之后,设备控制器会发起一个中断,通知操作系统该操作的完成,操作系统会调用对应的中断处理程序。中断处理程序从DRCB队列中摘取一个DRCB对象,根据设备的操作结果填充该对象,然后调用该DRCB对象的OnCompletion函数,该函数唤醒等待的线程。然后进一步检查DRCB队列是否为空,若是,则从中断中返回,否则,会从队列中获取一个DRCB对象,根据该对象指明的操作,再次发起一个设备操作,然后从中断中返回。图10-7反映了上述调用关系。

978-7-111-41444-5-Chapter10-25.jpg

图10-7 设备读取操作的步骤

上面描述的是没有缓冲的情况,实际上,为了提高访问速度,大多数的设备驱动程序提供了缓冲功能,在内存中创建缓冲区,缓存设备上的数据。当读请求到达时,设备驱动程序首先检查请求的内容是否位于本地缓冲区内,如果在,则直接从缓冲区中读出,这样可以大大提高读操作的速度。在实现缓冲的设备驱动程序中,上述流程略有不同,就是在上述第三步中,驱动程序首先检查本地缓冲区,如果读取的内容位于本地缓冲区内,则直接从缓冲区中读出,返回给用户,否则,再发起一个实际的设备操作。(www.xing528.com)

2.写操作(DeviceWrite)的实现

写操作的过程与读操作基本一致。不同的是,驱动程序提交一个设备的写操作,然后根据设备操作模式(中断模式或轮询模式)来等待或阻塞请求的线程,直到该操作完成。

3.设备控制(DeviceCtrl)的实现

读写操作不能抽象所有可能的设备操作,比如对一个音频设备,可能需要控制诸如暂停、重新开始、快进、倒退等操作,这种情况下,读写操作就无法胜任了。还有一种读写操作无法胜任的就是,设备的读写单位可能不一样。比如,针对串行接口可能是一个字节一个字节地读写,而针对硬盘、光盘等存储设备,则可能是一个数据块一个数据块地读写。在UNIX的实现中,对这两种类型的设备分别做了处理(对应于UNIX的字符设备和块设备)。而在Hello China的实现中,则没有进行区分,而进行了统一对待。但在读写的时候,客户程序必须首先确定每次操作的字节数(一个字节还是多个字节)。至于如何获取每次操作的字节数量,DeviceRead/DeviceWrite操作也是无法胜任的。因此,引入了设备控制操作(DeviceCtrl函数)。

设备控制操作是通过DeviceCtrl函数来实现的,用户通过IOControl函数调用(由IOManager提供)来实现对设备的DeviceCtrl函数的访问。

对于DeviceCtrl功能的输入参数和输出参数,在DRCB对象中做了完善的提供,客户程序在请求设备控制功能的时候,首先使用合适的参数调用IOManager的IOControl函数,该函数创建一个DRCB,根据IOControl的参数初始化这个对象,然后进一步调用设备驱动程序对象的DeviceCtrl函数。

DRCB对象中的dwCtrlCommand成员用来指出设备驱动程序应该执行哪个功能,然后调用适当的功能函数。一般情况下,下列控制功能必须实现。

(1)CONTROL_COMMAND_GET_READ_BLOCK_SIZE,获得设备每次读操作的数据大小,比如,针对串行接口可以是1B,针对磁盘可以是512B。

(2)CONTROL_COMMAND_GET_WRITE_BLOCK_SIZE,获得设备每次写操作的数据大小。

(3)CONTROL_COMMAND_GET_DEVICE_ID,获取设备的唯一ID,针对不同的设备,该功能的实现也不一样,而且ID也没有一个统一的编配。这种情况下,系统一致认为所有设备的ID是一个字符串,因此,设备驱动程序可以有选择地实现该功能,如果不能实现,则简单地返回失败结果。

(4)CONTROL_COMMAND_GET_DEVICE_DESC,获取设备的描述信息,设备驱动程序可以在描述信息中,对设备的具体型号、厂家、功能特点等进行描述,比如“Broadcom570x Gigabit Integrated Controller”。

其他的功能,设备根据实际的需要来自己定义,比如针对一个音频控制设备,驱动程序可以定义诸如快进、倒退、循环播放等功能命令,进而完成实现。

其他的功能函数,比如DeviceSeek、DeviceFlush等,功能比较简单,在第12章中会有比较详细的描述。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈