首页 理论教育 通用操作系统设备管理机制实现

通用操作系统设备管理机制实现

时间:2023-10-21 理论教育 版权反馈
【摘要】:在正式描述Hello China的设备管理框架前,有必要对通用操作系统的设备管理机制进行描述,以便读者理解这些通用操作系统的设备管理机制,以此为基础,从而可以更好地理解Hello China的设备管理框架。若读者对通用的操作系统设备管理机制非常熟悉,可跳过此节,直接阅读下一节。比如,图10-1表示了一个典型的操作系统的硬件信息数据库。因此,操作系统需要为该设备分配一段连续的IO端口资源,并写入设备的配置寄存器,这就是设备配置的工作。

通用操作系统设备管理机制实现

在正式描述Hello China的设备管理框架前,有必要对通用操作系统(比如Windows系列、Linux系列等)的设备管理机制进行描述,以便读者理解这些通用操作系统的设备管理机制,以此为基础,从而可以更好地理解Hello China的设备管理框架。若读者对通用的操作系统设备管理机制非常熟悉,可跳过此节,直接阅读下一节。

为了对设备进行管理,操作系统必须充分收集系统的硬件配置信息,并建立相应的数据库(前面讲过,在Hello China的实现中,这个数据库是由DeviceManager全局对象管理和维护的)。一般情况下,这个收集设备硬件信息、建立设备信息数据库的过程,是在操作系统启动过程中进行的。在操作系统启动的过程中,首先从系统总线开始探测,比如针对PCI总线,操作系统引导代码读取适当的端口(比如CF8H或CF0H),根据读取结果来判断对应的PCI总线是否存在。若存在,则跳转到PCI总线驱动程序代码,PCI总线驱动程序代码完成PCI总线上所有连接设备的枚举和检测。当然,对于其他支持自动配置的总线类型也执行类似的操作。

为了维护设备硬件信息,操作系统一般维护一个特定格式的数据库,在操作系统加载期间,由初始化代码检测系统硬件配置,根据检测的信息填写这个数据库。再以PCI总线为例,PCI总线驱动程序会依次枚举总线上的设备,并读取设备配置信息(PCI相关的详细信息,请参考第9章),然后针对每个系统中存在的设备,在硬件信息数据库中创建相应的对象(数据结构),并使用读取的配置信息填充这个对象。针对系统中的每条总线,都会进行这样一个检测操作,最终的结果是,操作系统收集了所有的系统硬件信息,并存放到一个统一的数据库中进行管理。比如,图10-1表示了一个典型的操作系统的硬件信息数据库。

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

图10-1 操作系统维护的硬件信息数据库

这样,如果要标识一个物理设备,一种可选的方式是为每条总线分配一个数字,作为总线号,并为位于该总线上的所有设备分配唯一的设备号,来表示该总线上的不同设备,这样可以通过“总线号+设备号”,形成一个唯一的设备ID来标识具体的设备。

这个硬件信息数据库会根据操作系统实现的不同而位于不同的位置。例如,Windows操作系统就把这些硬件设备配置信息写到磁盘上(注册表内),而有些操作系统则会把这些硬件配置信息保存在内存中,一旦计算机重新启动就会丢失。

完成设备硬件信息的枚举之后,下一步工作就是为硬件设备分配系统资源了,如中断向量号、内存映射空间、IO端口地址、DMA通道等,这一步称为设备配置。由于这些资源信息都从一个统一的资源空间中分配,因此操作系统必须保证为设备分配的所有资源信息不能出现冲突的情况。举例来说,假设一台计算机外设采用IO端口的方式进行通信,提供PCI接口,在枚举该设备的时候,操作系统可能只会得到该设备所需要的端口范围大小,而具体的端口号则尚未确定。因此,操作系统需要为该设备分配一段连续的IO端口资源,并写入设备的配置寄存器,这就是设备配置的工作。当然,有的时候,BIOS可能已经为所有的硬件设备分配了IO端口资源,这时候操作系统需要确认BIOS为硬件分配的资源会不会出现冲突。很可能出现的一种情况就是,BIOS为两个不同的物理设备分配了相同的系统资源(比如都分配了IO端口8E0H-8EFH),这可能是由BIOS软件错误引起的,也可能是由于设备物理硬件原因引起的,操作系统必须能够检测到这种错误并进行处理(比如,为其中的一台物理设备保留端口号8E0H-8EFH,为另一台物理设备另外分配不同的IO端口资源)。

在对设备完成配置之后,设备还不能被正常使用,因为还没有加载设备的驱动程序。因此,进入正式使用步骤之前,操作系统必须加载对应的设备驱动程序。一般来说,操作系统维护一个硬件设备ID(比如PCI设备的设备ID)和对应设备的驱动程序的一个映射文件,在完成设备的枚举和配置之后,就可以得到物理设备的设备ID(针对不同的总线类型,设备ID的标识方式也不一样),这样操作系统就可以根据设备ID查找映射文件,找到对应的设备驱动程序的文件名,然后把设备驱动程序加载到内存中。对应的设备驱动程序提供了对实际物理设备进行操作的软件代码,并以函数指针的形式提供给操作系统核心。

