wdk中ramdisk代码解读
入口函数,即驱动加载函数
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) /*++ Routine Description: Installable driver initialization entry point. This entry point is called directly by the I/O system. Arguments: DriverObject - pointer to the driver object RegistryPath - pointer to a unicode string representing the path to driver-specific key in the registry Return Value: STATUS_SUCCESS if successful. --*/ { WDF_DRIVER_CONFIG config; KdPrint(("Windows Ramdisk Driver - Driver Framework Edition.\n")); KdPrint(("Built %s %s\n", __DATE__, __TIME__)); WDF_DRIVER_CONFIG_INIT( &config, RamDiskEvtDeviceAdd ); return WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE); }
初始化可配置变量config,指定RamDiskEvtDeviceAdd的地址,这里的RamDiskEvtDeviceAdd相当于wdm中AddDevice回调函数,当即插即用管理器发现了一个新设备 ,则调用的函数。然后调用WdfDriverCreate返回,我的理解是WdfDriverCreate创建了WDF驱动框架。
接下来是RamDiskEvtDeviceAdd函数
NTSTATUS RamDiskEvtDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) { //将要建立的设备对象的属性描述变量 WDF_OBJECT_ATTRIBUTES deviceAttributes; //将要调用的各种函数的状态返回值 NTSTATUS status; //将要建立的设备对象 WDFDEVICE device; //将要建立的队列对象的属性描述变量 WDF_OBJECT_ATTRIBUTES queueAttributes; //将要简历的队列配置变量 WDF_IO_QUEUE_CONFIG ioQueueConfig; //设备对象的扩展域的指针 PDEVICE_EXTENSION pDeviceExtension; //将要建立的队列扩展域指针 PQUEUE_EXTENSION pQueueContext = NULL; //将要建立的队列对象 WDFQUEUE queue; //初始化字符串 DECLARE_CONST_UNICODE_STRING(ntDeviceName, NT_DEVICE_NAME); //保证这个函数可以操作paged内存 PAGED_CODE(); //防止产生警告 UNREFERENCED_PARAMETER(Driver); //初始化设备的名字 status = WdfDeviceInitAssignName(DeviceInit, &ntDeviceName); if (!NT_SUCCESS(status)) { return status; } //磁盘设备的类型 必须是FILE_DEVICE_DISK WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_DISK); //设备的io类型 WdfDeviceIoBuffered 使用缓冲区接受数据 WdfDeviceIoDirect 直接接收数据 IRP所携带的缓冲区 可以直接使用 //WdfDeviceIoBufferedOrDirect 前面两种方式 都可以使用 WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect); //Exclusive:独占 设置设备为非独占的 WdfDeviceInitSetExclusive(DeviceInit, FALSE); //设置属性描述变量 就是说 设备对象的扩展 使用什么样的数据结构存储数据 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_EXTENSION); //制定设备的清理回调函数 deviceAttributes.EvtCleanupCallback = RamDiskEvtDeviceContextCleanup; //建立这个设备 status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device); if (!NT_SUCCESS(status)) { return status; } //将指针指向新建立设备的设备扩展 pDeviceExtension = DeviceGetExtension(device); //将队列的配置对象初始化为默认值 WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE ( &ioQueueConfig, WdfIoQueueDispatchSequential ); //wdf中我们需要将发往自己创建的设备的请求处理函数 在队列对象的设置对象中设置 //我们暂时只关心IoDeviceControl和读写事件 ioQueueConfig.EvtIoDeviceControl = RamDiskEvtIoDeviceControl; ioQueueConfig.EvtIoRead = RamDiskEvtIoRead; ioQueueConfig.EvtIoWrite = RamDiskEvtIoWrite; //指定这个队列设备的属性描述对象 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&queueAttributes, QUEUE_EXTENSION); //创建这个队列对象 这里第一个参数是设备对象 说明这个队列在创建的时候 已经和设备绑定了 status = WdfIoQueueCreate( device, &ioQueueConfig, &queueAttributes, &queue ); if (!NT_SUCCESS(status)) { return status; } //获取队列设备的扩展指针 pQueueContext = QueueGetExtension(queue); //设置队列对象的扩展的设备对象扩展 pQueueContext->DeviceExtension = pDeviceExtension; //设置进度条显示 //status = SetForwardProgressOnQueue(queue); //if (!NT_SUCCESS(status)) { // return status; //} //初始化设备扩展中一些变量 pDeviceExtension->DiskRegInfo.DriveLetter.Buffer = (PWSTR) &pDeviceExtension->DriveLetterBuffer; pDeviceExtension->DiskRegInfo.DriveLetter.MaximumLength = sizeof(pDeviceExtension->DriveLetterBuffer); //从注册表中获取 这个注册表信息 其实是由inf文件指定 RamDiskQueryDiskRegParameters( WdfDriverGetRegistryPath(WdfDeviceGetDriver(device)), &pDeviceExtension->DiskRegInfo ); //分配非分页内存给这个磁盘 DiskImage是磁盘镜像的意思 //分页内存和非分页内存 分页内存的存储介质有可能在内存 也有可能在硬盘 //非分页内存的存储介质一定是内存 所以分配非分页内存 不会引起复杂的换页操作和一些缺页中断 //RAMDISK_TAG 代表空间标识 便于调试 pDeviceExtension->DiskImage = ExAllocatePoolWithTag( NonPagedPool, pDeviceExtension->DiskRegInfo.DiskSize, RAMDISK_TAG ); if (pDeviceExtension->DiskImage) { UNICODE_STRING deviceName; UNICODE_STRING win32Name; //初始化磁盘空间 RamDiskFormatDisk(pDeviceExtension); status = STATUS_SUCCESS; // /DosDevice/xxx windows下代表了磁盘设备 RtlInitUnicodeString(&win32Name, DOS_DEVICE_NAME); RtlInitUnicodeString(&deviceName, NT_DEVICE_NAME); pDeviceExtension->SymbolicLink.Buffer = (PWSTR) &pDeviceExtension->DosDeviceNameBuffer; pDeviceExtension->SymbolicLink.MaximumLength = sizeof(pDeviceExtension->DosDeviceNameBuffer); pDeviceExtension->SymbolicLink.Length = win32Name.Length; RtlCopyUnicodeString(&pDeviceExtension->SymbolicLink, &win32Name); //初始化磁盘盘符 RtlAppendUnicodeStringToString(&pDeviceExtension->SymbolicLink, &pDeviceExtension->DiskRegInfo.DriveLetter); status = WdfDeviceCreateSymbolicLink(device, &pDeviceExtension->SymbolicLink); } return status; }
调用PAGED_CODE()保证代码可以调用非分页内存,这个宏其实是判断IRQL级别不处于DISPATCH_LEVEL级别及以上,如果是,则触发断言。
使用WdfDeviceInitAssignName初始化设备的名字,设备名和符号链接是不一样的,设备名只能在内核态被其他驱动识别。而符号链接则可以在用户态被应用程序识别。
使用WdfDeviceInitSetIoType指定设备的IO类型,一共有三种类型可选,1.WdfDeviceIoBuffered 使用缓冲区接受数据 2.WdfDeviceIoDirect,直接接收数据,IRP所携带的缓冲区 ,可以直接使用
3.WdfDeviceIoBufferedOrDirect 前面两种方式都会使用
使用函数WdfDeviceInitSetExclusive设置设备为非独占
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE指定设备扩展使用什么样的数据结构存储数据,DEVICE_EXTENSION为自定义的结构体
deviceAttributes.EvtCleanupCallback这里指定设备的清理函数为RamDiskEvtDeviceContextCleanup
准备完毕之后使用WdfDeviceCreate创建磁盘设备,参数都是前面处理过,其中device为传出参数
下面是创建队列对象的代码
ioQueueConfig为队列对象的配置对象,首先通过WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE初始化为默认值,然后指定IoDeviceControl和读写事件,通过WdfIoQueueCreate创建这个队列对象
注意的是队列对象的扩展结构里面包含了设备对象的扩展结构指针
RamDiskQueryDiskRegParameters取得注册表数据,系统在驱动加载之前,会首先提供访问注册表的功能,这里的数据其实是安装驱动时ramdisk.inf指定
pDeviceExtension->DiskImage这个指针是磁盘镜像,即磁盘位置的起始,然后为这个指针分配非分页内存,调用ExAllocatePoolWithTag传入第一个参数NonPagedPool。使用非分页内存的原因是,非分页内存的存储介质一定是内存,所以分配非分页内存 ,不会引起复杂的换页操作和一些缺页中断。
如果分配内存成功,则使用RamDiskFormatDisk函数初始化磁盘空间。接下来使用了win32Name存储符号链接,最终的符号链接为/DosDevice/Z: ,在用户态会默认显示Z作为盘符
刚开始我找到WdfControlFinishInitializing这个函数,以为可以通知用户层更新,从而显示出新建的磁盘对象,后来发现这个函数不能达到目的。在这里也求大神指教,如何不用重启,即可显示出新建的磁盘设备。
//初始化磁盘结构的一些数据 NTSTATUS RamDiskFormatDisk( IN PDEVICE_EXTENSION devExt ) { //将分配的非分页内存的首地址 转化为DBR结构的指针 PBOOT_SECTOR bootSector = (PBOOT_SECTOR) devExt->DiskImage; //指向FAT表的指针 PUCHAR firstFatSector; //根目录入口点个数 ULONG rootDirEntries; //一个簇由多少扇区组成 ULONG sectorsPerCluster; //fat文件系统类型 USHORT fatType; // Type FAT 12 or 16 //FAT表里面有多少表项 USHORT fatEntries; // Number of cluster entries in FAT //一个FAT表需要占多少簇存储 USHORT fatSectorCnt; // Number of sectors for FAT //第一个根目录入口点 PDIR_ENTRY rootDir; // Pointer to first entry in root dir //保证这个函数可以操作paged内存 PAGED_CODE(); ASSERT(sizeof(BOOT_SECTOR) == 512); ASSERT(devExt->DiskImage != NULL); //清空磁盘镜像 RtlZeroMemory(devExt->DiskImage, devExt->DiskRegInfo.DiskSize); //每个扇区有512个字节 devExt->DiskGeometry.BytesPerSector = 512; //每个磁道有32个扇区 devExt->DiskGeometry.SectorsPerTrack = 32; // Using Ramdisk value //每个柱面有两个磁道 devExt->DiskGeometry.TracksPerCylinder = 2; // Using Ramdisk value //计算得出磁柱面数 devExt->DiskGeometry.Cylinders.QuadPart = devExt->DiskRegInfo.DiskSize / 512 / 32 / 2; devExt->DiskGeometry.MediaType = RAMDISK_MEDIA_TYPE; KdPrint(( "Cylinders: %ld\n TracksPerCylinder: %ld\n SectorsPerTrack: %ld\n BytesPerSector: %ld\n", devExt->DiskGeometry.Cylinders.QuadPart, devExt->DiskGeometry.TracksPerCylinder, devExt->DiskGeometry.SectorsPerTrack, devExt->DiskGeometry.BytesPerSector )); //初始化根目录入口点个数 rootDirEntries = devExt->DiskRegInfo.RootDirEntries; //一个簇由多少扇区组成 sectorsPerCluster = devExt->DiskRegInfo.SectorsPerCluster; //这里 调整根目录数目的地方很疑惑 if (rootDirEntries & (DIR_ENTRIES_PER_SECTOR - 1)) { rootDirEntries = (rootDirEntries + (DIR_ENTRIES_PER_SECTOR - 1)) & ~ (DIR_ENTRIES_PER_SECTOR - 1); } KdPrint(( "Root dir entries: %ld\n Sectors/cluster: %ld\n", rootDirEntries, sectorsPerCluster )); //硬编码写入跳转指令 bootSector->bsJump[0] = 0xeb; bootSector->bsJump[1] = 0x3c; bootSector->bsJump[2] = 0x90; //oem名字 bootSector->bsOemName[0] = 'R'; bootSector->bsOemName[1] = 'a'; bootSector->bsOemName[2] = 'j'; bootSector->bsOemName[3] = 'u'; bootSector->bsOemName[4] = 'R'; bootSector->bsOemName[5] = 'a'; bootSector->bsOemName[6] = 'm'; bootSector->bsOemName[7] = ' '; //每个扇区有多少字节 bootSector->bsBytesPerSec = (SHORT)devExt->DiskGeometry.BytesPerSector; //指定这个磁盘卷保留扇区 仅DBR这一个扇区为保留扇区 bootSector->bsResSectors = 1; //fat表一般一式两份 但这里就创建一份就可以 bootSector->bsFATs = 1; //指定根目录入口点个数 bootSector->bsRootDirEnts = (USHORT)rootDirEntries; //磁盘总扇区数 磁盘大小除以扇区大小 bootSector->bsSectors = (USHORT)(devExt->DiskRegInfo.DiskSize / devExt->DiskGeometry.BytesPerSector); //磁盘介质类型 bootSector->bsMedia = (UCHAR)devExt->DiskGeometry.MediaType; //每个簇有多少个扇区 bootSector->bsSecPerClus = (UCHAR)sectorsPerCluster; //fat表项数 总扇区数-保留扇区数 //这里很疑惑 如何得到FAT表项数 fatEntries = (bootSector->bsSectors - bootSector->bsResSectors - bootSector->bsRootDirEnts / DIR_ENTRIES_PER_SECTOR) / bootSector->bsSecPerClus + 2; //如果表项数 大于2的12次方 则使用FAT16 if (fatEntries > 4087) { fatType = 16; //这一步的调整 我很不理解 fatSectorCnt = (fatEntries * 2 + 511) / 512; fatEntries = fatEntries + fatSectorCnt; fatSectorCnt = (fatEntries * 2 + 511) / 512; } else { fatType = 12; fatSectorCnt = (((fatEntries * 3 + 1) / 2) + 511) / 512; fatEntries = fatEntries + fatSectorCnt; fatSectorCnt = (((fatEntries * 3 + 1) / 2) + 511) / 512; } //初始化FAT表所占的扇区数 bootSector->bsFATsecs = fatSectorCnt; //初始化DBR每个磁道的扇区数 bootSector->bsSecPerTrack = (USHORT)devExt->DiskGeometry.SectorsPerTrack; //初始化每个柱面的磁道数 bootSector->bsHeads = (USHORT)devExt->DiskGeometry.TracksPerCylinder; //初始化启动签名 windows要求必须是0x29或者0x28 bootSector->bsBootSignature = 0x29; //卷ID 随便填写 bootSector->bsVolumeID = 0x12345678; //初始化卷标 bootSector->bsLabel[0] = 'R'; bootSector->bsLabel[1] = 'a'; bootSector->bsLabel[2] = 'm'; bootSector->bsLabel[3] = 'D'; bootSector->bsLabel[4] = 'i'; bootSector->bsLabel[5] = 's'; bootSector->bsLabel[6] = 'k'; bootSector->bsLabel[7] = ' '; bootSector->bsLabel[8] = ' '; bootSector->bsLabel[9] = ' '; bootSector->bsLabel[10] = ' '; //设置磁盘文件类型 bootSector->bsFileSystemType[0] = 'F'; bootSector->bsFileSystemType[1] = 'A'; bootSector->bsFileSystemType[2] = 'T'; bootSector->bsFileSystemType[3] = '1'; bootSector->bsFileSystemType[4] = '?'; bootSector->bsFileSystemType[5] = ' '; bootSector->bsFileSystemType[6] = ' '; bootSector->bsFileSystemType[7] = ' '; bootSector->bsFileSystemType[4] = ( fatType == 16 ) ? '6' : '2'; //设置DBR结束标识 bootSector->bsSig2[0] = 0x55; bootSector->bsSig2[1] = 0xAA; //初始化FAT表结构 firstFatSector = (PUCHAR)(bootSector + 1); firstFatSector[0] = (UCHAR)devExt->DiskGeometry.MediaType; firstFatSector[1] = 0xFF; firstFatSector[2] = 0xFF; if (fatType == 16) { firstFatSector[3] = 0xFF; } rootDir = (PDIR_ENTRY)(bootSector + 1 + fatSectorCnt); //接下来 初始化根目录入口点信息 rootDir->deName[0] = 'M'; rootDir->deName[1] = 'S'; rootDir->deName[2] = '-'; rootDir->deName[3] = 'R'; rootDir->deName[4] = 'A'; rootDir->deName[5] = 'M'; rootDir->deName[6] = 'D'; rootDir->deName[7] = 'R'; // // Set device extension name to "IVE" // NOTE: Fill all 3 characters, eg. sizeof(rootDir->deExtension); // rootDir->deExtension[0] = 'I'; rootDir->deExtension[1] = 'V'; rootDir->deExtension[2] = 'E'; rootDir->deAttributes = DIR_ATTR_VOLUME; return STATUS_SUCCESS; }
RamDiskFormatDisk函数用来初始化硬盘空间,如果是引导盘,那么硬盘镜像的第一个结构是MBR(主引导扇区),但是ramdisk虚拟的硬盘没有引导系统的作用,所以,第一个结构是DBR,结构如下
typedef struct _BOOT_SECTOR { UCHAR bsJump[3]; // x86 jmp instruction, checked by FS CCHAR bsOemName[8]; // OEM name of formatter USHORT bsBytesPerSec; // Bytes per Sector UCHAR bsSecPerClus; // Sectors per Cluster USHORT bsResSectors; // Reserved Sectors UCHAR bsFATs; // Number of FATs - we always use 1 USHORT bsRootDirEnts; // Number of Root Dir Entries USHORT bsSectors; // Number of Sectors UCHAR bsMedia; // Media type - we use RAMDISK_MEDIA_TYPE USHORT bsFATsecs; // Number of FAT sectors USHORT bsSecPerTrack; // Sectors per Track - we use 32 USHORT bsHeads; // Number of Heads - we use 2 ULONG bsHiddenSecs; // Hidden Sectors - we set to 0 ULONG bsHugeSectors; // Number of Sectors if > 32 MB size UCHAR bsDriveNumber; // Drive Number - not used UCHAR bsReserved1; // Reserved UCHAR bsBootSignature; // New Format Boot Signature - 0x29 ULONG bsVolumeID; // VolumeID - set to 0x12345678 CCHAR bsLabel[11]; // Label - set to RamDisk CCHAR bsFileSystemType[8];// File System Type - FAT12 or FAT16 CCHAR bsReserved2[448]; // Reserved UCHAR bsSig2[2]; // Originial Boot Signature - 0x55, 0xAA } BOOT_SECTOR, *PBOOT_SECTOR;
DBR结构如下图