我们已经明白了缓冲区方式的读写操作,下面根据这部分知识,来编写一个虚拟设备。这个设备来模拟一个文件,可以将这个设备想象成一个普通文件,可以进行读写作。另外,每次写这个文件,文件的长度会增加,可以利用GetFileSize函数(API函数)得到该文件的长度。
为了实现这个目的,需要编写三个IRP的派遣函数。它们分别对应着写操作、读操作获取文件长度操作。下面分别进行介绍。
(1)写操作。在应用程序中,通过 WriteFile 函数对设备进行写操作。下面程序片级是应用程序分配10字节的缓冲区,然后将缓冲区填写0XBB,然后将这10个字节写入设备。
UCHAR buffer[10];
memset(buffer, 0xBB, 10);
ULONG ulRead;
ULONG ulWrite;
BOOL bRet = WriteFile(hDevice,buffer,10,&ulWrite,NULL);
if (bRet)
{
printf("Write %d bytes\n",ulWrite);
}
WriteFile 内部会创建产生 IRP_MJ_WRITE 类型的 IRP,操作系统会将这个 IRP 传递给驱动程序。IRP_MJ_WRITE的 派遣函数需要将传送进来的数据保存起来,以便读取该设备的时候读取。在本例中,这个数据存储在一个缓冲区中,缓冲区的地址记录在设备扩展中,在设备启动的时候,驱动程序负责分配这个缓冲区,在设备被卸的时候,驱动程序回收该缓冲区。
对于IRP_MJ_WRITE 的派遣函数,主要任务是将写入的数据存储在这段缓冲区中。如果写入的字节数过大,超过缓冲区的大小,派遣函数将 IRP 的状态设置成错误状态。分外,在设备扩展中有一个变量记录着这个虚拟文件设备的文件长度。对设备的写操作会更改这个变量。
NTSTATUS HelloDDKWrite(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKWrite\n"));
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
// 获取存储的长度
ULONG ulWriteLength = stack->Parameters.Write.Length;
// 获取存储的偏移量
ULONG ulWriteOffset = (ULONG)stack->Parameters.Write.ByteOffset.QuadPart;
if (ulWriteOffset + ulWriteLength > MAX_FILE_LENGTH)
{
// 如果存储长度 + 偏移量大于缓冲区长度,则返回无效
status = STATUS_FILE_INVALID;
ulWriteLength = 0;
}
else
{
// 将写入的数据,存储在缓冲区内
memcpy(pDevExt->buffer + ulWriteOffset, pIrp->AssociatedIrp.SystemBuffer, ulWriteLength);
status = STATUS_SUCCESS;
// 设置信的文件长度
if (ulWriteLength + ulWriteOffset > pDevExt->file_length)
{
pDevExt->file_length = ulWriteLength + ulWriteOffset;
}
}
//设置IRP完成状态
pIrp->IoStatus.Status = status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information = ulWriteLength; // bytes xfered
//处理IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave HelloDDKRead\n"));
return status;
}
(2) 读操作。在应用程序中,通过 ReadFile 从设备读取数据,代码如下。
BOOL bRet = ReadFile(hDevice,buffer,10,&ulRead,NULL);
if (bRet)
{
printf("Read %d bytes:",ulRead);
// 显示读取的数据
for (int i=0;i<(int)ulRead;i++)
{
printf("%02X ",buffer[i]);
}
printf("\n");
}
ReadFile 内部会创建 IRP_MJ_READ 类型的 IRP,操作系统会将这个IRP传递给驱动程序中IRP_MJ_READ的派遣函数。IRP_MJ_READ 的派道函数的主要任务是把记录的数据复制到 AssociatedIrp.SystemBuffer 中。
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKRead\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//对一般IRP的简单操作,后面会介绍对IRP更复杂的操作
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = stack->Parameters.Read.Length;
ULONG ulReadOffset = (ULONG)stack->Parameters.Read.ByteOffset.QuadPart;
if (ulReadOffset + ulReadLength > MAX_FILE_LENGTH)
{
// 如果存储长度 + 偏移量大于缓冲区长度,则返回无效
status = STATUS_FILE_INVALID;
ulReadLength = 0;
}
else
{
// 将的数据存储在AssociatedIrp.SystemBuffer,以便应用程序使用
memcpy(pIrp->AssociatedIrp.SystemBuffer, pDevExt->buffer + ulReadLength, ulReadLength);
status = STATUS_SUCCESS;
}
//设置IRP完成状态
pIrp->IoStatus.Status = status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information = ulReadLength; // bytes xfered
//结束IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave HelloDDKRead\n"));
return status;
}
(3) 读取文件长度,读取文件长度依靠 GetFileSize Win32API获得。GetFileSize内容会创建IRP_MJ_QUERY_INFORMNTON类型的IRP。这个IRP请求的作用是向设备查询一些信息,这包括育询文件长度、设备创建的时间、设备属性等。在本例中IRP_MJ_QUERY_INFORMATON 派遭函数的主要任务是告诉应用程序这个设备的长度。
IRP_MJ_QUERY_INFORMATION 可以获得不同的信息,每种信息对应一种类多类别号,该类别号是一个 PILE_INFORMATION_CLASS 类型的枚举变量,可以读取I/O堆栈的
Parameters.QueryFile.FileInformationClass 子域。查询设备长度时,也就是调用 Win32 API GetFileSize 时, Parameters.QueryFile.FileInformationClass 应该为 FileStandardInformation。
这时,IRP的缓冲区数据为 FILE_STANDARD_INFORMATION 数据结构的数据。
typedef struct _FILE_STANDARD_INFORMATION {
LARGE_INTEGER AllocationSize;
LARGE_INTEGER EndOfFile;
ULONG NumberOfLinks;
BOOLEAN DeletePending;
BOOLEAN Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
其中,EndOfFile子域指明设备长度,修改这个子域会在 GetFileSize的返回值中得到体现,
IRP_MJ_QUERY_INFORMATION 派遣函数如下。
NTSTATUS HelloDDKQueryInformation(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKQueryInformation\n"));
// 获得IO堆栈
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
// 获得设备扩展
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
// 得到文件信息
FILE_INFORMATION_CLASS info = stack->Parameters.QueryFile.FileInformationClass;
// 判断是否标准文件信息
if (info == FileStandardInformation)
{
KdPrint(("FileStandardInformation\n"));
PFILE_STANDARD_INFORMATION file_info =
(PFILE_STANDARD_INFORMATION)pIrp->AssociatedIrp.SystemBuffer;
file_info->EndOfFile = RtlConvertUlongToLargeInteger(pDevExt->file_length);
}
NTSTATUS status = STATUS_SUCCESS;
//设置IRP完成状态
pIrp->IoStatus.Status = status;
//设置IRP操作字节数
pIrp->IoStatus.Information = stack->Parameters.QueryFile.Length; // bytes xfered
//结束IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave HelloDDKQueryInformation\n"));
return status;
}