到目前为止,从理论上说,设备已经可以使用了,因为设备已经被操作系统配置好,而且设备驱动程序已经被加载。但实际上,仅仅靠这些信息,用户应用程序是无法使用设备的。试想,用户应用程序希望通过Ethernet接口卡发送一个数据报文,而实际系统中存在两张以太网接口卡,这种情况下,必须采用一种机制让用户可以唯一指定一个特定的Ethernet接口卡,并唯一指定一个特定的操作(发送操作而不是接收操作)。对于功能的指定,可以通过不同的函数来进行,比如针对以太网接口卡,驱动程序提供发送、接收、重新启动等一系列接口函数,这样用户就可以调用不同的功能函数实现特定的功能。而对于设备的标识方式,一种可以选择的方式是使用设备的物理ID(设备ID)直接进行标识。这种方式在理论上是可行的,但不直观,用户将面临一系列无任何特定意义的数字,非常不容易使用。因此,可以考虑采用字符串的形式对设备进行标识。

一般操作系统的做法是,给每个具体的设备都分配一个字符串(具有很明显的描述含义),用来唯一指定一台设备,用户可以直接看到这些设备标识字符串。这个字符串可以由操作系统在枚举设备的时候指定,也可以由设备驱动程序自己指定。设备描述字符串往往需要与设备驱动程序进行关联,因为只有这样,才可以通过设备标识字符串直接找到具体的设备,并定位到具体的操作函数。因此,一般情况下,操作系统会单独维护另外一个数据库,这个数据库是由一系列的设备标识字符串加上对应的设备驱动程序操作函数所组成的对象的集合。比如,下面就是一个典型的数据库元素。

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

其中,DeviceIdentifyString是设备的标识字符串,而接下来的一系列函数指针,则是对应驱动程序所提供的功能函数的地址。这样用户在访问具体设备的时候,就可以通过设备字符串很容易地定位到上述数据库元素,并根据操作需求定位到具体的操作函数。比如,用户发出一个操作请求:

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

这样操作系统就可以查找上述数据库(根据“Ethernet0”),找到以后,根据操作类型(SendPacket)来定位到具体的函数,然后以剩下的参数为调用参数调用SendPacket函数。

图10-2示例了设备标识字符串数据库的格式。

上述数据库(链表)中的每个元素(结构)称为设备对象,这个存放设备对象的数据库(一般情况下,采用链表进行存放,因此有时候也称为“设备对象链表”),一般称为设备对象数据库。

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

图10-2 设备标识字符串数据库格式(www.xing528.com)

显然,上述实现方式中一个很重要的问题就是,针对不同的物理设备需要定义不同的操作函数,这样实现起来,显然是十分困难的,而且几乎是不可能的,因为操作系统无法预先知道所有的硬件设备。为了解决这个问题,操作系统对物理设备进行了抽象,抽象出了一组通用的函数,来操作所有的设备。其中,最典型的两个抽象出来的操作就是Read和Write,这样任何设备的驱动程序,只需要支持通用的抽象操作(其实是把设备特定的操作以通用操作的函数原型来实现)即可。比如,物理硬盘和Ethernet网卡都支持Read和Write操作。对于硬盘,在这两个操作中,只需要完成通常的读/写操作即可,但对于Ethernet网卡,则需要在读操作中,实现ReceivePacket功能,而在写操作中,实现SendPacket功能。这样实现后,对于用户程序的接口,也不用提供一个抽象的“OperateRequest”函数了,只需要提供有限的与抽象操作对应的函数即可。比如,提供给用户一个Read和一个Write函数,这两个函数与物理设备对应的驱动程序提供的操作相对应,每当用户针对特定的设备调用这两个函数的时候,操作系统就会把这种调用映射到对应设备驱动程序的相应函数,从而实现设备的透明访问。一个比较典型的例子就是Windows操作系统提供的ReadFile函数和WriteFile函数,这两个函数不但可以用于完成普通文件的读/写操作,也可以完成设备的读/写操作。实际上,在物理设备上(非文件)调用这两个函数的时候,操作系统就把这些函数的调用传递到了设备驱动程序相关函数的调用上。

到此为止,用户就可以很容易地访问硬件设备了,比如,用户调用Read(“Harddisk0”,…)(函数参数中,省略的部分为传递的参数),操作系统就会根据设备标识字符串“Harddisk0”查找设备对象链表,找到“Harddisk0”对应的设备对象,然后以用户传递过来的参数为参数(或稍做调整)调用设备驱动程序提供的Read函数。

但细心的读者可能发现,任何针对设备的操作,如果按照上述形式,则需要操作系统完成一个字符串查找工作(根据函数提供的设备标识字符串,查找设备对象数据库),这显然是十分低效的,尤其是设备操作十分频繁的时候。目前,大多数操作系统都提供一个打开(Open)操作,用户在这个函数调用中,指定设备标识字符串作为参数,函数返回的时候返回一个句柄(Handle),在实现上,这个句柄可能是设备对象的指针,或者其他可以快速检索到设备对象的数据,后续操作(比如Read、Write等)则不必提供设备标识字符串,只需直接使用Open函数返回的句柄即可。这样操作系统就可以省略查找过程,而直接通过句柄快速定位到设备对象,从而调用设备对象的相关操作。比如,对一个物理硬盘的访问,遵循下列顺序。

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

