(VR虚拟现实)虚拟磁盘
的设计与操作
虚拟磁盘的设计与操作
——李季季
摘要:
通常情况下是在磁盘上建立文件系统即硬盘分区,文件系统驱动程序(FSD)
已经实现了在硬盘上创建和管理文件,本系统主要实现将一个文件虚拟成一个相
应的磁盘,在文件系统驱动程序的基础之上,实现基于虚拟设备的文件系统,这
种虚拟技术得到了很多应用,例如虚拟光驱就是其中之一,我们将对磁盘进行相
关的操作。
关键字:
函数加载模块读写操作卸载模块格式化
一.整体功能概述:
1. 通过命令行输入/mount的加载相应虚拟磁盘的命令,执行完毕后便可以在“我
的电脑”中看到又多出了与命令行输入相应的盘符,通过 format将磁盘格式化
或通过右键选择格式化成相应格式后,便可以对它操作。
2. 通过命令行输入/mount的加载相应虚拟磁盘的命令,执行完后便可以在“我
的电脑”中看到又多出了一个虚拟的磁盘,便可以对它执行读写操作。
3. 在命令行下输入/umount的卸载相应虚拟磁盘的命令,执行完后便可以卸载除
刚才加载的虚拟磁盘。
二.设计的描述:
通常情况下是在磁盘上建立文件系统即硬盘分区,文件系统驱动程序(FSD)已
经实现了在硬盘上创建和管理文件,本系统主要实现将一个文件虚拟成一个相应
的磁盘,在文件系统驱动程序的基础之上,实现基于虚拟设备的文件系统,这种
虚拟技术得到了很多应用,例如虚拟光驱就是其中之一。虚拟磁盘文件系统经过
安装可以模拟真实的硬盘,支持各种文件系统功能。
三.基本的数据结构
(1)全局变量 dir_handle
HANDLEdir_handle;
该全局变量用于指向一个“目录对象”,存放本驱动程序创建的所有的磁盘对象。
(2)模拟磁盘文件信息结构 OPEN_FILE_INFORMATION
typedefstruct_OPEN_FILE_INFORMATION{
DEVICE_TYPEDeviceType;//所模拟的磁盘的类型
BOOLEANReadOnly;//是否设置虚拟磁盘为只读
LARGE_INTEGERFileSize;//本文件的大小
USHORTFileNameLength;//文件名的长度
UCHARFileName[1];//文件名字符串
}OPEN_FILE_INFORMATION;
该结构保存了用于虚拟磁盘的文件的详细信息。在应用程序利用 mount命令制定
这个文件时,会将文件的信息组成这样一个结构后作为参数传入内核。驱动程序
会抽取这些信息,并把它们作为虚拟磁盘的磁盘信息,例如磁盘的大小好只读属
性。
(3)设备扩展结构 DEVICE_EXTENSION:
自定义的磁盘信息结构,用来保存设备的细节以及对设备的编程方式。
Typedefstruct_DEVICE_EXTESION
{
BOOLEANmedia_in_device;
HANDLEfile_handle;//文件句柄
ANSI_STRINGfile_name;//文件名
LARGE_INTEGERfile_size;//文件的大小
BOOLEANread_only;//只读属性
PSECURITY_CLIENT_CONTEXTsecurity_client_context;
LIST_ENTRYlist_head;
KSPIN_LOCKlist_lock;
KEVENTrequest_event;
PVOIDthread_pointer;
BOOLEANterminate_thread;
}DEVICE_EXTENSION,*PDEVICE_EXTENSION;
本驱动程序所创建的虚拟对象均为自己维护这样一个数据结构。实际上,所有内
核驱动程序中的设备对象都维护这样一个数据结构。其主要作用就是在此保存对
应设备的一些参数,使得所有有关此设备的处理都可以通过设备对象访问到这些
信息。
相关函数介绍
(1)入口函数 DirverEntry
DirverEntry(
INPDRIVER_OBJECTDriverObject,
INPUNICODE_STRINGRegisterPath
)
该函数主要实现建立相应个数的磁盘对象和向 I/O管理器注册 4个分发例程以处
理来自应用程序的相应请求。
(2)分发例程 FileDiskCreateClose
FileDiskCreateClose(
INPDEVICE_OBJECTDriverObject,
INPIRPIrp
)
在这个函数中相应打开文件和关闭文件对象的请求,对应主功能代码
IRP_MJ_CREATE和 IPR_MJ_CLOSE。在函数中就是简单的返回打开成功的标记,不
需要完成额外的工作。
(3)分发例程 FileDiskReadWrite
FileDiskReadWrite(
INPDEVICE_OBJECTDeviceObject,
INPIRPIrp
)
这个函数完成对虚拟磁盘的读写,对应于主功能代码 IRP_MJ_WRITE和
IRP_MJ_READ。
在进行读写之前先确认设备是否存在。
(4)分发例程 FileDiskDeviceControl
FileDiskDeviceControl(
INPDEVICE_OBJECTDeviceObject,
INPIRPIrp
)
该函数处理发送到虚拟磁盘的控制信息。
(5)分发例程 FileDiskUnload
FileDiskUnload(
INPDRIVER_OBJECTDriverobject
)
用于卸载驱动程序。通过调用辅助函数 FileDiskDeleteDevice卸载本驱动程序
创建的各设备对象和对应的设备对象工作线程。
(6)辅助函数
辅助函数有 FileDiskCreateDevice、FileDiskThread、FileDiskDeleteDevice、
FileDiskOpenFile和 FileDiskCloseFile这些辅助函数被前面的各个函数调用,
以完成相对应的任务,其中最后两个函数在 FileDiskThread中进行调用,完成
相对应的工作。
程序主要模块代码
1. 虚拟磁盘或光驱的加载模块
IntFileDiskMount(
IntDeviceNumber,//设备号 0123等
POPEN_FILE_INFORMATIONOpenFileInformation,//文件类型信息
CharDriverletter,//卷名
BOOLEANCdImage//是否为 CD
)
{
charVolumeName[]=”\\\\.\\:”;
charDeviceName[255];//临时存放文件名
HANDLEDevice;//文件句柄
DWORDBytesReturned;//缓冲区
VolumeName[4]=DriveLetter;//将空格用盘符号代替
Device=CreateFile(
VolumeName,//要打开的文件的名字
GENERIC_READ|GENERIC_WRITE,//访问模式
FILE_SHARE_READ|FILE_SHARE_WRITE,//允许对文件进行读写共享访问
NULL,
OPEN_EXISTING,//文件必须已经存在。//打开一个文件,如果文件不存在函数将
会失败
FILE_FLAG_NO_BUFFERING,//禁止对文件进行缓冲处理。文件只能写入磁盘卷的
扇区块
NULL
);
if(Device!=INVALID_HANDLE_VALUE)//创建文件成功
{
SetLastError(ERROR_BUSY);
PrintLastError(&VolumeName[4]);
return-1;
}
//上面 CreateFile的作用是:判断文件是否已经存在,存在则设置错误信息,
中断返回。不存在继续向下执行;
if(CdImage)
{
sprintf(DeviceName,DEVICE_NAME_PREFIX"Cd""%u",DeviceNumber);//格 式 化
输出到 DeviceName中\Device\FileDiskCd*
}
else
{
sprintf(DeviceName,DEVICE_NAME_PREFIX"%u",DeviceNumber);//格式化输出
到 DeviceName中\Device\FileDisk*
}
if(!DefineDosDevice(//创建一个指向设备命名空间的符号链接,创建失败,输
出错误信息;
DDD_RAW_TARGET_PATH,
&VolumeName[4],//盘符号,例如 Z:
DeviceName
))
{
PrintLastError(&VolumeName[4]);
return-1;//返回
}
Device=CreateFile(//用 CreateFile打开上面建立的符号链接
VolumeName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,//打开一个文件 ,如果文件不存在函数将会失败,利用
DefineDosDevice创建 VolumeName[4]与 DeviceName的链接故文件存在;
FILE_FLAG_NO_BUFFERING,
NULL
);
if(Device==INVALID_HANDLE_VALUE)//创建失败;
{
PrintLastError(&VolumeName[4]);//错误信息;
DefineDosDevice(DDD_REMOVE_DEFINITION,&VolumeName[4],NULL);//删除前面
建立的符号链接
return-1;//返回
}
if(!DeviceIoControl(
Device,
IOCTL_FILE_DISK_OPEN_FILE,
OpenFileInformation,
sizeof(OPEN_FILE_INFORMATION)+OpenFileInformation->FileNameLength-1,
NULL,
0,
&BytesReturned,
NULL
))//创建失败
{
PrintLastError("FileDisk:");//错误处理
DefineDosDevice(DDD_REMOVE_DEFINITION,&VolumeName[4],NULL);////删除前
面建立的符号链接
return-1;//返回
}
return0;
}
2.虚拟磁盘或光驱的卸载模块
intFileDiskUmount(charDriveLetter)
{
charVolumeName[]="\\\\.\\:";//"\\.\:"特别特别注意中间有个空格
HANDLEDevice;
DWORDBytesReturned;
VolumeName[4]=DriveLetter;//将空格用盘符号代替
Device=CreateFile(//此处的作用是判断文件是否存在
VolumeName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,//文件必须已经存在。打开一个文件,如果文件不存在函数将会
失败 GetLastError()函数会捕获消息;
FILE_FLAG_NO_BUFFERING,
NULL
);
//返回一个无效的文件句柄,说明要卸载的文件系统根本不存在
if(Device==INVALID_HANDLE_VALUE)
{
PrintLastError(&VolumeName[4]);//比如要删除 Z:但是 Z不存在,便会输出错
误“Z:系统找不到指定的文件。”
return-1;//返回
}
//如果返回一个有效文件句柄,则虚拟的盘符存在
//开始执行/umount的动作
//包括以下几个步骤:
//1、锁定当前卷,通过发送 FSCTL_LOCK_VOLUME到设备驱动实现
//2、关闭所有该卷上打开的所有文件,通过发送 IOCTL_FILE_DISK_CLOSE_FILE
到设备驱动实现
//3、卸载该卷,通过发送 FSCTL_DISMOUNT_VOLUME到设备驱动实现
//4、解除该卷的锁定,通过发送 FSCTL_UNLOCK_VOLUME到设备驱动实现
//5、关闭设备
//6、删除虚拟盘符
if(!DeviceIoControl(
Device,
FSCTL_LOCK_VOLUME,//首先我们得发出 FSCTL_LOCK_VOLUME锁住光驱以防止写入
NULL,
0,
NULL,
0,
&BytesReturned,
NULL
))
{
PrintLastError(&VolumeName[4]);//操作失败,GetLastError()函数会捕获消
息,则进行错误处理;
return-1;//返回
}
if(!DeviceIoControl(
Device,
IOCTL_FILE_DISK_CLOSE_FILE,//发出 IOCTL_FILE_DISK_CLOSE_FILE,IO的控制
信息关闭磁盘文件
NULL,
0,
NULL,
0,
&BytesReturned,
NULL
))
{
PrintLastError("FileDisk:");//操作失败,GetLastError()函数会捕获消息,
则进行错误处理;
return-1;//返回
}
if(!DeviceIoControl(
Device,
FSCTL_DISMOUNT_VOLUME,//卸载该卷,通过发送 FSCTL_DISMOUNT_VOLUME到设备
驱动实现
NULL,
0,
NULL,
0,
&BytesReturned,
NULL
))
{
PrintLastError(&VolumeName[4]);//操作失败,GetLastError()函数会捕获消
息,则进行错误处理;
return-1;//返回
}
if(!DeviceIoControl(
Device,
FSCTL_UNLOCK_VOLUME,//解除该卷的锁定,通过发送 FSCTL_UNLOCK_VOLUME到设
备驱动实现
NULL,
0,
NULL,
0,
&BytesReturned,
NULL
))
{
PrintLastError(&VolumeName[4]);//操作失败,GetLastError()函数会捕获消
息,则进行错误处理;
return-1;//返回
}
CloseHandle(Device);//关闭内核对象设备 HANDLE
if(!DefineDosDevice(//删除虚拟盘符
DDD_REMOVE_DEFINITION,
&VolumeName[4],
NULL
))
{
PrintLastError(&VolumeName[4]);//操作失败,GetLastError()函数会捕获消
息,则进行错误处理;
return-1;//返回虚拟盘符
}
return0;//正常执行结束,返回
}
intFileDiskStatus(charDriveLetter)
{
charVolumeName[]="\\\\.\\:";//"\\.\:"特别特别注意中间有个空格
HANDLEDevice;
POPEN_FILE_INFORMATIONOpenFileInformation;
DWORDBytesReturned;
VolumeName[4]=DriveLetter;//将空格用盘符号代替
Device=CreateFile(//此处的作用是判断文件是否存在
VolumeName,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,//文件必须已经存在。打开一个文件,如果文件不存在函数将会
失败 GetLastError()函数会捕获消息;
FILE_FLAG_NO_BUFFERING,
NULL
);
//返回一个无效的文件句柄,说明文件系统根本不存在
if(Device==INVALID_HANDLE_VALUE)
{
PrintLastError(&VolumeName[4]);//操作失败,GetLastError()函数会捕获消
息,则进行错误处理;
return-1;//返回
}
OpenFileInformation=malloc(sizeof(OPEN_FILE_INFORMATION)+MAX_PATH);
if(!DeviceIoControl(
Device,
IOCTL_FILE_DISK_QUERY_FILE,//向 驱 动 层 传 递 控 制 码 , 返 回 一 个 指 向
OpenFileInformation的指针,其中有虚拟磁盘获知光驱的详细信息;
NULL,
0,
OpenFileInformation,
sizeof(OPEN_FILE_INFORMATION)+MAX_PATH,
&BytesReturned,
NULL
))
{
PrintLastError(&VolumeName[4]);
return-1;
}
if(BytesReturned<sizeof(OPEN_FILE_INFORMATION))
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
PrintLastError(&VolumeName[4]);
return-1;
}
printf("%c:%.*sSize:%I64ubytes%s\n",
DriveLetter,
OpenFileInformation->FileNameLength,
OpenFileInformation->FileName,
OpenFileInformation->FileSize,
OpenFileInformation->ReadOnly?",ReadOnly":""
);
return0;
}
通过这次学习,我基本上了解了虚拟磁盘程序设计的基本流程,对于驱动层
的那些分发例程,之前从来没有接触过,分析起来很困难,但最后也基本了解了
每个例程实现的功能,我们基本上都是在读代码,没有加入自己的东西。但是还
是很有收获,以前对于虚拟设备的理解仅限于略知一二,这次通过对代码的仔细
分析,,对内部的原理,实现方式等有了进一步的了解。