《Windows内核安全与驱动编程》-第九章-磁盘的虚拟-day-2

磁盘的虚拟

9.4 EvtDriverAdd 函数

9.4.5 用户配置的初始化

​ 当前,根据上一次的学习内容,设备和用来处理设备请求的队列都建立好了。接下来初始化与内存盘相关的一些数据结构。内存盘在驱动中的代表就是刚刚创建的设备,内存盘对应的数据结构自然应该与创建的这个设备相关。所以我们在设备拓展中存放这些数据结构。其中 DISK_INFO 用来给用户提供一些可配置的参数。

typedef struct _DEVICE_EXTENSION{
    // 用来指向一块内存区域,作为内存盘的实际数据存储空间
    PUCHAR DiskImage;
    // 用来存储内存盘的磁盘 Geometry
    DISK_GEOMETRY DiskGeometry;
    // 我们自己定义的磁盘信息结构,在安装时候存放在注册表中
    DISK_INFO  DiskRegInfo;
    // 这个盘的符号链接名,这是真正的符号链接名
    UNICODE_STRING SysbolicLink;
    // DiskregInfo 中 DriverLettr 的存储空间,这是用户在注册表中指定的盘符
    WCHAR	DriverLetterBuffer[DRIVER_LETTER_BUFFER_SIZE];
    //SymbolicLink 的存储空间
    WCHAR	DosDeviceNameBuffer[DOS_DEVNAME_BUFFER_SIZE];
}DEVICE_EXTENSION,*PDEVICE_EXTENSION;

typedef sturct _DISK_INFO{
    //磁盘的大小,以byte 计算,我们的磁盘最大设为4GB
    ULONG DiskSize;
    //磁盘上根文件系统的进入节点
    ULONG RootDirEntries;
    //磁盘上的每个簇由多少个山区组成
    ULONG SectrosPerCluster;
    //磁盘的盘符
    UNICODE_STRING DriverLetter;
}DISK_INFO,*PDISK_INFO;

​ 在了解了数据结构中各个成员对象的用处之后,就可以开展对这些数据结构的初始化工作了。首先要去注册表中获取用户指定的信息。这里通过自定义的函数实现。

//将生成的设备的设备拓展中相应的 UNICODE_STRING 初始化
pDeviceExtension->DiskRegInfo.DriverLetter.Buffer = (PWSTR)pDeviceExtension->DriverLetterBuffer;
pDeviceExtension->DiskRegInfo.DriverLetter.MaximumLength = sizeof(pDeviceExtension.DriverLetterBuffer);
//从系统为本驱动提供的注册表键中获取我们需要的信息
RamDiskQueryDiskRegParameters(
	WdfDriverGetRegistryPath(WdfDeviceGetDriver(device));
	&pDeviceExtension->DiskRegInfo
);

​ 首先使用 WdfDeviceGetDriver 获取我们生成的设备的上层驱动对象,然后使用 WdfDriverGetRegistryPath 从驱动对象中获取到相应的注册表路径。第二个参数是一个结构体变量,在这里将要向这个结构体变量里填写从注册表获得的值。

​ 获取了注册表路径之后,还需要分配一定大小的空间来模拟磁盘空间。这个大小是由注册表中的磁盘大小参数来决定的,往后这块空间称为 Ramdisk 的磁盘镜像。在这里,我们全部分配非分页内存,关于分页和非分页内存是在操作系统里学习到的知识。

​ 在分配了内存之后,磁盘就有了存储空间,但是就好像任何新磁盘一样。这个磁盘需要被分区,格式化。需要我们自己去格式化操作,因为内核中是没有地方调用 format 命令的。具体的操作会在下一节介绍。这里先知道 RamDiskFormatDisk 起的作用是把内存介质的磁盘格式化就可以了。

9.4.6 链接给应用程序

​ 至此,键盘设备已经具备了所有的部件,最后需要做的是暴露给应用层以使用。在 Windows 中各个盘符 C: D: 实际上都是符号链接。应用层的代码不能直接访问在内核中建立的设备,但是可以访问符号链接,所以在这里只需要用符号链接指向这个设备,就可以访问这个设备了。这里我们根据用户配置中选定的盘符去建立符号链接,将这个盘符和在这一届最开始建立的符号链接联系起来。

//分配用户指定大小的非分页内存,并使用我们自己的内存 TAG 值
pDeviceExtension->DiskImage = ExAllocatePoolWithTag(
	NonPagedPool,
    pDeviceExtension->DiskRegInfo.DiskSize,
    RAMDISK_TAG
);
//下面的代码只有在内存分配成功时才运行
if(pDeviceExtension->DiskImage) {
    UNICODE_STRING deviceName;
    UNICODE_STRING win32Name;
    //在这里调用我们自己实现的函数去初始化磁盘
    RamDiskFormatDisk(pDeviceExtension);
    status = STATUS_SUCCESS;
    //初始化一个内容为 "\\DosDevices\\" 的UNICODE_STRING 的变量
    RtlInitUnicodeString(&win32Name,DOS_DEVICE_NAME);
    //初始化一个内容为 "\\Devices\\RamDisk" 的UNICODE_STRING 的变量,这里无用
    //为了保持原文档的完整性
    RtlInitUnicodeString(&deviceName,NT_DEVICE_NAME);
    //准备好用来存储符号链接名的 UNICODE_STRING 变量
    pDeviceExtension->SymbolicLink.Buffer = (PWSTR)&pDeviceExtension->DosDeviceNameBuffer;
    pDeviceExtension->SymbolicLink.MaxmemLength = sizeof(&pDeviceExtension->DosDeviceNameBuffer);
    pDeviceExtension->SymbolicLink.Length = win32Name.Length;
    
    //将符号链接名的一开始设置为 "\\DosDevices\\",这是所有符号链接共有的前缀
    RtlCopyUnicodeString(&pDeviceExtension->SymbolicLink,&win32Name);
    //后面紧跟我们从用户配置中读出来指定的盘符
    RtlAppendUnicodeStringToString(
    	&pDeviceExtension->SymbolicLink,
        &pDeviceExtension->DiskReginfo.DriverLetter
    );
    //现在符号链接名已经准备就绪,调用 WDF 驱动框架模型提供的
    //函数 来为之前生成的设备建立符号链接
    status = WdfDeviceCreateSymbolicLink(
    	device,
        &pDeviceExtension->SymbolicLink
    );
 
}
//结束
return status;