在对设备的操作完成之后,为保险或节约系统资源起见,一般需要采用Close函数(由操作系统提供)关闭打开的设备。

显然,这样对设备的访问就十分完善了。如果读者对Windows API十分熟悉,通过上面的叙述,就应该对Windows操作系统提供的CreateFile、ReadFile、WriteFile、CloseHandle等函数的实现机制有了一定了解,这些函数,分别与上面介绍的Open、Read、Write、Close对应。

最后补充一点,引入设备对象数据库(设备对象链表)的另外一个目的,是用于存储多设备实例情况下单个设备实例的特定状态数据。比如,计算机系统配备了两个IDE接口的物理硬盘,由于这两个物理硬盘都是IDE接口,因此只需要一个IDE驱动程序即可,这样为了存储这两个物理硬盘的相关信息,就可以创建两个设备对象,这两个设备对象分别具有不同的标识字符串(比如“IDEHD0”和“IDEHD1”),以及不同的状态参数(比如当前磁头的位置、当前需要读/写的扇区个数等),但这两个设备对象提供的操作函数,却是一样的,都是IDE接口硬盘驱动程序提供的函数。图10-3是设备对象数据库的一个更详尽的示例。

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

图10-3 一个设备对象数据库的例子

在这个例子中,所有的设备对象被存储在一个链表数据结构中。第一个设备对象“Harddisk0”是一个硬盘设备,HDRead和HDWrite函数是硬盘驱动程序提供的操作方法,这两个操作方法的原型必须与操作系统定义的一致;而COM1和COM0则是两个串口对象,这两个物理设备对象采用同一个设备驱动程序(COM通信端口驱动程序)提供的操作方法。需要注意的是,ComRead和HDRead的函数原型(参数)必须一样,都必须与操作系统抽象的操作函数保持一致。

综上所述,为了对计算机系统中的设备进行有效管理,操作系统一般情况下需要维护两套数据结构(数据库):硬件信息数据库和设备对象数据库(设备对象链表)。其中,硬件信息数据库是操作系统在引导的时候,通过收集硬件信息而建立的,用于维护系统中的硬件配置信息以及硬件物理参数。再强调一下,在Hello China的实现中,这个硬件设备的配置信息数据库是由设备管理器(DeviceManager)对象维护的。而设备对象数据库则是由操作系统建立,用于完成特定的设备与其操作方法(驱动程序)的关联,并提供设备标识字符串,用以标识设备,还用来保存设备运行过程中的状态信息。这个设备对象数据库(或设备对象链表),在Hello China的实现中,是在IOManager中实现的。这样读者就可进一步理解DeviceManager和IOManager的区别了。

下面对IOManager做一个简单的介绍,其详细实现,本章后续内容会陆续介绍。

IO管理器用于管理设备对象数据库(设备对象列表)和文件系统,并提供一组统一的函数(方法)呈现给上层应用程序,供上层应用程序访问设备对象列表和文件系统。对设备对象的管理,主要提供了设备对象的创建、销毁等函数供硬件驱动程序调用。对于设备驱动程序的管理,也是由IOManager完成。当前版本的实现中,对于驱动程序的管理按照下列方式进行:

(1)IOManager每加载一个设备驱动程序,都需要创建一个驱动程序对象(__DRIVER_OBJECT)并初始化,然后以该对象为参数,调用驱动程序提供的DriverEntry函数(每个驱动程序必须输出一个DriverEntry函数作为驱动程序的入口函数)。

(2)驱动程序使用输出的操作函数(Read、Write等)填写驱动程序对象的相关成员变量

(3)驱动程序在DriverEntry中,检查系统中对应的物理设备(通过调用DeviceManager提供的GetDevice函数),针对自己支持的每个设备,驱动程序必须创建一个设备对象(__DEVICE_OBJECT,通过调用IOManager提供的函数创建),并初始化该物理设备对象,比如赋予物理设备对象标识字符串等。

(4)第三步完成之后,由驱动程序创建的物理设备对象就会插入设备对象链表(由IOManager维护)中,一旦插入设备对象链表,就对应用程序可见了,应用程序就可以采用Open系统调用,打开这个设备,并调用Read、Write等函数对设备进行操作了。

另外,IO管理器对象也提供了应用程序调用的标准接口,比如Read、Write等,这些函数与驱动程序实现的一组标准接口对应,用户调用这些函数的时候,IOManager会把用户的调用映射到驱动程序提供的相应函数上。调用的参数直接从用户调用的参数传递到驱动程序提供的函数里,或者稍做调整,然后再传递到驱动程序提供的函数上。这样就实现了用户调用到具体设备驱动程序的透明传递。可见,操作系统是物理设备和用户应用程序之间的“桥梁”。

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

我要反馈