前面分别介绍了文件系统驱动程序和存储设备驱动程序的加载过程。从介绍的内容来看,这两者似乎是完全独立的,没有任何关联。但是要实现文件操作功能,必须让这两者建立关联,否则无法建立正确的文件卷。在介绍文件系统驱动程序的时候我们提到,文件系统驱动程序的加载,一定要先于存储设备驱动程序。之所以有这样的约束条件,正是为了在加载存储设备驱动程序的时候,能够让存储设备有机会“接触”到文件系统,提供一个“展示”自己的机会,以让文件系统能够识别自己。用一个不恰当的比喻,这是一个“寻找组织”的过程。文件系统就好比是组织,而存储设备则是脱离组织的“成员”,存储设备需要找到正确的组织,才能实现自身的有效管理。
这个“寻找组织”的过程,是由IOManager作为中介来“协助”完成的。文件系统不知道存储设备的存在,同时存储设备也不知道文件系统在哪里。这样IOManager就必须为两者搭一座桥,让它们能够有效沟通。细心的读者会发现,是IOManager的CreateDevice函数完成了这个“搭桥”的工作。
文件系统驱动程序首先加载。在加载的时候,会创建一个文件系统设备对象,并把这个对象的指针存放在FsCtrlArray数组中(通过调用RegisterFileSystem函数)。接下来加载存储设备驱动程序,存储设备驱动程序会针对每个设备,比如物理硬盘、硬盘分区等,都会调用CreateDevice函数创建对应的存储设备对象。在创建存储设备对象的时候,需要指定设备的属性,比如可以是DEVICE_TYPE_STORAGE、DEVICE_TYPE_PARTITION等值。这样CreateDevice就可以根据这些属性,来做不同处理了。对于属性是DEVICE_TYPE_PARTITION的设备,CreateDevice函数会给所有已加载的文件系统一个机会,让文件系统尝试识别这些存储设备。
具体实现时,文件系统驱动程序需要实现一个函数—CheckPartition,这个函数被CreateDevice调用,用于检查存储设备是不是一个当前文件系统能够识别的分区。如果是,则CheckPartition需要加载这个分区,否则会继续检查系统中存在的下一个文件系统驱动程序。
与存储设备驱动程序的随机扇区读写功能一样,这个函数也是通过DeviceCtrl作为入口进行提供的。下面先看一下FAT32文件系统的CheckPartition函数的实现。为了解释方便,删除了部分无关代码:
这个函数有三个关键点,分别对应上述代码中用黑体标注的三个函数:
(1)InitFat32函数。这个函数试图识别一个分区对象(通过其参数传递)是不是一个合法的FAT32文件分区。如果是,则创建一个FAT32分区对象,然后返回。否则返回NULL。这个函数在实现的时候,就是根据FAT32文件系统规范,对分区的第一个扇区进行检查。在检查之前,首先调用DeviceReadSector(随机扇区读取,详细内容请参考本章12.3.5节)读取分区的第一个扇区,然后对第一个扇区进行分析。具体的分析过程不是很复杂,但是比较冗长,这里就不详细说明了。感兴趣的读者可以直接阅读源代码(位于[/kernel/fs/fat32.cpp]文件中),只要对FAT32文件系统规范熟悉了,阅读整个函数的代码会非常简单。返回的FAT32分区对象,是一个用于管理FAT32分区的数据结构,里面包含了分区的卷标、根目录的起始cluster编号、每个cluster的扇区数量、文件分配表的个数和起始cluster号等。具体的定义,位于文件[/kernel/fs/fat32.h]中。
(2)CreateDevice函数。若InitFat32成功地识别了一个FAT32分区,则CheckPartition需要创建一个文件卷对象,并把这个卷对象注册到系统中。需要注意的是,这个文件卷对象的设备扩展就是InitFat32返回的FAT32分区对象。
(3)AddFileSystem函数。成功创建FAT32文件卷对象之后,需要把文件卷对象注册到系统中,这样这个文件卷即可对系统用户可见。AddFileSystem函数即是把新创建的文件卷对象加入到IOManager的FsArray数组。
上述几个步骤完成之后,被FAT32文件系统识别的FAT32文件卷,即可呈现在系统中了。(www.xing528.com)
接下来再看CheckPartition函数是怎么被调用的。前面说过,这个函数是由CreateDevice函数调用的。CreateDevice针对每个属性是DEVICE_TYPE_PARTITION的设备,都会依次调用系统中已经注册的文件系统设备对象的CheckPartition函数。下面是CreateDevice函数的相关代码(CreateDevice函数比较长,我们只摘录与CheckPartition调用有关的代码进行说明):
CreateDevice函数针对设备的类型进行特殊处理。如果发现设备类型是一个分区对象,则会依次调用系统中已安装的文件系统驱动程序的CheckPartition函数,对分区进行识别。如果能够正确识别(即DeviceCtrl函数,实际上是CheckPartition函数,返回非0值),则结束循环,无需进一步检查。最后再强调一点,CheckPartition函数的说法可能不太合适,更严格说,应该是check partition命令。CheckPartition函数是FAT32文件系统(也是其他文件系统)驱动程序实现的一个分区检查函数,这个函数完全可以用其他的名字,只要实现分区检查和安装功能即可。这个函数被DeviceCtrl进一步封装,真正暴露给CreateDevice函数的,还是标准的驱动程序接口函数DeviceCtrl。
好了,对文件系统、存储设备等之间的相互协调,以及分区的识别和加载过程,相信读者已经搞明白了。在此进一步总结一下:
(1)文件系统驱动程序首先被加载。
(2)存储设备驱动程序继而被加载。在加载存储设备驱动程序的时候,会调用CreateDevice函数创建分区设备对象。
(3)CreateDevice根据设备属性(DEVICE_TYPE_PARTITION),调用系统中已经注册的文件系统的CheckPartition函数,对分区进行检查。
(4)文件系统的CheckPartition函数如果能够识别分区,则创建文件卷对象,并调用AddFileSystem注册到系统中。至此文件卷可见。
这不是一个复杂的过程,但具备比较好的扩展性,比如,这个框架可以很好地适应移动存储设备的即插即用功能。假设有一个USB接口的存储设备连接到了计算机,USB总线驱动程序就会感知这个事件,通知操作系统加载对应的驱动程序。在USB存储设备驱动程序的加载过程中,调用CreateDevice函数,创建USB存储设备对象。这时候一定要指定设备对象的属性为分区设备。这样系统中所有已安装的文件系统,比如FAT32、NTFS等的CheckPartition函数都将有机会被调用,试图识别USB设备的文件格式。一旦识别,就会被自动安装到系统中,从而对用户可见。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。