9.4.7 小结

​ 至此,完整的分析了 RamDisk 驱动中 EvtDriverDeviceAdd 函数的具体实现。这个函数建立了设备对象,为了处理各种请求又建立了处理队列。同时注册了三个函数处理读写和 DeviceControl 请求。然后根据用户在注册表中的配置参数初始化了一些相关属性。为设备存储数据准备好了内存,建立了盘符。至此,RamDisk 建立完成。它在内核中代表了这个内存盘。但是我们还不了解磁盘卷的结构,所以不清楚如何去处理各种请求。接下来会继续学习磁盘卷的结构。

9.5 FAT12/16 磁盘卷的初始化

9.5.1 磁盘卷结构简介

​ 我们学习一个最简单的磁盘结构,即一个分区的基于 FAT12/16 的磁盘。Windows 磁盘卷首先继承了它所在磁盘的特性,这些特性是由硬件决定的,不可设置,不可改变。这些特性包括:

  • 每扇区的字节数。扇区是磁盘读写的基本单位,由于硬盘的物理设计导致它不能一次读写一个字节,而是一次最少读写一个扇区,现在的所有硬盘扇区大小为 512 字节。
  • 每磁道的扇区数。磁盘的盘片是原型的,读者可以认为每一个磁道都是一个源泉。整个盘片被划分成多个同心圆,即多个磁道。每个磁道具有相同的扇区数。
  • 每柱面的磁道数。磁盘是由许多个盘片组成的,成为一个圆柱体。可以认为所有盘片同一位置的磁道组成了一个柱面。
  • 柱面数。表示硬盘的一个圆形盘片能够划分出多少个同心圆。

​ 在 FAT12/16 文件系统中,有这么几个参数需要解释一下:

  • MBR。主引导记录,位于磁盘的第一个扇区,大小正好是一个扇区的大小。MBR 的起始处是一段程序,在 BIOS 的代码执行到最后时,BIOS 会将这段程序加载到内存中并开始执行。在这段程序的后面是一个硬盘分区表,用于记录当前磁盘具体的分区信息。由于 RamDisk 驱动只是用来建立一个可用的磁盘卷,并不要求这个卷可以引导和具有其他一些特征,所以在 RamDisk 的磁盘镜像中并不会看到 MBR 部分的存在。

  • DBR。 DBR 是 DOS Boot Record (操作系统引导记录) 的简称。MBR 的硬盘分区表中会记录每个分区的信息,包括起始位置等。而 DBR 就存在这个起始位置指向的第一个扇区里,DBR 里面包括了有效的引导程序、厂商标志、描述数据区等。引导程序是一段用来加载真正操作系统的程序,在DBR 的最开始是一个跳转指令,跳转到 DBR 后面一点的引导程序处。厂商标志又叫 OEM 串,一般是由格式化程序写的。数据描述区又称为 BPB 的数据块,记录了这个分区的众多信息,这些信息用于系统在为这个逻辑盘建立系统时做初始参数,如: 文件系统格式、根目录大小和簇大小等。作为文件系统的一个组成部分, DBR 是由操作系统的格式化程序建立的,在文件系统驱动操作任何一个磁盘卷时,这一部分的信息将被读取作为文件系统在这个磁盘卷上的参数被使用。所以在 RamDisk 驱动中,DBR 部分是需要存在的。

  • FAT区。FAT 是 File Allocation Table (文件分配表) 的简称。位于 DBR 之后,并且以一式两份的形式连续保存。FAT表 实际上是一个链表,它的每个表项的编号都代表磁盘上的一个簇,每个表项的内容都是另一个簇的编号。而一条完整的链表就代表了一个文件在磁盘上所占有的簇。FAT 表的 0 和 1 项是被保留的,从第二项开始用来记录某个文件所在的位置。由此可知,FAT 表的大小只和磁盘的大小以及这个磁盘上的文件系统对每个簇的大小定义有关。FAT12 和 16 的区别仅仅是一个 FAT 表的表项中能用几位做存储空间。

  • 根目录入口点。FAT 是多个链表的集合体,其中的每一条链代表了一个文件。但是这些链起始点如何确定呢?多个根目录入口点形成了一个表,这个表紧跟着 FAT 表存储,这个表的每个表项代表了根目录下的一个文件或者一个目录,这个表项上记载了很多相关的信息,如文件名,修改时间,最重要的是记录了这个文件在 FAT 表中的起点。这样就可以通过查询 FAT 表找到这个文件或者目录的所有簇,进而获取所有的数据。而通过查询每个目录的目录项内容又可以知道这个目录下面存储的文件在 FAT 表中的位置,这样就能遍历到磁盘上所有的文件了。

    ​ 以上知识,作为磁盘内容的小知识了解。有些会用到,有些不会。但是了解即可!对于所学的大部分内容,感觉在不用的时候有一个小印象就行,在用的时候知道如何去快速查找并且能迅速捡起来用即可。

明日计划

  • 论文开写
  • 继续学习驱动编程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章