Windows CE的储存管理

本文介绍了Windows CE的储存管理架构,包括RAM-based和ROM-based档案系统、档案系统的类型、储存管理程序的分层结构等内容。同时分析了Ramdisk的安装过程,详细描述了装置驱动程序、分割区驱动程序及档案系统驱动程序的加载流程。

本章将介绍Windows CE 的储存管理。我们将本章内容分为两大部分,前半部会依序介绍 Windows CE的档案系统类型、 Windows CE储存管理结构和每一个层次、以及如何自行开发档案系统并加载之,后半部则以Ramdisk上的档案系统为例,实际分析储存管理相关的原始程序代码与数据型态。 5.1 Windows CE的储存管理架构 5.1.1 概述 Windows CE提供了三种类型的档案系统:RAM-based 档案系统、ROM-based 档案系统、以及用于支持ATA (Advanced Technology Attachment) 装置和SRAM卡等外围储存装置的FAT档案系统。其中,前两种档案系统属于 Windows CE的内建档案系统,后者属于可安装性档案系统。另外,嵌入式系统的开发人员也可以编写自己的档案系统,并在系统中注册使用。Windows CE提供了platform-independent API,不论是何种储存装置,所有对档案系统的存取都是透过Win32 API完成。 Windows CE预设的储存装置为最大可达256MB的RAM内存。RAM被分割为程序空间 (program memory) 和对象空间 (object store) 。程序空间和一般计算机系统中RAM的使用类似,用来储存执行程序及所需数据。对象空间则类似一般计算机系统中的硬盘,用来储存应用程序及档案。对象空间中存放的数据可分为三大类: (1) 档案系统 (file system) , (2) 注册信息 (registry) , (3) Windows CE数据库 (Windows CE database) 。Windows CE系统的电源管理机制,即使在关机状态时,仍旧有少量的电力从电池供应RAM,以保留储存在其中的数据。只有在电池电力完全耗尽时,RAM中储存的数据才会消失。 与对象空间相关的RAM-based档案系统和ROM-based档案系统,是 Windows CE预设支持的内建档案系统。除此之外,使用者还可以安装用于支持外围储存装置的档案系统,比如FAT、UDFS等等。对外围储存装置的存取,都是透过这种可安装性档案系统来完成的。另外,我们还可以将一个外围储存装置,分为多个volume(卷)并分别加载,其中每个volume可以使用不同的档案系统。Windows CE没有像Windows XP或Windows ME使用磁盘驱动器代号来表示volume,加载后的volume在Windows CE内以目录的形式呈现。 5.1.2 对象空间 如前所述,对象空间的数据分为三部分-档案系统、系统注册信息及数据库。然而,档案系统、数据库以及注册信息都不一定要储存在对象空间中,它们也可以被存放ROM或是外围储存装置中。数据的建立和使用,与实际的储存装置无关,只是依赖于储存的类型。 操作系统负责管理内存堆,在必要的时候会对档案进行压缩和解压缩。对象空间使用基于事务的数据管理机制。如果当数据写入对象空间时,发生了电源中断的情况,Windows CE会透过各种手段保证对象空间不被破坏,例如在系统重启后继续完成该写操作,或是恢复到电源中断前的状态。 在早期的Windows CE版本中,RAM档案系统和档案大小最大皆只能达16MB。到了Windows CE 3.0这个版本,RAM档案系统最大可达256MB,而档案大小最大也能达32MB。另外,对象空间中的对象数目也由原本的65,536增加到4,000,000左右。最新的Windows CE .NET允许档案最大可达4GB。 Windows CE为对象空间中的每个对象,都分配了一个唯一的对象识别码 (Windows CE object identifier, 简称CEOID) 。对象识别码的作用就是用于存取对象空间中的对象。在对象空间中,以下列出的各项都可以被定义为一个对象: 注册信息中的一个键 注册信息中的一个值 一个档案 档案数据中大小为4KB的一部分 (意即一个12KB的档案本身会对应一个档案对象,另外其所含的三个4KB的数据块也分别对应着一个对象) 数据库中的一项记录 (最多可达4KB数据) 数据库中一项记录的扩展信息 (也可达4KB数据) 一个数据库 数据库的一个volume (a database volume) 需要说明的一点是,Windows CE仅仅保证对象识别码在同一个volume中是唯一的,当有多个volume,可能在不同volume中发现相同对象识别码的对象。因此,Windows CE为每个数据库volume (database volume) 都分配了一个唯一的数据库识别码 (CEGUID)。数据库识别码和对象识别码的结合,就可以表示数据库volume中的某个对象了。 要对对象空间中的对象进行存取,第一步就是获得该对象的对象识别码。根据不同的对象类型,Windows CE提供了相对应的函式以获得该对象的对象识别码,参考表5.1。 表5.1 获得对象识别码函式 对象类型 获得对象识别码的方法 目录或档案 FindFirstFile和FindNextFile函式回传的WIN32_FIND_DATA结构的dwOID字段。GetFileInformationByHandle函式回传的BY_HANDLE_FILE_INFORMATION结构的dwOID字段。 数据库 CeCreateDatabaseEx2或CeFindNextDatabaseEx函式的回传值 数据库记录 CeSeekDatabaseEx、CeReadRecordPropsEx以及 CeWriteRecordProps函式的回传值。 已加载的数据库volume CeMountDBVol和CeEnumDBVolumes函式可以回传已挂载数据库volume中的数据库识别码。 有了对象识别码,就可以使用CeOidGetInfoEx函式得到该对象内的数据。CeOidGetInfoEx函式会回传一个CEOIDINFO结构 (结构原型列于程序代码5.1)。CEOIDINFO结构有一个wObjType字段说明了对象的类型。假如该字段的值为OBJTYPE_DATABASE,就指这是一个数据库对象。由wObjType可得知该对象是档案、目录、数据库还是数据库记录,同时也得知该对象的数据地址。例如,如果对一个数据库对象使用CeOidGetInfoEx的话,那么回传的类型值为OBJTYPE_DATABASE,而与之相关的数据库名称、类型识别码、记录的数目、数据库的大小以及排序方式等信息,都将保存在一个CEDBASEINFO结构中。 程序代码5.1 CEOIDINFO数据结构 typedef struct _CEOIDINFO { WORD wObjType; //Type of object // OBJTYPE_INVALID | There was no valid object // with this CEOID // OBJTYPE_FILE | The object is a file // OBJTYPE_DIRECTORY | The object is a directory // OBJTYPE_DATABASE | The object is a database // OBJTYPE_RECORD | The object is a record inside // a database WORD wPad; // dword alignment union { //This is a union CEFILEINFO infFile; //Valid for file objects CEDIRINFO infDirectory; //Valid for directory objects // @CESYSGEN IF FILESYS_FSDBASE CEDBASEINFO infDatabase; //Valid for database objects CERECORDINFO infRecord; //Valid for record objects // @CESYSGEN ENDIF }; } CEOIDINFO, PCEOIDINFO; 5.1.3 储存管理程序 储存管理程序 (Storage Manager) 负责管理外围储存装置所使用的档案系统和区块装置驱动程序 (Block Device Driver) 。储存管理程序的功能实作位在fsdmgr.dll模块,它由三部分组成,包括区块装置管理程序 (Block Driver Manager) 、分割区管理程序 (Partition Manager) 以及档案系统管理程序 (File System Driver Manager,或简称FSD Manager) 。所有对档案和volume的处理,都是经由储存管理程序完成的,储存管理程序在永久性储存介质的存取过程中,扮演了很重要的角色。 图5.1 储存管理程序结构图 Windows CE的储存管理程序是一个分层结构 ,其结构如图5.1所示,因此做Disk I/O必需经过储存管理程序的各个阶层。首先是过滤器层 (File System Filter) ,在将I/O请求交由档案系统驱动程序处理之前,在过滤器这一层可以完成一些加密、压缩和病毒扫描等性质的额外工作。接下来是档案系统驱动程序 (File System Driver) ,在这里对参数进行格式化,将文件名称转换为区块装置驱动程序可以识别的物理地址。在这之后,就可以经分割区管理程序,或直接交由区块装置驱动程序,完成实体的I/O操作了。 在储存管理程序的启动过程中,有很多信息是从系统的注册信息中得到的。例如,下面的注册信息会告知系统加载储存管理程序模块: [HKEY_LOCAL_MACHINE/System/StorageManager] "Dll"="fsdmgr.dll" 另外,每当系统检测到新的外围储存装置时,装置管理程序会自动加载支持该装置的区块装置驱动程序,之后向储存管理程序报告,该装置的配置信息在注册信息中的位置。储存管理程序就是根据这些信息,决定如何加载适当的分割区驱动程序 (partition driver) 和档案系统驱动程序。下面是所有装置都可使用的预设配置信息: [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles] "AutoMount"=dword:1 "AutoPart"=dword:0 "AutoFormat"=dword:0 "MountFlags"=dword:0 "DefaultFileSystem"="FATFS" "PartitionDriver"="mspart.dll" "Folder"="Mounted Volume" 其中AutoFormat表明是否对该装置进行自动格式化,AutoPart表明是否用该装置上的最大可用空间自动建立分割区,AutoMount表明是否自动挂载每个检测到的分割区,而MountFlags则指出了应如何对分割区进行挂载。 如果装置管理程序向储存管理程序,指定新装置的配置信息所在的位置,那么储存管理程序会使用该装置的指定配置信息。例如,硬盘的配置信息可能如下: [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/HDProfile] "Name"="IDE Hard Disk Drive" "Folder"="Hard Disk" 对于未提供一些配置信息,比如AutoFormat、AutoPart和AutoMount,则要使用系统提供的默认值。 当系统发现新的外围储存装置时,储存管理程序为其加载分割区驱动程序和档案系统驱动程序的程序如下: 1.装置管理程序负责加载区块装置的驱动程序, 2.由该装置发出一个通知,告知储存管理程序该装置的装置名称及其GUID, 3.储存管理程序根据注册信息中的配置信息,为该装置加载分割区驱动程序, 4.储存管理程序行举该装置上的所有分割区, 5.储存管理程序为每个分割区,加载档案系统驱动程序。 5.1.3.1 档案系统过滤器 档案系统过滤器和档案系统驱动程序类似,它们都有一组输出函式被CreateFile和 CreateDirectory这类的标准档案系统API映像到。二者不同之处在于,档案系统过滤器要提供HookVolume和UnhookVolume这两个函式,而档案系统驱动程序必须提供的是MountDisk和UnmountDisk这两个函式。档案系统驱动程序和档案系统过滤器的加载,都是透过档案系统管理程序完成的。 有关档案系统过滤器的信息,也是保存在注册信息中。档案系统管理程序需要到以下各处搜寻应加载的档案系统过滤器: [HKEY_LOCAL_MACHINE/System/StorageManager/AutoLoad/FileSystem /Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/ProfileName /FileSystem/Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/FileSystem/Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/Filters] 此处,FileSystem代表档案系统在注册信息中所使用的名称,例如FATFS、UDFS等等。如果在 [HKEY_LOCAL_MACHINE/System/StorageManager/AutoLoad/FileSystem /Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/ProfileName /FileSystem/Filters] 中出现了一个过滤器的话,那么所有使用该配置信息的档案系统,都将使用该过滤器。如果在 [HKEY_LOCAL_MACHINE/System/StorageManager/FileSystem/Filters] 中出现了一个过滤器的话,那么名为FileSystem的档案系统将使用该过滤器。如果一个过滤器出现在 [HKEY_LOCAL_MACHINE/System/StorageManager/Filters] 中,那么所有的档案系统都将使用该过滤器。下面是一个注册信息中有关过滤器信息的例子: [HKEY_LOCAL_MACHINE/System/StorageManager/.../Filters/FilterName] "dll"="filter.dll" "Order"=dword:x 其中dll的值,是该过滤器所对应的动态链接库,Order的值,则是建造过滤器堆栈时各过滤器之间的顺序。 5.1.3.2 档案系统管理程序与档案系统驱动程序 首先来看一下档案系统驱动程序 (File System Driver-FSD) 。档案系统驱动程序只适用于区块装置。我们知道,任何装置加载系统后,都需要对应的装置驱动程序的配合才能得以工作,对于区块装置而言,自然也少不了区块装置驱动程序。但是与其它装置不同的是,区块装置除了需要直接和硬件沟通的区块装置驱动程序之外,还需要一个档案系统驱动程序,这是因为,虽然区块装置驱动程序已经向上层提供了各种函式接口,透过这些接口就可以对区块装置进行初始化、读写数据区块等等各种操作了,但是这种接口的操作对象却是数据区块,如果直接将这种接口提供给使用者使用的话,使用者所见到的区块装置就是一个杂乱无章、毫无规律的数据区块集合,这对使用者而言非常地不方便。因此必需将这些散乱的数据区块,组织成档案的形式,并向使用者提供一个按名存取的档案系统接口,这就是档案系统驱动程序的主要功能。意即档案系统驱动程序建立在区块装置驱动程序模块之上,负责对区块装置的进一步抽象,把毫无规律的数据区块,组织成对使用者来说,更为方便易用的档案,并向上提供了档案操作的函式接口。档案系统程序与区块装置驱动程序间的关系如图5.2所示。 File 1 File 2 File k …… 档案系统驱动程序 区块装置驱动程序 B 1 B 2 B n …… 图5.2 档案系统驱动程序和区块装置驱动程序关系图 Windows CE支持大多数Windows XP或Windows ME使用的档案I/O函式,例如CreateFile、ReadFile、WriteFile和CloseHandle等Win32 API呼叫。同时,Windows CE仍然遵循使用句柄对档案进行存取的传统方法。由CreateFile函式回传,新建立或打开的档案句柄,之后的读写操作,都使用该句柄来决定档案操作的对象。当然,对读写操作来说,还需要一个指标,来指出在档案中进行读写的位置。 系统中所有的可安装档案系统,都是由档案系统管理程序负责管理的。每个可安装档案系统,都对应着一个档案系统驱动程序,并且以动态链接库 (Dynamic Link Library) 的形式提供,它有一组输出函式被映像到标准的档案系统Win32 API上。像CreateFile、ReadFile、WriteFile和CloseHandle这些档案系统API的功能,最后都是呼叫某个档案系统驱动程序的函式MyFSD_CreateFileW、MyFSD_ReadFile、MyFSD_WriteFile和MyFSD_CloseFile完成的。另外值得注意的一点是,档案系统驱动程序的动态链接库的名字,与该档案系统驱动程序的输出函式的前缀 (prefix) 是相同的。例如,如果档案系统名为MyFSD,那么对应的动态链接库就是MyFSD.dll,其输出函式的前缀为MyFSD_*。 在应用程序中存取可安装档案系统,使用的是标准的档案系统Win32 API。例如,当应用程序想要在某个储存装置上建立一个目录时,可以呼叫CreateDirectory。之后由档案系统管理程序对CreateDirectory中指明的路径加以识别,如果发现该路径指向一个使用可安装档案系统的装置,便会呼叫相对应的档案系统驱动程序-MyFSD.dll内的建立目录函式MyFSD_CreateDirectoryW。也就是说,应用程序呼叫CreateDirectory的结果,是导致档案系统管理程序,呼叫相对应档案系统驱动程序的函式MyFSD_CreateDirectoryW。 在Windows CE中,负责管理档案系统驱动程序的模块,称为档案系统管理程序 (File System Driver Manager) 。由于现代操作系统一般都支持多个档案系统并存,因此需要对系统中用到的各个档案系统加以管理,这就是档案系统管理程序的作用。从功能上讲,Windows CE中的档案系统管理程序与Linux中的虚拟档案系统VFS类似,如图5.3所示。首先,作为不同的档案系统,它们向上层提供的函式接口必然也是不同的。但是,如果为了使用系统中不同的档案系统,就必须记忆多套不同的函式接口,这对使用者来说是难以接受的。我们所希望的是向使用者提供一套CreateFile、ReadFile、WriteFile和CloseHandle这样的标准接口,为使用者隐藏掉不同的档案系统间的差异。这个任务就是由档案系统管理程序来完成的。在Windows CE中,使用者见到的并不是档案系统驱动程序所提供的接口,而是经过档案系统管理程序统一包装过的Win32 API函式接口。这种统一接口的作法虽方便了使用者,但另一方面却又不可避免的带来了系统复杂性。对使用者而言,使用者根本不用关心,他所使用的档案所在的volume到底使用何种档案系统,因为档案系统管理程序已经为使用者隐藏了其中的细节,但是在进行的实际的档案操作时,我们又必须要能够找出对应的档案系统,这也是由档案系统管理程序来完成的。在Windows CE中没有目前目录 (current directory) 的概念,所有对档案的存取都必需提供完整路径名称。在为外围储存装置安装档案系统驱动程序的同时,档案系统管理程序会为其注册一个标识性的目录名称。如此一来,当使用者对外围储存装置上的档案进行存取时,档案系统管理程序就可以根据使用者提供的完整目录名称,分析出该档案所在的volume、使用的档案系统等。 ….. Standard file system Win32 API 档案系统管理程序 FSD 1 FSD 2 FSD n 图5.3 档案系统管理程序与档案系统驱动程序关系图 5.1.3.3 分割区管理程序与分割区驱动程序 一个储存装置是由多个扇区 (sector) 组成的,每个扇区包含了一组连续的字节,所有扇区的大小都是相同的。储存装置可能被划分为多个逻辑分割区,每个分割区由一组连续的扇区组成。分割区之间不能相互重迭,否则操作系统将停止响应。没有被划分到任何分割区中的储存空间属于未分割区空间。分割区管理程序是储存管理程序的一部分,它透过呼叫分割区驱动程序来完成对这些分割区的管理、挂载 (mount) 与卸载 (unmount)。 同一储存装置上的不同分割区可以使用不同的档案系统,每个分割区所使用的档案系统类型是在建立或格式化时就确定了。在对该分割区进行挂载时,分割区管理程序会通知储存管理程序启动对应的档案系统。 Windows CE可以同时支持多个分割区驱动程序,但是对于一个指定的储存装置来讲只能使用一个分割区驱动程序,由它为分割区管理程序解释该装置上的所有分割区。 如前所述,分割区是对储存装置进行逻辑划分的结果。在Windows CE中,多个分割区既可以使用相同的档案系统,也可以使用不同的档案系统。分割区管理程序透过呼叫分割区驱动程序完成对分割区的操作。分割区驱动程序也是以动态链接库的形式提供的,在Windows CE中预设的分割区驱动程序是MSPart.dll,它输出了一组API。使用者也可以编写自己的分割区驱动程序,只要该分割区驱动程序支持与MSPart.dll相同的API即可。为了使自己编写的分割区驱动程序MyPart.dll生效,还需要在注册信息的配置信息中填写"PartitionDriver"="MyPart.dll",这样就可以使用自己的分割区驱动程序了。表5.2说明了一个分割区驱动程序应该提供的所有函式。 表5.2 分割区驱动程序提供的函式 函式名 功能 PD_ClosePartition 结束I/O操作,关闭分割区 PD_CloseStore 关闭储存装置上的volume PD_CreatePartition 在储存装置的volume上建立分割区 PD_DeletePartition 从储存装置的volume上删除分割区 PD_DeviceIoControl 向指定的装置驱动程序直接发送句柄,完成相对应的操作 PD_FindPartitionClose 关闭列举分割区时使用的搜寻句柄 PD_FindPartitionNext 在列举分割区时得到下一个分割区的信息。 PD_FindPartitionStart 开始列举分割区。 PD_FormatPartition 在储存装置的volume上格式化一个分割区 PD_FormatStore 对储存装置进行格式化操作 PD_GetPartitionInfo 得到指定分割区的属性信息 PD_GetStoreInfo 得到储存装置的信息 PD_IsStoreFormatted 查询储存装置是否已被格式化 PD_OpenPartition 打开分割区以进行I/O操作 PD_OpenStore 打开储存装置上的volume PD_RenamePartition 重新命名储存装置上的一个分割区 PD_SetPartitionAttrs 设定指定分割区的属性 5.1.3.4 区块装置管理程序 区块装置的读写操作都是以区块 (block) 为单位,而不是以字节为单位进行的。区块的大小都是固定的,常用的有512 字节、1 KB、2 KB和4 KB。区块装置驱动程序通常都会向上层提供一个串流 (stream) 的接口,应用程序透过使用CreateFile和ReadFile这些标准的档案系统API对区块装置进行存取。 当应用程序呼叫ReadFile对区块装置进行存取时,档案系统会先找到对应的逻辑区块,检查这些逻辑区块是否保存在缓存区 (buffer) 中。如果在的话,则可直接由缓存区中读取数据,否则就需要透过区块装置驱动程序对实际的区块装置进行读取操作了。 当一个区块装置驱动程序加载系统中时,它会通知储存管理程序这一个加载事件,作为对此事件的响应。储存管理程序将负责查询与此区块装置驱动程序相关的注册信息,并通知分割区管理程序加载对应的装置。 5.1.4 档案系统驱动程序的建立与加载 Windows CE提供了一套模板式方法,使得开发人员可以轻松地开发一套新的档案系统。新开发的档案系统既可以充分利用储存装置的特性,也能够限制储存装置的档案操作。 5.1.4.1 档案系统驱动程序的建立 档案系统驱动程序为一个有一组输出函式被映射到Win32 API上的动态链接库。当应用程序呼叫Win32 API来操作档案时,如果发现要操作的档案位于某一档案系统的volume上,那么档案系统管理程序会将该系统呼叫,映像到该档案系统相对应的输出函式,藉由该档案系统驱动程序的输出函式来操作档案。 Windows CE还提供了一个档案系统驱动程序的开发模板,开发人员只要提供各种所需的输出函式就可以了。通常,一个档案系统驱动程序应该提供所有的输出函式,但这并不是必要的。例如,如果开发人员所开发的档案系统驱动程序不允许使用者建立或删除目录,只要在档案系统驱动程序中不提供CreateDirectory和RemoveDirectory这两个输出函式即可。档案系统驱动程序没有提供的输出函式,将由档案系统管理程序自动填入stub function代替,一般stub function只会回传ERROR_NOT_SUPPORTED。因此,在前面的例子中,当应用程序尝试建立或删除目录时就会失败。 除了提供Win32 API需映像到的输出函式,档案系统驱动程序还要提供FSD_MountDisk和FSD_UnmountDisk这两个函式。当系统要安装或移除某个档案系统驱动程序时,装置管理程序会呼叫这两个函式。 如果要开发一个名为MyFSD的档案系统驱动程序,那么该档案系统驱动程序的动态链接库必须命名为MyFSD.dll。该动态链接库中要包含了数据类型的定义、标头档案 (header file) 以及MyFSD所支持的输出函式。 PVOLUME、PFILE和PSEARCH这三种数据预设都是DWORD类型的,分别用于储存已载入的volume、档案和搜寻句柄的信息。然而,我们也可以在MyFSD.dll中对这三种数据类型重新进行定义,使它们包含我们所需要的信息。例如,我们可以将它们定义为指向某种结构的指针。需要注意的一点是,这种重新定义必须在纳入标头档案Fsdmgr.h之前进行,否则将使用预设的定义。 编译生成MyFSD.dll的程序代码中必须包含Fsdmgr.h,该档案中包含了所有的输出函式原型宣告。前面已经讲过,在纳入Fsdmgr.h之前可以对PVOLUME、PFILE和PSEARCH这三种数据类型重新定义。除此之外,我们还必须定义一个FSDAPI常数,此常数决定了档案系统名称、所有函式前缀以及最后生成的动态链接库名称。程序代码5.2说明了如何在MyFSD.dll中纳入Fsdmgr.h。 程序代码5.2 在MyFSD.dll中纳入Fsdmgr.h // This defines a type that is a pointer to a structure containing // two DWORDs. typedef struct { DWORD first_field; DWORD second_field; } TWODWORDS, *PTWODWORDS; // In this example, only PSEARCH is redefined. // PFILE and PVOLUME will default to being defined as DWORDs. // You can define these types to be different types or the same type, based on your FSD needs. #define PSEARCH PTWODWORDS // Now define the name of the file system. #define FSDAPI MyFSD #include 每个MyFSD.dll所提供的输出函式名称,都必须以档案系统驱动程序的名称为开头,在本例中为MyFSD_。不过也有特例,那就是FSD_MountDisk和FSD_UnmountDisk这两个函式,不必使用档案系统驱动程序的名称作为前缀。档案系统管理程序会检查这两个函式是否存在,来决定一个档案系统驱动程序是否合法。程序代码5.3说明了如何在模块定义档案 (.def) 中,定义MyFSD.dll所提供的函式。 程序代码5.3 定义MyFSD.dll所提供函式的模块定义档案内容 LIBRARY MyFSD DESCRIPTION 'My File System for Windows CE' EXPORTS MyFSD_MountDisk MyFSD_UnmountDisk FSD_MountDisk=MyFSD_MountDisk FSD_UnmountDisk=MyFSD_UnmountDisk MyFSD_CreateDirectoryW MyFSD_RemoveDirectoryW MyFSD_GetFileAttributesW MyFSD_SetFileAttributesW MyFSD_DeleteFileW MyFSD_MoveFileW MyFSD_DeleteAndRenameFileW MyFSD_GetDiskFreeSpaceW MyFSD_Notify MyFSD_RegisterFileSystemFunction MyFSD_FindFirstFileW MyFSD_FindNextFileW MyFSD_FindClose MyFSD_CreateFileW MyFSD_ReadFile MyFSD_ReadFileWithSeek MyFSD_WriteFile MyFSD_WriteFileWithSeek MyFSD_SetFilePointer MyFSD_GetFileSize MyFSD_GetFileInformationByHandle MyFSD_FlushFileBuffers MyFSD_GetFileTime MyFSD_SetFileTime MyFSD_SetEndOfFile MyFSD_DeviceIOControl MyFSD_CloseFile MyFSD_CloseVolume 这个我们假想的档案系统驱动程序MyFSD,提供了所有的档案系统函式,但是开发人员也可以在档案系统驱动程序中不提供某些输出函式。对于这些缺少的函式,档案系统管理程序将自动以回传ERROR_NOT_SUPPORTED的stub function代替。 5.1.4.2 档案系统驱动程序的加载 当安装已注明使用某档案系统的外围储存装置时,该档案系统驱动程序会被自动加载。当外围储存装置加载系统时,储存管理程序会从注册信息中,读取与其相关的注册信息,其中定义了将要使用的档案系统驱动程序。在此我们仍假设该档案系统驱动程序为MyFSD。外围储存装置加载系统后,储存管理程序会从装置管理程序接收到一个通知,其中含有该装置的装置名称及其GUID等信息。其中,GUID的作用非常简单,透过检查装置的GUID,就可以确定新发现的装置是否为区块装置,因为只有区块装置才会用到档案系统。之后储存管理程序会根据从注册信息中得到的配置信息,为该装置加载合适的分割区驱动程序,并为每个分割区加载对应的档案系统。储存管理程序在加载档案系统驱动程序时,会检查MyFSD是否含有FSD_MountDisk和FSD_UnmountDisk这两个函式,只有找到了FSD_MountDisk和FSD_UnmountDisk,才能说明这是一个合法的档案系统驱动程序,也才会进行后续的工作。 之后,档案系统管理程序会呼叫MyFSD.dll的函式FSD_MountDisk。FSD_MountDisk负责根据MBR (Master Boot Record) 中的DPT (Disk Partition Table),以及扩展分割区连结串行中的信息找到装置上所有的volume,并呼叫FSDMGR_RegisterVolume向档案系统管理程序注册每个volume。所有这些注册过的volume,就会以目录的形式显示出来,应用程序也可以透过档案系统函式对它们进行存取了,往后当使用者透过档案系统Win32 API对该volume上的档案进行存取时,档案系统管理程序就可以透过完整目录名称中的目录名称,找到对应的档案系统驱动程序了。与此类似,当档案系统驱动程序的输出函式MyFSD_UnmountDisk被呼叫时,装置上的所有volume会被注销。 5.1.4.3 档案系统管理程序的作用 档案系统管理程序的功能是由Fsdmgr.dll这个模块提供的,它负责管理和沟通系统中所有可安装档案系统。档案句柄的建立、volume的注册、所有必需函式的安装、档案系统Win32 API到可安装档案系统输出函式的映像,所有这些都是由档案系统管理程序完成的。当应用程序存取某个volume上的目录和档案时,档案系统管理程序会负责呼叫相对应的档案系统输出函式。 应用程序是透过档案系统Win32 API存取可安装档案系统的。例如,当应用程序要在装置上建立一个目录时,它会呼叫CreateDirectory函式。之后,再由档案系统管理程序将建立目录的工作,交由该装置所使用的档案系统驱动程序来完成。 在开发档案系统驱动程序时,可以使用档案系统管理程序提供的下列函式: FSDMGR_CreateFileHandle FSDMGR_CreateSearchHandle FSDMGR_DeregisterVolume FSDMGR_GetDiskInfo FSDMGR_GetVolumeName FSDMGR_ReadDisk FSDMGR_ReadDiskEx FSDMGR_RegisterVolume FSDMGR_WriteDisk FSDMGR_WriteDiskEx 这些函式实作了档案系统驱动程序与操作系统和装置驱动程序的双重隔离。除了以上函式之外,Fsdmgr.dll也为 CreateFile和FindFirstFile这些函式提供了对应的函式。这些函式的原型都是在Fsdmgr.h中定义。 5.2 实际分析 5.2.1 概述 对于任何新加载系统的外围储存装置而言,首先要做的就是将其纳入系统的管理,然后才使用该装置。从底层区块装置驱动程序的加载,到分割区驱动程序的加载,以及档案系统驱动程序的加载,最后得以将区块装置及其使用的各种驱动程序,纳入装置管理程序和储存管理程序的管理当中,这是一个由下而上的过程。而透过统一的档案系统Win32 API接口到对应的档案系统驱动程序,再经由分割区驱动程序或直接交给装置驱动程序,完成最后的档案操作,这则是一个由上而下的过程。前者就是将外围储存装置纳入系统管理的过程,后者则是对外围储存装置的使用过程,本段落的重点是分析将外围储存装置纳入系统管理的过程。 当系统发现一个新的装置时,首先,装置管理程序会负责为其加载相对应的装置驱动程序,并在装置管理程序中建立相对应的数据结构,记录与该装置相关的各种信息。之后,如果发现新加载的装置是区块装置的话,还要由储存管理程序为其加载档案系统驱动程序。这时,装置管理程序会将一些重要的信息告知储存管理程序,由它来完成后面的工作。储存管理程序首先会根据注册信息中的配置信息,为区块装置加载分割区驱动程序,分割区驱动程序的主要作用就是定位区块装置上的分割区。储存管理程序会为装置加载所需的档案系统驱动程序。在这一过程中,储存管理程序会为装置上的volume,注册一级目录的入口项,将来使用者透过完整路径名称对档案进行存取时,就是由其中的一级目录名找到相对应的档案系统的。这一过程大致如下: 1.装置管理程序负责加载区块装置的驱动程序, 2.由该装置发出一个通知,告知储存管理程序该装置的装置名称及其GUID等信息, 3.储存管理程序根据配置信息,为该装置加载分割区驱动程序, 4.储存管理程序行举该装置上的所有分割区, 5.储存管理程序为每个分割区加载档案系统驱动程序。 下面我们先对这一过程中所使用到的数据结构做介绍,然后再详细讲说上述储存管理程序分层结构的建立过程,以Ramdisk的安装过程为例,看一看在实际的程序代码中这一过程是如何实作的。 5.2.2 重要的数据结构 每当系统中出现新的外围储存装置时,储存管理程序都会为其建立一系列的数据结构,用来维护与该装置有关的信息。这些数据结构包括CStore、DriverState和DSK等等,它们都是用来描述一个装置的。对于装置上的分割区,储存管理程序中也有一套用于维护分割区信息的数据结构,包括CPartition、PartState和VOL等等。 CStore是一个类别,它描述了一个外围储存装置的一些基本信息,由储存管理程序负责建立,其中一些成员变量的含义如下: m_szDeviceName:装置名称,由装置管理程序告知; m_DeviceGuid:装置的GUID,也是由装置管理程序告知的; m_szRootRegKey:此装置的配置信息在注册信息中的位置; m_pPartDriver:装置所使用的分割区驱动程序; m_pPartitionList:装置上的CPartition分割区连结串行; m_dwStoreId:由分割区管理程序维护的与此装置对应的DriverState结构的指针; m_pNextStore:指向g_pStoreRoot队列中下一个CStore。 DriverState由分割区管理程序负责建立并维护,记录了一些与分割区管理程序有关的装置细节信息,其中一些成员变量的含义如下: pPartState:装置上的PartState分割区连结串行; pSearchState:指向SearchState结构组成的连结串行; diskInfo:装置参数,包括CHS值、每个扇区所含的字节数等等; snExtPartSector:装置上扩展分割区的起始扇区; snExtPartEndSec:扩展分割区的结束扇区。 CPartition也是一个类别,用于描述装置上的分割区,由储存管理程序负责建立并维护,其中一些成员变量的含义如下: m_dwPartitionId:由分割区管理程序维护,与此分割区对应的PartState的句柄; m_dwStoreId:与此分割区所在的装置对应的DriverState结构的指针; m_szPartitionName:分割区的名称; m_szFileSys:此分割区所使用的档案系统的名称; m_pDsk:指向由档案系统驱动程序负责维护,与此装置相关的DSK结构; m_hFSD:档案系统驱动程序对应的动态链接库的句柄; m_pNextPartition:指向同一装置上的下一个CPartition物件。 PartState是分割区管理程序为维护分割区信息所使用的数据结构,与DriverState密切相关,其中一些成员变量的含义如下: cPartName:分割区的名称; snStartSector:该分割区的起始扇区号; snNumSectors:此分割区所占用的扇区数,即分割区大小; NextPartState:指向同一装置上的下一个PartState结构; pState:指向此分割区所属装置的DriverState结构。 CPartDriver是一个类别,用于描述一个分割区驱动程序,其成员变量m_hPartDriver记录了该分割区驱动程序对应的动态链接库的句柄,其它的成员变量均为函式指针,一一对应于分割区驱动程序的输出函式。以上这些数据结构的关系如图5.4所示: 图5.4 CStore, DriverState, CPartition, PartState, DriverState数据结构关系图 下面再来看一下由档案系统管理程序建立并维护的FSD、DSK和VOL这三个数据结构。 FSD结构用来描述一个档案系统驱动程序或是一个过滤器,其中一些成员变量的含义如下: hFSD:档案系统驱动程序或过滤器对应的动态链接库句柄; pfnMountDisk:指向档案系统驱动程序函式FSD_MountDisk的指标; pfnUnmountDisk:指向档案系统驱动程序函式FSD_UnmountDisk的指标; pfnHookVolume:指向过滤器函式FSD_HookVolume的指标; pfnUnhookVolume:指向过滤器函式FSD_ UnhookVolume的指标; szFileSysName:档案系统或过滤器的名称; szRegKey:档案系统或过滤器的相关信息在注册信息中的位置; wsFSD:档案系统驱动程序函式的前缀; apfnAFS、apfnFile和apfnFind:记录了所有输出函式的地址。 每个档案系统驱动程序和过滤器除了对应一个FSD结构以外,还会对应一个DSK结构,其中一些成员变量的含义如下: pVol:指向由VOL结构双向连结串行的标头 (header); pFSD:指向对应的FSD结构; fdi:装置参数信息,包括CHS值、每个扇区所含的字节数等等; pDeviceIoControl:指向DeviceIoControl函式的指标。 VOL结构用于描述一个volume,也就是一个分割区,其中一些成员变量的含义如下: pDsk:指向此volume所使用的最上层档案系统,或过滤器所对应的DSK结构; dwVolData:由FSDMGR_RegisterVolume提供的某一档案系统驱动程序中使用的VOLUME结构的指针; iAFS:由FSDMGR_RegisterVolume获得的AFS号。 5.2.3 相关程序代码的分析 首先看一下Windows CE与档案系统相关的原始程序代码组织。在下面的说明中,%_WINCEROOT% 代表Windows CE所安装的根目录。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/FSD:三种档案系统驱动程序的原始程序代码,包括FAT、RELFSD以及UDFS。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE:储存管理程序的原始程序代码。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/DOSPART:MSPart.dll的原始程序代码。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/FSDMAIN:档案系统管理程序的原始程序代码。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV:档案系统管理程序使用的一些service routine及其提供的API。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREAPI:储存管理程序提供的API的封装。 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR:储存管理程序的启动程序代码,CStore和CPartition等类的原始程序代码。 另外,与Ramdisk相关的程序代码位于以下两个目录: %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/BLOCK/RAMDISK/DRIVER:Build此目录下的原始程序代码可以得到Ramdisk的驱动程序ramdisk.dll。 %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/BLOCK/RAMDISK/LOADER:Build此目录下的原始程序代码可以得到Ramdisk的安装程序ceramdrv.exe,该程序用于启动Ramdisk。 下面我们就以Ramdisk的安装过程为例,说明当系统中出现新的外围储存装置时,其装置驱动程序、分割区驱动程序以及档案系统驱动程序是如何被加载系统的,以及系统是如何透过装置管理程序和储存管理程序来管理新发现的装置。 在系统的启动过程中会自动加载两个模块,一是filesys.exe,与对象空间相关的RAM-based和ROM-based 档案系统的功能就是由该模块实作的;另一个是fsdmgr.dll,当新的外围储存装置加载系统时,由它负责加载FAT这种可安装的档案系统。系统在加载fsdmgr.dll这一模块时会呼叫初始化函式Init,而在Init函式的InitStorageManager会开始建立储存管理程序分层结构。在InitStorageManager中,首先会呼叫CreateAPISet和RegisterAPISet注册储存管理程序提供的API,以负责建立g_hPNPUpdateEvent事件。接下来呼叫CreateMsgQueue函式建立了一个讯息队列g_hPNPQueue,由于msgopts.bReadAccess的值被设为TRUE,因此对Storage Manager而言只能对该讯息队列 (message queue) 进行读取的操作。之后是启动用于支持随插即用的PNPThread。在此thread中,首先由GetProcAddress得到RequestDeviceNotifications和StopDeviceNotifications这两个函式在coredll.dll中的入口地址,然后透过呼叫RequestDeviceNotifications,告知装置管理程序将发现新的区块装置的通告写入讯息队列g_hPNPQueue中。这些准备工作完成后,PNPThread将进入一个无穷循环,每隔一定时间对g_hPNPQueue和g_hPNPUpdateEvent进行一次轮询。如果装置管理程序发现新装置,会透过WriteMsgQueue向讯息队列中写入含有新装置信息 (GUID,装置名称等) 的DEVDETAIL结构。这时,在PNPThread中就可以透过ReadMsgQueue将此信息读出,并呼叫MountStore来进行接下来的加载工作了。程序代码5.4为InitStorageManager、PNPThread函式的原始程序代码。 程序代码5.4 InitStorageManager、PNPThread函式原始程序代码 ***%_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/storemain.cpp Line 608 *** BOOL InitStorageManager () { DWORD dwThreadId = 0; int iAFS = INVALID_AFS; MSGQUEUEOPTIONS msgopts; iAFS = RegisterAFSName (L"StoreMgr") ; if (iAFS != INVALID_AFS && GetLastError () == 0) { g_hSTRMGRApi = CreateAPISet("PFSD", ARRAY_SIZE(apfnSTGMGRAPIs), (const PFNVOID*) apfnSTGMGRAPIs, asigSTGMGRAPIs); if (!RegisterAFSEx(iAFS, g_hSTRMGRApi, (DWORD)1, AFS_VERSION, AFS_FLAG_HIDDEN)) { DEBUGMSGW(ZONE_INIT, (DBGTEXTW("STOREMGR:InitStoreMgr failed registering secondary volume/r/n"))); DeregisterAFSName(iAFS); goto Fail; } } else { goto Fail; } // 建立API set g_hFindStoreApi = CreateAPISet("FSTG", ARRAY_SIZE(apfnFindStoreAPIs), (const PFNVOID *) apfnFindStoreAPIs, asigFindStoreAPIs); g_hFindPartApi = CreateAPISet("FPRT", ARRAY_SIZE (apfnFindPartAPIs), (const PFNVOID *) apfnFindPartAPIs, asigFindPartAPIs); g_hStoreApi = CreateAPISet("STRG", ARRAY_SIZE(apfnSTGAPIs), (const PFNVOID *) apfnSTGAPIs, asigSTGAPIs); g_hPNPUpdateEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 建立讯息队列,储存管理程序只能对该队列进行读取操作 msgopts.dwSize = sizeof(MSGQUEUEOPTIONS); msgopts.dwFlags = 0; msgopts.dwMaxMessages = 0; //? msgopts.cbMaxMessage = sizeof(g_pPNPBuf); msgopts.bReadAccess = TRUE; g_hPNPQueue = CreateMsgQueue(NULL, &msgopts); if (!g_hFindPartApi || !g_hFindStoreApi || !g_hStoreApi || !g_hPNPUpdateEvent || !g_hPNPQueue) goto Fail; // 注册API set RegisterAPISet (g_hFindStoreApi, HT_FIND | REGISTER_APISET_TYPE); RegisterAPISet (g_hFindPartApi, HT_FIND | REGISTER_APISET_TYPE); RegisterAPISet (g_hStoreApi, HT_FILE | REGISTER_APISET_TYPE); InitializeCriticalSection (&g_csStoreMgr); // 启动PNP执行绪,随时准备加载新发现的外围储存装置 if (g_hPNPThread = CreateThread(NULL, 0, PNPThread, NULL, 0, &dwThreadId)) { } else { goto Fail; } return TRUE; Fail: CloseAllGlobalHandles(); return FALSE; } *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/storemain.cpp Line 493 *** DWORD PNPThread (LPVOID lParam) { extern const TCHAR *g_szSTORAGE_PATH; // = L"System//StorageManager"; extern const TCHAR *g_szReloadTimeOut; // = L"UnloadDelay"; DWORD dwFlags, dwSize; // HKEY hDevKey; HANDLE hReg; DEVDETAIL * pd = (DEVDETAIL *) g_pPNPBuf; GUID guid = {0}; HANDLE pHandles[2]; TCHAR szGuid[MAX_PATH]; HMODULE hCoreDll; DWORD dwTimeOut = INFINITE, dwTimeOutReset = DEFAULT_TIMEOUT_RESET; PSTOPDEVICENOT pStopDeviceNotification = NULL; PREQUESTDEVICENOT pRequestDeviceNotification = NULL; pHandles[0] = g_hPNPQueue; pHandles[1] = g_hPNPUpdateEvent; HKEY hKey; // 从注册信息[HKEY_LOCAL_MACHINE/System/StorageManager]处读取PNPUnloadDelay的值 if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, g_szSTORAGE_PATH, 0, 0, &hKey) ) { if ( !FsdGetRegistryValue(hKey, g_szReloadTimeOut, &dwTimeOutReset)) { dwTimeOutReset = DEFAULT_TIMEOUT_RESET; } } DEBUGMSG(ZONE_INIT, (L"STOREMGR: Using PNP unload delay of %ld/r/n", dwTimeOutReset)); AutoLoadFileSystems(); // 由coredll.dll中得到RequestDeviceNotifications和StopDeviceNotifications两个函式的地址 hCoreDll = (HMODULE) LoadLibrary(L"coredll.dll"); if (hCoreDll) { pRequestDeviceNotification = (PREQUESTDEVICENOT) GetProcAddress(hCoreDll, L"RequestDeviceNotifications"); pStopDeviceNotification = (PSTOPDEVICENOT) GetProcAddress(hCoreDll, L"StopDeviceNotifications"); } FreeLibrary (hCoreDll); if (pRequestDeviceNotification && pStopDeviceNotification) { DEBUGMSG (ZONE_INIT, (L"STOREMGR: PNPThread Created/r/n")); while ( !IsAPIReady (SH_DEVMGR_APIS)) { Sleep(1000); } // 告知系统将新发现的区块装置的信息写入刚刚建立的讯息队列g_hPNPQueue中 hReg = pRequestDeviceNotification (&BLOCK_DRIVER_GUID, g_hPNPQueue, TRUE); while (TRUE) { DWORD dwWaitCode; dwWaitCode = WaitForMultipleObjects (2, pHandles, FALSE, dwTimeOut); if (dwWaitCode == WAIT_OBJECT_0) { // 讯息队列有新的讯息写入,读取有关新装置的信息 if (ReadMsgQueue (g_hPNPQueue, pd, sizeof(g_pPNPBuf), &dwSize, INFINITE, &dwFlags)) { FsdStringFromGuid (&pd->guidDevClass, szGuid); DEBUGMSG(ZONE_INIT,(L"STOREMGR: Got a plug and play event %s Class (%s) Attached=%s!!!/r/n", pd->szName, szGuid, pd->fAttached ? L"TRUE":L"FALSE")); // 检查新发现的装置是否为区块装置 if(memcmp(&pd->guidDevClass, &BLOCK_DRIVER_GUID, sizeof(GUID)) == 0) { // 如果新的区块装置的状态为Attached则继续后面的加载工作 if (pd->fAttached) { // 将装置名称及其GUID作为参数传给MountStore MountStore ( pd->szName, pd->guidDevClass); …… // 后面略去 对于Ramdisk来说,当我们执行安装程序ceramdrv.exe后,系统便会发现这块虚拟的外围储存装置,且装置管理程序加载区块装置驱动程序ramdisk.dll。Ramdisk所使用的装置驱动程序等配置信息,是其由安装程序ceramdrv.exe写入注册信息的 (见图5.5)。其中dll的键值 (key value) 就是Ramdisk的装置驱动程序,而IClass的键值{A4E7EDDA-E575-4252-9D6B-4195D48BB865}即为Windows CE中标识区块装置所用的GUID,它将作为参数之一传给MountStore。另外还有很重要的一个键是Profile,它的键值说明了Ramdisk的配置信息所在的位置,在后面我们还会提到。 图5.5 Ramdisk的注册信息 另外,在成功为其加载区块装置驱动程序之后,装置管理程序会将Ramdisk作为已经启动的装置填入注册信息 (见图5.6),并透过WriteMsgQueue将Ramdisk的装置名称及其GUID等信息写入讯息队列。可以看到,与Ramdisk有关的装置基本信息位于[HKEY_LOCAL_MACHINE/Drivers/Builtin/Ramdisk],也就是图5.6所示的注册信息位置;另外,Name的键值即为Ramdisk的装置名称,也就是说DSK1: 将作为参数之一传给MountStore。 图5.6 启动装置的注册信息 进入MountStore之后,首先由以g_pStoreRoot为标头的连结串行,搜寻是否已经存在具有相同装置名称的store。如果发现了具有相同装置名称的相符项,并且该store的标志位STORE_FLAG_DETACHED处于复位状态 (说明装置可用) ,则表明该装置已经加载到系统中,不再做其它动作。相反,如果在连结串行中未找到相符项,或虽然找到但是该store的标志位STORE_FLAG_DETACHED处于置位状态 (说明装置不可用) ,则继续接下来的工作。以装置管理程序告知的装置名称及其GUID建立一个CStore对象,然后呼叫CStore::MountStore (BOOL bMount) 完成实际的加载工作。如果以g_pStoreRoot为标头的连结串行中存在标志位STORE_FLAG_DETACHED置位的store,则参数bMount为FALSE,否则为TRUE。 在CStore::MountStore函式中,首先呼叫OpenDisk。由于从装置管理程序那里我们已经知道了装置名称,因此可以透过CreateFileW函式建立装置句柄,之后还可以由DeviceIoControl函式得到有关该装置的详细信息。在呼叫DeviceIoControl时如果dwIoControlCode值为DISK_IOCTL_GETINFO的话,那么就可以得到包含CHS等装置参数的DISK_INFO结构,并将其存在Cstore的成员变量m_si中;如果dwIoControlCode值为IOCTL_DISK_DEVICE_INFO,那么可以得到有关该装置的一些额外信息,这些信息以STORAGEDEVICEINFO结构表示,并且存在Cstore的成员变量m_sdi中。STORAGEDEVICEINFO结构中很重要的一项信息,就是该装置的Profile在注册信息中的位置,这决定了我们将从哪里得到有关该装置的配置信息。之后,我们就可以呼叫GetStoreSettings,由注册信息的Profile中读出该装置的配置信息。例如,是否支持AUTOMOUNT、AUTOFORMAT以及AUTOPART、该装置所使用的预设档案系统、为该装置加载哪个partition driver等等。 得到这些配置信息后,首先建立一个CPartDriver对象,并透过LoadPartitionDriver,加载我们从注册信息中得知的该装置所使用的分割区驱动程序。另外LoadPartitionDriver还会完成刚加载的分割区驱动程序中,输出函式到CPartDriver中函式指标的映射。如果在注册信息中未提供有关分割区驱动程序的加载信息,则系统使用预设的分割区驱动程序 (MSPart.dll) 。接下来呼叫的OpenStore函式会映像到刚刚加载的分割区驱动程序的PD_OpenStore函式,它会根据由装置启动扇区读入的磁盘分割区表,建立并维护分割区管理程序将用到的DriverState (代表装置) 、PartState (代表装置上的分割区) 和SearchState (用于go through装置上的分割区) 等数据结构。该函式回传时,m_dwStoreId值为与装置相对应的DriverState结构的指针。之后的GetStoreInfo被映像为PD_GetStoreInfo,由于刚刚得到的m_dwStoreId是指向DriverState结构的指针,因此可以从分割区管理程序保存的数据结构中读出我们这里所需的信息,并把这些信息组织在一个PD_STOREINFO结构中。接下来检查m_dwStoreId所指向的装置是否已经格式化,如果该装置尚未被格式化,并且前面由注册信息读出的配置信息表明该装置支持AUTOFORMAT,则在此透过呼叫FormatStore函式对该装置进行自动格式化。 接下来的GetPartitionCount透过PD_FindPartitionStart、PD_FindPartitionNext以及PD_FindPartitionClose这三个函式的扫视该装置的分割区,由此统计出装置上的分割区个数。如果统计结果为零,说明该装置尚未分割。这时如果该对应的注册信息表明该装置支持AUTOPART,则在此以装置上最大储存空间自动建立并格式化为一个分割区,这是透过呼叫CreatePartition和FormatPartition这两个函式完成的。最后一步是呼叫LoadExistingPartitions,它会像GetPartitionCount一样透过所加载的分割区驱动程序函式PD_FindPartitionStart、PD_FindPartitionNext以及PD_FindPartitionClose go through装置的所有分割区,但这次go through的目的不是为了统计分割区个数,而是呼叫LoadPartition依次载入每个分割区。下述的程序代码5.5依序列出CStore::MountStore、 CStore::OpenDisk、GetStoreSettings的原始程序代码。 程序代码5.5 CStore::MountStore、CStore::OpenDisk、GetStoreSettings原始程序代码 *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp Line 322 *** DWORD CStore::MountStore (BOOL bMount) { DWORD dwError = ERROR_GEN_FAILURE; // 由装置名称DSK1:建立Ramdisk的装置句柄,并得到装置的相关信息 if (ERROR_SUCCESS == (dwError = OpenDisk())) // 得到Ramdisk的配置信息 if (GetStoreSettings ()) { m_pPartDriver = new CPartDriver (); // 加载预设的分割区驱动程序mspart.dll if(ERROR_SUCCESS == (dwError = m_pPartDriver->LoadPartitionDriver (m_szPartDriver))) { // 由mspart.dll的PD_OpenStore为Ramdisk在分割区管理程序建立相对应的数据结构 if(ERROR_SUCCESS==(dwError=m_pPartDriver->OpenStore (m_hDisk, &m_dwStoreId))) { DEBUGMSG (1, (L"Opened the store hStore=%08X/r/n", m_dwStoreId)); m_si.cbSize = sizeof (PD_STOREINFO); m_pPartDriver->GetStoreInfo ( m_dwStoreId, &m_si); DEBUGMSG(1, (L"NumSec=%ld BytesPerSec=%ld FreeSec=%ld BiggestCreatable=%ld/r/n", (DWORD)m_si.snNumSectors, (DWORD)m_si.dwBytesPerSector, (DWORD)m_si.snFreeSectors, (DWORD) m_si.snBiggestPartCreatable)); // 检查Ramdisk是否已经进行过格式化操作,如果尚未进行过格式化 // 并且Ramdisk支持AUTOFORMAT,则对其进行自动格式化操作 if (ERROR_SUCCESS != m_pPartDriver->IsStoreFormatted (m_dwStoreId)) { // TODO: Look for ERROR_BAD_FORMAT if (m_dwFlags & STORE_ATTRIBUTE_AUTOFORMAT) { // TODO: Check for failure m_pPartDriver->FormatStore (m_dwStoreId); m_pPartDriver->GetStoreInfo ( m_dwStoreId, &m_si); } } // 透过分割区驱动程序提供的PD_FindPartitionStart、 // PD_FindPartitionNext和PD_FindPartitionClose三个函式统计分割区个数 // GetPartitionCount (); // 如果统计结果为零,说明装置尚未建立分割区。这时根据注册信息中 // AutoPart的键值决定是否自动分割区 if (!m_dwPartCount) { if (m_dwFlags & STORE_ATTRIBUTE_AUTOPART) { PD_STOREINFO si; si.cbSize = sizeof (PD_STOREINFO); m_pPartDriver->GetStoreInfo (m_dwStoreId, &si); if ((ERROR_SUCCESS==m_pPartDriver->CreatePartition(m_dwStoreId,L"PART00", 0, si.snBiggestPartCreatable, TRUE)) &&(ERROR_SUCCESS==m_pPartDriver->FormatPartition(m_dwStoreId, L"PART00", 0, TRUE))) { m_pPartDriver->GetStoreInfo ( m_dwStoreId, &m_si); DEBUGMSG(1, (L"NumSec=%ld BytesPerSec=%ld FreeSec=%ldBiggestCreatable=%ld/r/n", (DWORD) m_si.snNumSectors, (DWORD)m_si.dwBytesPerSector, (DWORD) m_si.snFreeSectors,(DWORD) m_si.snBiggestPartCreatable)); m_dwPartCount = 1; } } } // 呼叫LoadExistingPartitions完成后面的工作 LoadExistingPartitions (bMount); m_dwFlags |= STORE_FLAG_MOUNTED; } } } } return dwError; } *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp Line 178 *** DWORD CStore::OpenDisk () { DWORD dwError = ERROR_SUCCESS; // 根据装置名称建立装置句柄,首先尝试以可读可写方式建立 m_hDisk = CreateFileW(m_szDeviceName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (m_hDisk == INVALID_HANDLE_VALUE) { dwError = GetLastError (); // 如果尝试失败则以只读方式建立 if (dwError == ERROR_ACCESS_DENIED) { dwError = ERROR_SUCCESS; m_hDisk = CreateFileW (m_szDeviceName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (m_hDisk != INVALID_HANDLE_VALUE) { m_dwFlags |= STORE_ATTRIBUTE_READONLY; } else { dwError = GetLastError(); } } } if (m_hDisk != INVALID_HANDLE_VALUE) { DWORD dwRet; // 得到含有CHS值的DISK_INFO信息 if (!::DeviceIoControl(m_hDisk, DISK_IOCTL_GETINFO, &m_si, sizeof(DISK_INFO),NULL, 0, &dwRet, NULL)) { dwError = GetLastError (); if ((dwError == ERROR_BAD_COMMAND) || (dwError == ERROR_NOT_SUPPORTED)) { memset ( &m_si, 0, sizeof (DISK_INFO)); dwError = ERROR_SUCCESS; } } if (dwError == ERROR_SUCCESS) { // 得到STORAGEDEVICEINFO信息,其中m_sdi. szProfile指明了装置的 // 配置信息在注册信息中的位置 if (!::DeviceIoControl(m_hDisk, IOCTL_DISK_DEVICE_INFO, &m_sdi, sizeof(STORAGEDEVICEINFO), NULL, 0, &dwRet, NULL)) { DEBUGMSG (ZONE_INIT, (L"FSDMGR!CStore::OpenDisk (0x%08X) call to IOCTL_DISK_DEVICE_INFO failed..filling info/r/n", this)); m_sdi.dwDeviceClass = STORAGE_DEVICE_CLASS_BLOCK; m_sdi.dwDeviceFlags = STORAGE_DEVICE_FLAG_READWRITE; m_sdi.dwDeviceType = STORAGE_DEVICE_TYPE_UNKNOWN; wcscpy ( m_sdi.szProfile, L"Default"); } } DEBUGMSG ( ZONE_INIT, (L"FSDMGR!CStore::OpenDisk (0x%08X) DeviceInfo Class(0x%08X) Flags (0x%08X) Type (0x%08X) Profile (%s) /r/n", this, m_sdi.dwDeviceClass,m_sdi.dwDeviceFlags, m_sdi.dwDeviceType, m_sdi.szProfile)); } return dwError; } *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp Line 121 *** BOOL CStore::GetStoreSettings () { HKEY hKeyStorage=NULL, hKeyProfile = NULL; BOOL bRet = FALSE; // hKeyStorage值设为[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles] if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE, g_szPROFILE_PATH, 0, 0, &hKeyStorage)) { DUMPREGKEY(ZONE_INIT, g_szPROFILE_PATH, hKeyStorage); // 将hKeyProfile值设为[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/RamDisk] wsprintf (m_szRootRegKey, L"%s//%s", g_szPROFILE_PATH, m_sdi.szProfile); if (ERROR_SUCCESS != RegOpenKeyEx ( HKEY_LOCAL_MACHINE, m_szRootRegKey, 0, 0, &hKeyProfile)) { hKeyProfile = NULL; } else { DUMPREGKEY (ZONE_INIT, m_sdi.szProfile, hKeyProfile); } // 根据注册信息中AUTOMOUNT、AUTOFORMAT、AUTOPART三者的值设定m_dwFlags if (!hKeyProfile || !FsdLoadFlag (hKeyProfile, g_szAUTO_MOUNT_STRING, &m_dwFlags, STORE_ATTRIBUTE_AUTOMOUNT)) if (!FsdLoadFlag (hKeyStorage, g_szAUTO_MOUNT_STRING, &m_dwFlags, STORE_ATTRIBUTE_AUTOMOUNT)) m_dwFlags |= STORE_ATTRIBUTE_AUTOMOUNT; if (!hKeyProfile||!FsdLoadFlag (hKeyProfile,g_szAUTO_FORMAT_STRING, &m_dwFlags, STORE_ATTRIBUTE_AUTOFORMAT)) FsdLoadFlag (hKeyStorage, g_szAUTO_FORMAT_STRING, &m_dwFlags, STORE_ATTRIBUTE_AUTOFORMAT); if (!hKeyProfile||!FsdLoadFlag (hKeyProfile, g_szAUTO_PART_STRING, &m_dwFlags, STORE_ATTRIBUTE_AUTOPART)) FsdLoadFlag (hKeyStorage,g_szAUTO_PART_STRING, &m_dwFlags, STORE_ATTRIBUTE_AUTOPART); // 根据注册信息得到Ramdisk所使用的档案系统驱动程序,最后使用的一级目录名 // 称以及储存装置的描述性名称 if (!hKeyProfile||!FsdGetRegistryString (hKeyProfile,g_szFILE_SYSTEM_STRING, m_szDefaultFileSystem, sizeof (m_szDefaultFileSystem))) if (!FsdGetRegistryString(hKeyStorage,g_szFILE_SYSTEM_STRING, m_szDefaultFileSystem, sizeof (m_szDefaultFileSystem))) wcscpy (m_szDefaultFileSystem, g_szDEFAULT_FILESYSTEM); if ( !hKeyProfile || !FsdGetRegistryString (hKeyProfile, g_szFOLDER_NAME_STRING, m_szFolderName, sizeof(m_szFolderName))) if ( !FsdGetRegistryString (hKeyStorage, g_szFOLDER_NAME_STRING, m_szFolderName, sizeof(m_szFolderName))) wcscpy (m_szFolderName, g_szDEFAULT_FOLDER_NAME); if ( !hKeyProfile || !FsdGetRegistryString(hKeyProfile, g_szSTORE_NAME_STRING, m_szStoreName, sizeof(m_szStoreName))) if ( !FsdGetRegistryString (hKeyStorage, g_szSTORE_NAME_STRING, m_szStoreName, sizeof(m_szStoreName))) wcscpy (m_szStoreName, g_szDEFAULT_STORAGE_NAME); } // 从注册信息中读取Ramdisk所使用的分割区驱动程序名称 if ( !GetPartitionDriver (hKeyStorage, hKeyProfile)) goto ExitFalse; bRet = TRUE; ExitFalse: if (hKeyStorage) { RegCloseKey (hKeyStorage); } if (hKeyProfile) { RegCloseKey (hKeyProfile); } return bRet; } 由图5.5可知,Profile的键值为RamDisk,这个值记载了Ramdisk的配置信息在注册信息中的位置,也就是[HKEY_LOCAL_MACHINE/System /StorageManager/Profiles/RamDisk],此处所含的注册信息信息如图5.7所示。 图5.7 Ramdisk的注册信息 由图5.7可知,Ramdisk支持AutoFormat、AutoPart和AutoMount三种操作,使用的档案系统驱动程序为FATFS,在后面注册时使用的一级目录名为Storage Card。另外对于此处没有给出的一些信息则要用到系统的预设配置了,这些预设配置信息位于[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles],见图5.8。这里Ramdisk用到的唯一预设配置就是分割区驱动程序mspart.dll。 图5.8 Ramdisk配置信息 下面再来看看LoadPartition。首先会呼叫OpenPartition,透过该函式得到由分割区管理程序维护,并代表该分割区的PartState结构的指针。在这之后,建立一个CPartition对象并将其加入到CStore的m_pPartitionList连结串行中,然后呼叫LoadPartition得到有关该分割区的具体信息 (汇总为一个PD_PARTINFO结构) 。之后是一个判断语句,如果该store代表一个新发现的装置,并且前面由注册信息读出的配置信息表明该装置支持AUTOMOUNT,则呼叫MountPartition。其参数m_szRootRegKey为该装置的Profile在注册信息中的位置,另一个参数则是预设使用的档案系统名称。在函式MountPartition中,首先由注册信息读出有关分割区的Mount选项以及使用的档案系统等信息,然后加载对应的档案系统驱动程序的动态链接库。最后呼叫InitEx。接下来的工作就交由档案系统管理程序来完成了。程序代码5.6为CStore::LoadPartition、CPartition::MountPartition的原始程序代码。 程序代码5.6 CStore::LoadPartition、CPartition::MountPartition原始程序代码 *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp Line 291 *** BOOL CStore::LoadPartition (LPCTSTR szPartitionName, BOOL bMount) { DWORD dwPartitionId; // 得到由分割区管理程序负责维护的PartState结构的指针 if (ERROR_SUCCESS == m_pPartDriver->OpenPartition (m_dwStoreId,szPartitionName, dwPartitionId)) { // 建立CPartition对象并将其加入到CStore的m_pPartitionList连结串行中 CPartition *pPartition = new CPartition(m_dwStoreId, dwPartitionId, m_pPartDriver, m_szFolderName); AddPartition (pPartition); // 得到有关该分割区的具体信息 pPartition->LoadPartition (szPartitionName); // 如果的配置信息表明该装置支持AUTOMOUNT,则呼叫CPartition的成员函式MountPartition // 完成后面的工作 if (bMount && (m_dwFlags & STORE_ATTRIBUTE_AUTOMOUNT)) { if (pPartition->MountPartition (m_szRootRegKey, m_szDefaultFileSystem)) { m_dwMountCount++; } } return TRUE; } return FALSE; } *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp Line 291 *** BOOL CPartition::MountPartition (LPCTSTR szRootRegKey, LPCTSTR szDefaultFileSystem) { extern const TCHAR *g_szPART_TABLE_STRING; extern const TCHAR *g_szFILE_SYSTEM_MODULE_STRING; extern const TCHAR *g_szMOUNT_FLAGS_STRING; TCHAR szRegKey[MAX_PATH]; TCHAR szPartId[10]; HKEY hKey = NULL; DWORD dwFlags = 0; // 由[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/RamDisk]得到MountFlags的值 wcscpy (m_szRootKey, szRootRegKey); if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, m_szRootKey, 0, 0, &hKey)) { if (!FsdGetRegistryValue (hKey, g_szMOUNT_FLAGS_STRING, &dwFlags)) { dwFlags = 0; } RegCloseKey (hKey); } wsprintf (szRegKey, L"%s//%s", m_szRootKey, g_szPART_TABLE_STRING); // 决定Ramdisk所使用的档案系统驱动程序,即FATFS wcscpy (m_szFileSys, szDefaultFileSystem); if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, 0, &hKey) { DUMPREGKEY (ZONE_INIT, szRegKey, hKey); wsprintf (szPartId, L"%02X", m_pi.bPartType); if ( !FsdGetRegistryString (hKey, szPartId, m_szFileSys, sizeof (m_szFileSys))) { wcscpy (m_szFileSys, szDefaultFileSystem); } } // 从注册信息[HKEY_LOCAL_MACHINE/System/StorageManager/FATFS]得到FAT // 档案系统驱动程序的模块名fatfsd.dll if (wcslen (m_szFileSys)) { GetFSDString (g_szFILE_SYSTEM_MODULE_STRING, m_szModuleName, sizeof (m_szModuleName)); GetFSDString ( g_szFILE_SYSTEM_MODULE_STRING, m_szFriendlyName, sizeof (m_szFriendlyName)); if (m_szModuleName) { // 加载FAT档案系统驱动程序fatfsd.dll m_hFSD = LoadDriver ( m_szModuleName); // 填写FSDINITDATA数据结构,呼叫InitEx if (m_hFSD) { FSDINITDATA fid; fid.pIoControl = (PDEVICEIOCONTROL) PartitionIoControl; fid.hDsk = (HANDLE) this; fid.dwFlags = dwFlags; wcscpy ( fid.szFileSysName, m_szFileSys); wcscpy ( fid.szRegKey, szRootRegKey); wcscpy ( fid.szDiskName, m_szPartitionName); fid.hFSD = m_hFSD; fid.pNextDisk = NULL; m_pDsk = InitEx ( &fid); } } } if (m_pDsk) { m_dwFlags |= PARTITION_ATTRIBUTE_MOUNTED; } else { if (m_hFSD) { FreeLibrary (m_hFSD); m_hFSD = NULL; } } if (hKey) { RegCloseKey (hKey); } return TRUE; } 进入InitEx之后首先会呼叫FSDLoad,由FSDLoad完成两件事: 1. 呼叫AllocFSD完成FSD结构的分配及初始化,并将其纳入档案系统管理程序的管理当中。其参数hFSD是加载档案系统驱动程序,所对应的动态链接库后得到的句柄,有了这个句柄,我们就可以透过呼叫GetModuleFileName得到该动态链接库的名称。由于档案系统驱动程序所对应的动态链接库的名称,与该档案系统驱动程序的输出函式所使用的前缀名相同,因此刚刚得到的动态链接库的名称是所有输出函式所使用的前缀名。下面透过LocalAlloc申请分配一个FSD结构,并将其插入到档案系统管理程序负责维护的dlFSDList双向连结串行中。之后是透过GetFSDProcAddress,检查hFSD对应的动态链接库是否带有FSD_MountDisk和FSD_UnmountDisk这两个函式输出点。如果有,则说明这是一个档案系统驱动程序;否则继续呼叫GetFSDProcAddress,检查该动态链接库是否带有FSD_HookVolume和FSD_UnhookVolume这两个函式,如果有,则说明这是一个过滤器驱动程序,如果还未找到则将刚刚申请的FSD结构从dlFSDList双向连结串行中删除,并释放该结构所占用的空间。在经由了上述检查之后,由于前面我们已经知道了该档案系统驱动程序的输出函式所使用的前缀名,因此可以透过GetFSDProcArray得到该档案系统驱动程序,和过滤器驱动程序的所有输出函式的地址。到此我们就完成了一个FSD结构的初始化工作,并且已经将其纳入档案系统管理程序负责维护的dlFSDList双向连结串行中。 2. 呼叫AllocDisk为该FSD分配并初始化一个DSK结构。DSK结构中的hDsk句柄由呼叫CreateFileW得到。在呼叫CreateFileW时,首先试探以可读可写方式打开,如失败则以只读方式打开。由此也确定了DSK结构中的dwFlags是否具有DSK_READONLY属性。之后透过LocalAlloc申请分配一个DSK结构,并完成其成员变量的初始化。其中成员变量pFSD的值是刚刚透过AllocFSD得到的,这样DSK与FSD这两个结构之间就建立了连接。下面为InitEx、FSDLoad、AllocFSD、AllocDisk的原始程序代码。 程序代码 5.7 InitEx、FSDLoad、AllocFSD、AllocDisk函式原始码 *** PRIVATE/WINCEOS/COREOS/STORAGE/FSDMAIN/Fsdmain.cpp Line 155 *** PDSK InitEx(FSDINITDATA *pInitData) { BOOL bSuccess = TRUE; PDSK pDsk = FSDLoad( pInitData ) ; // 呼叫FSDLoad建立的_DSK (装置) // 和_FSD (FSD模块) 结构 PDSK pDskNew = HookFilters( pDsk, pDsk->pFSD ) ; // 在FSD上挂载过滤器 PFSD pFSD = pDsk->pFSD; // 指向FSDLoad中建立的_FSD结构 // 如果_FSD和_DSK均建立成功 if (pFSD && pDsk) { if (pFSD->pfnMountDisk) { // 呼叫XXX_MountDisk注册装置中的每个volume if (pFSD->pfnMountDisk(pDsk)) { // 修改_DSK的状态 UnmarkDisk(pDsk, DSK_UNCERTAIN); bSuccess = TRUE; } } } // 如果建立失败,释放前面申请的内存资源,略去… // 回传_DSK结构的指针 return (pDsk); } *** PRIVATE/WINCEOS/COREOS/STORAGE/FSDMAIN/Fsdmain.cpp Line 127 *** PDSK FSDLoad(FSDINITDATA * pInitData) { PFSD pFSD; PDSK pDsk; // 透过AllocFSD建立_FSD结构 pFSD = AllocFSD((HMODULE) pInitData->hFSD); // 透过AllocDisk建立 DSK 结构 pDsk = AllocDisk(pFSD, pInitData->szDiskName, pInitData->hDsk, pInitData->pIoControl); // 如果_DSK结构建立成功,继续设定 if (pDsk) { FSDMGR_GetDiskInfo(pDsk, &pDsk->fdi) ; // 获得装置硬件的信息(容量,扇区大小等) pDsk->dwFlags |= pInitData->dwFlags << 16; // 设定_DSK的属性 pDsk->pNextDisk = pInitData->pNextDisk; // 在实作其值为NULL pDsk->pPrevDisk = NULL; } // 如果_FSD结构建立成功,继续设定 if (pFSD) { // 拷贝名称 (实作时值是NULL) wcscpy(pFSD->szFileSysName, pInitData->szFileSysName); // 拷贝名称 (实作时值是NULL) wcscpy(pFSD->szRegKey, pInitData->szRegKey); } // 回传 DSK 结构的指针 return pDsk; } *** PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdalloc.cpp Line 37 *** PFSD AllocFSD (HMODULE hFSD) { PFSD pFSD; // 用于记录这个FSD模块的前缀 WCHAR baseName[MAX_FSD_NAME_SIZE]; // 如果模块中FSD_CreateFileW函式存在,那么前缀很简单,就是FSD_,这是一种特殊情况 if (GetProcAddress(hFSD, L"FSD_CreateFileW")) { wcscpy(baseName, L"FSD_"); } else { // 否则就会比较麻烦才能找到这个前缀,略过不看… } // 函式前缀被存放在了baseName中 // 首先,为新建的_FSD分配内存空间 pFSD = (PFSD) LocalAlloc(LPTR, sizeof(FSD)); if (pFSD) { // 设定SIGFIELD(就是把后一个值,赋给SIGFIELD) INITSIG(pFSD, FSD_SIG); // 把新建的_FSD结构,加入全局的双向连结串行,连结串行头是dlFSDList AddListItem ( (PDLINK) &dlFSDList, (PDLINK) &pFSD->dlFSD); // 设定_FSD中模块句柄的属性 pFSD->hFSD = hFSD; // 把baseName拷贝到_FSD中去 wcscpy ( pFSD->wsFSD, baseName); } // 有了名称前缀,我们就能透过GetProcAddress获得模块中函式的指针 // 这里的GetFSDProcAddress本质上还是呼叫了GetProcAddress,只不过 // 先把后边的 (如MountDisk) 和前缀合并起来,再去呼叫 if (pFSD) { if (!pFSD->pfnMountDisk || !pFSD->pfnUnmountDisk) { // 给_FSD中的函式指针属性赋值 pFSD->pfnMountDisk = (PFNMOUNTDISK) GetFSDProcAddress(pFSD, TEXT("MountDisk")); pFSD->pfnUnmountDisk = (PFNMOUNTDISK) GetFSDProcAddress(pFSD, TEXT("UnmountDisk")); } } // 在_FSD结构中有三个函式指针的数组,而GetFSDProcArray就是给这个数组中的 // 函式指标的值,其本质上还是去呼叫GetProcAddress,没有什么特别的地方 if (GetFSDProcArray(pFSD, pFSD->apfnAFS, apfnAFSStubs, apwsAFSAPIs, ARRAY_SIZE(apwsAFSAPIs)) && GetFSDProcArray(pFSD, pFSD->apfnFile, apfnFileStubs, apwsFileAPIs, ARRAY_SIZE(apwsFileAPIs)) && GetFSDProcArray(pFSD, pFSD->apfnFind, apfnFindStubs, apwsFindAPIs, ARRAY_SIZE(apwsFindAPIs))) { pFSD->pRefreshVolume = (PREFRESHVOLUME) GetFSDProcAddress(pFSD, L"RefreshVolume"); // 如果获取函式指标失败的话,透过DeallocFSD释放_FSD结构 (并从连结串行中取出) } else { DeallocFSD (pFSD); pFSD = NULL; } // 回传新建立的_FSD结构的指针 (如果建立失败,回传NULL) return pFSD; } *** PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdalloc.cpp Line 159 *** PDSK AllocDisk( PFSD pFSD, // 这个装置所需要的FSD模块对应的_FSD结构指针 PCWSTR pwsDsk, // 标识的装置名称 (比如说COM1:) HANDLE hDsk, // 在InitEx中呼叫时传递的是INVALID_HANDLE_VALUE PDEVICEIOCONTROL pIOControl // 用于IoControl的函式指标 ) { DWORD dwFlags = DSK_NONE; // 如果前面_FSD建立失败,直接错误退出 if (pFSD == NULL) return NULL; // 透过装置的唯一名称,呼叫CreateFile打开装置 if (hDsk == INVALID_HANDLE_VALUE) { hDsk = CreateFileW(pwsDsk, GENERIC_READ | GENERIC_WRITE, 0, NULL, PEN_EXISTING, 0, NULL); // 如果成功,可以进行读写操作,设定dwFlag if (hDsk != INVALID_HANDLE_VALUE) // 本装置可以进行读写操作 (而不是只读) dwFlags &= ~DSK_READONLY; // 否则的话,以只读模式开启装置 else if (GetLastError() == ERROR_ACCESS_DENIED) { hDsk = CreateFileW (pwsDsk, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hDsk != INVALID_HANDLE_VALUE) // 如果成功,说明装置是只读的 dwFlags |= DSK_READONLY; // 装置只可以进行读取操作 } pwsDsk = NULL; } // 如果读取操作都不可以的话,直接错误退出 if (hDsk == INVALID_HANDLE_VALUE) { return NULL; } // 申请_DSK结构需要的的内存空间 PDSK pDsk = (PDSK) LocalAlloc(LPTR, sizeof (DSK)); // 接下来开始设定_DSK结构的各个属性 if (pDsk) { INITSIG (pDsk, DSK_SIG) ; // 设定 SIGFIELD pDsk->pFSD = pFSD; // 指向前面建立的_FSD的结构 pDsk->pDeviceIoControl = pIOControl; // 设定用于IoControl的函式指标 pDsk->pVol = NULL; // 尚未向FSD Manager注册本装置 // 中的档案系统volume,现在是空的 // 把本装置的标识名称拷贝到_DSK中 if (pwsDsk) { wcscpy ( pDsk->wsDsk, pwsDsk); } else { wcscpy ( pDsk->wsDsk, L""); } } // 如果为_DSK分配空间失败,关闭前面开启的装置句柄(程序代码一定要严谨) else { if (hDsk) { CloseHandle(hDsk); } } if (pDsk) { pDsk->hDsk = hDsk; // 设定装置的句柄 pDsk->dwFlags = dwFlags; // 设定装置的属性(现在只有只读,或者读写两种属性) } // 最后,回传新建立的_DSK结构的指针 return pDsk; } FSDLoad回传之后,再透过FSDMGR_GetDiskInfo函式以及FSDINITDATA结构中尚未用到的一些信息,完成FSD和DSK两个结构的剩下初始化工作。如所使用的档案系统的名称,装置的配置信息在注册信息中的位置等等。在这之后,FSDLoad成功回传。 之后是呼叫HookFilters,从注册信息中搜寻所有与该档案系统驱动程序相关的过滤器,并将它们纳入档案系统管理程序的管理之中。如前所述,有关档案系统过滤器的信息也是保存在注册信息中的,档案系统管理程序需要到以下各处,搜寻应加载的档案系统过滤器: [HKEY_LOCAL_MACHINE/System/StorageManager/AutoLoad/FileSystem /Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/ProfileName /FileSystem/Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/FileSystem/Filters] [HKEY_LOCAL_MACHINE/System/StorageManager/Filters] HookFilters的第一项工作就是到以上各处,搜寻需要加载的过滤器驱动程序,并透过呼叫LoadFSDList,将所有的过滤器驱动程序链接成一个单向队列。注册信息中与每个过滤器相关的配置信息中都有一个Order,由它决定向档案系统驱动程序上加载这些过滤器驱动程序时所采用的顺序。与此对应,LoadFSDList函式也有一个参数bReverse用于表明链接时所采用的顺序。如果为TRUE,则LoadFSDList在搜寻到过滤器驱动程序后将它们按升幂排列。在收集了全部有关过滤器的信息之后,下面要做的就是将所有的过滤器驱动程序一个一个链接到档案系统驱动程序上。这也是透过呼叫FSDLoad完成的,只不过前面加载的对象是档案系统驱动程序,而这次是一些过滤器驱动程序。由于每次都是由LoadFSDList建立的pFSDLoadList标头 (header) 摘下一个过滤器,因此连接完成之后这些过滤器驱动程序的顺序便成了降序 (REVERSE) 。这一过程如图5.9、图5.10所示,所有的过滤器驱动程序构成了过滤器驱动程序堆栈。 filter1 filter2 filter3 Order=dword:0 Order=dword:1 Order=dword:2 图5.9 呼叫LoadFSDList之后 filter3 filter2 filter1 DSK Order=dword:2 Order=dword:1 Order=dword:0 图5.10 与DSK链接之后 最后要做的是呼叫档案系统驱动程序的输出函式FSD_MountDisk,由其负责定位新装置上的所有volume,并呼叫FSDMGR_RegisterVolume在档案系统管理程序中对它们进行注册。在每个档案系统驱动程序中,一般还会有一套由其自己定义的DSK、VOLUME及其它一些结构,这些结构所代表的对象与档案系统管理程序中定义的DSK、VOL等结构相同,不过会适应不同档案系统驱动程序的具体需要。FSD_MountDisk的主要作用就在于配合档案系统管理程序,完成对其自身定义的这些结构的管理工作,以便日后使用。 FSD_MountDisk首先呼叫GetDiskInfo得到装置的相关信息,之后再由ReadWriteDisk读入该装置上的第一个扇区―启动扇区的内容。由于启动扇区中含有分割区信息表,因此可知道如何为该装置分配DSK和VOLUME等结构、保存这些信息、并将它们管理起来。最后对各个volume以目录的形式在档案系统管理程序中进行注册 注册时要用到就是下面将要分析的FSDMGR_RegisterVolume函式,它负责申请并初始化一个VOL结构,然后呼叫RegisterAFSEx以及RegisterAFSName,按照档案系统驱动程序指定的名称对该卷进行注册。注册成功之后,档案系统管理程序就会以一级目录的形式将这些volume显示出来了,并且使得应用程序可以透过标准的档案系统API函式,对这些volume上的档案进行存取。FSDMGR_RegisterVolume函式的原型如下: PVOL FSDMGR_RegisterVolume( PDSK pDsk, // 档案系统volume所在装置的_DSK结构 // 指标 PWSTR pwsName, // 希望把这个volume挂载到以此字符串为名 // 的一级目录中 PVOLUME pVolume // 具体的FSD中用于管理volume使用的 // 数据结构指针 // 对于FAT FSD而言它就是_VOLUME // 结构的指针 ) 前两个参数从变量名称就可以清楚知道其用途,我们以下针对第三个参数做详细的解释。事实上,作为档案系统管理程序,它并不需要知道关于每一个volume的详细信息 (如分割区从哪个扇区开始,在哪个扇区结束) ,它的工作仅仅是去告诉相对应的档案系统驱动程序,由它管理的哪一个volume需要进行什么操作。这些详细的信息对于档案系统管理程序而言是完全不必要的,因此不必放在对应的_VOL结构中。但对于具体的一个档案系统驱动程序,情况就不是这样了。这里的pVolume就指向了具体档案系统驱动程序中,管理这个档案系统volume的数据结构 (对于FAT档案系统驱动程序就是_VOLUME) 。在FSDMGR_RegisterVolume中,所要做的就是简单的就是把这个指标赋给 (将被建立的) _VOL结构中的dwVolData属性。 具体而言,这些入口是怎么被注册的呢?简单的想来,系统中应该维护一个清单,清单中记录了这些入口。一旦使用者呼叫档案系统API,如CreateFile,必然会传给系统一个路径。系统先从路径名中分析出一级目录的名字,然后开始从清单中进行搜索,看这个一级目录是否是已经被登记的一个外设入口,如果是的话,就把操作权转让给档案系统管理程序,档案系统管理程序会进一步把操作权转给装置对应的档案系统模块。 虽然这些注册入口的函式原始码并没有公开,但是不难透过呼叫函式的原型大概猜测出其注册的方法。上面讲的那种入口,在Windows CE中称为AFS。一个AFS包括了这个入口的名称,还记录了是否有装置被挂载 (mount) 到了这个入口下,或者说是否有档案系统volume占用了这个入口。此外还会有一个序号 (或者说索引) 同每一个AFS一一对应。系统中预设进行预先登记的那个Storage Card对应的AFS序号是OID_FIRST_AFS。 对AFS进行具体操作的函式有如下三个: 1. GetAFSName 查看iAFS这个索引所指向的AFS是否已经被一个档案系统volume占用,pwsAFS会记录下这个入口的名字。如果回传值为零,就表示这个入口尚未被占用。 2. RegisterAFSName 建立一个AFS,其入口的名字就是此函式唯一的参数。回传值就是新建AFS一一对应的索引 3. RegisterAFSEx 占用一个AFS入口。函式需要的参数包括这个被占用的AFS的索引 (首先透过上面的RegisterAFSName来建立) ,以及占用这个入口的档案系统volume所对应的_VOL结构指针等等。 当一个档案系统volume进行挂载,寻找 (建立) AFS入口的时候,系统首先会从注册信息中,搜寻是否有已经设定好的对应这个装置的预设入口名称A (FSDMGR_RegisterVolume呼叫时那个pwsName就指向了这个名称A,如果注册信息中没有预设的名字,那么这个指标就是NULL) 。然后会判断Storage Card这个预设的入口是否已经被占用,如果尚未被占用,就优先把volume挂载到这个入口中。否则的话,判断是否提供了预设的入口 (pweName是否为NULL) 。如果两个方向都被否定了,就比较麻烦了,需要自己提供一个名字作为入口。 FSDMGR_RegisterVolume首先会呼叫AllocVolume,为这个档案系统volume建立相对应的_VOL结构。接下来,透过一定的策略为这个volume找出一个入口名,最后把这个档案系统volume注册在这个入口AFS中。程序代码5.8为FSDMGR_RegisterVolume的具体原始程序代码。 程序代码5.8 FSDMGR_RegisterVolume函式实作 *** PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdserv.cpp Line 336 *** PVOL FSDMGR_RegisterVolume(PDSK pDsk, PWSTR pwsName, PVOLUME pVolume) { PVOL pVol; BOOL bSuccess = FALSE; EnterCriticalSection (&csFSD) ; // 如果已经为装置注册了档案系统volume,先透过DeregisterVolume注销掉,再重新注册 if (pDsk->pVol) { FSDMGR_DeregisterVolume (pDsk->pVol); pDsk->pVol = NULL; } // 呼叫AllocVolume建立 (并初始化) _VOL结构 if (pVol = AllocVolume(pDsk, (DWORD) pVolume)) { PFSD pFSD = pVol->pDsk->pFSD; // 接下来的工作就是给这个新的volume注册一个AFS入口,首先需要找到合适的入口名称 if (pVol->iAFS == INVALID_AFS) { int cch; int iAFS, iSuffix = 1; WCHAR wsAFS[128]; // 首先透过GetAFSName看Storage Card这个预设的入口是否已经被占用 cch = GetAFSName(OID_FIRST_AFS, wsAFS, ARRAY_SIZE (wsAFS)); // 如果没有给出可供使用的入口名(pwsName=NULL)而且Storage Card已经被占用的话 if (pwsName == NULL && cch) { // 后面会为这种最差的情况做出一个入口名 iSuffix = 2; pwsName = wsAFS; } // 如果没有给可供使用的入口名(或者入口名就是Storage Card),而这个 // 预设的入口并没有被占用 if (((pwsName == NULL) || (wcscmp(pwsName, L"Storage Card")==0)) && !cch) { // 那么直接呼叫RegisterAFSEx来占用这个入口就可以了 if (RegisterAFSEx(OID_FIRST_AFS, hAFSAPI, (DWORD) pVol, AFS_VERSION, (pDsk->dwFlags >> 16) & (AFS_FLAG_ROOTFS | AFS_FLAG_HIDEROM | AFS_FLAG_BOOTABLE))) { // 入口的AFS索引就是OID_FIRST_AFS pVol->iAFS = OID_FIRST_AFS; // 把入口名称储存在_VOL结构中 wcscpy ( pVol->wsVol, wsAFS); bSuccess = TRUE; } } if (!bSuccess) { // 如果上面的做法都不成功的话,就给Storage Card(或者给出的那 // 个名字)后面加上数字序号1、2…,如Storage Card1,Storage Card2等等 wcscpy(wsAFS, pwsName[0] == '//'? pwsName+1 : pwsName); cch = wcslen(wsAFS); do { // 不断尝试各个序号 if (iSuffix > 1) { wsAFS[cch] = '0'+iSuffix; wsAFS[cch+1] = 0; } // 看这个新的名字 (末尾加了序号) 对应的入口是否已经被占用 iAFS = RegisterAFSName(wsAFS); // 如果尚未被使用,就可以透过RegisterAFSEx来占用它,否则的 // 话继续尝试 (序号++) if (iAFS != INVALID_AFS && GetLastError() == 0) { if (RegisterAFSEx(iAFS, hAFSAPI, (DWORD)pVol, AFS_VERSION, (pDsk->dwFlags >> 16) & (AFS_FLAG_ROOTFS | AFS_FLAG_HIDEROM | AFS_FLAG_BOOTABLE))) { // 把入口名称储存在_VOL结构中 wcscpy ( pVol->wsVol, wsAFS) ; // 把AFS的索引储存在_VOL结构中 pVol->iAFS = iAFS; break; } } } while (iSuffix++ < 9) ; } } // 如果连序号都试过 (Storage Card1到Storage Card9) ,却都被占用了 // 的话(这种情况应该很少见,毕竟CE不可能连接那么多装置) if (pVol->iAFS == INVALID_AFS) { // 注册 (Mount) 失败,释放前面申请的资源 DeallocVolume (pVol); pVol = NULL; } else { pVol->hNotifyHandle = NotifyCreateVolume(pVol->wsVol); } } LeaveCriticalSection(&csFSD); return pVol; } AllocVolume这个函式的目的如同前面的AllocFSD,AllocDisk都非常类似。主要就是建立 (并初始化) 一个同档案系统volume相对应的_VOL结构。比起AllocFSD和AllocDisk来要简单很多。首先会申请_VOL结构需要的内存空间,然后做简单的属性设定就可以了。程序代码段5.9为AllocVolume函式的具体原始程序代码。 程序代码5.9 AllocVolume函式 *** PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdalloc.cpp Line 298 *** PVOL AllocVolume ( PDSK pDsk, // 档案装置volume所属装置的_DSK结构指针 DWORD dwVolData // 具体FSD中用于管理这个档案系统volume使用的数据结构指针 // 它会被简单的赋予_VOL结构中的 dwVolData属性 ) { PVOL pVol; if (pDsk == NULL) return NULL; // 首先,申请_VOL结构需要的内存空间 pVol = (PVOL) LocalAlloc(LPTR, sizeof (VOL)); // 申请成功的话,进行简单的初始化工作 if (pVol) { INITSIG (pVol, VOL_SIG) ; // 设定SIGFIELD InitList ( (PDLINK) &pVol->dlHdlList) ; // 初始化_HDL结构的连结串行头 pDsk->pVol = pVol; // 设定_DSK的_VOL指标 pVol->dwFlags = 0; pVol->pDsk = pDsk; // 设定_VOL的_DSK指标 pVol->iAFS = INVALID_AFS; // 现在尚未被挂载,所以索引还没有意义 pVol->dwVolData = dwVolData; // 把dwVolData赋给_VOL中的相对应属性 } // 最后回传_VOL结构的指针 return pVol; } 所有的工作结束之后,Ramdisk就会以一级目录的形式展现在我们面前了,而这之后我们就可以透过CreateFile、ReadFile和WriteFile这些标准的档案系统Win32 API接口对Ramdisk进行档案操作了。图5.11中的一级目录“储存卡”即为载入后的Ramdisk,这个一级目录名称正是图5.6所示的注册信息中Folder的键值。当然由于是中文版Windows CE的关系,Storage Card变成了储存卡。 图5.11 Ramdisk挂载为一级目录储存卡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值