tiechui_lesson03_缓冲读写与自定义控制

文章介绍了Windows内核驱动中如何通过缓冲区与应用层进行交互,包括读写操作,自定义控制码的设置,以及在IRP结构体中处理这些操作。开发者需要注意头文件的引用,如<windef.h>和<winioctl.h>,以及在IRP的IoStatus.Information字段中返回信息。示例代码展示了驱动的Create、Cleanup、Close、Read、Write和DeviceControl分发函数的实现,以及应用层如何通过DeviceIoControl调用自定义控制码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习了与应用层通过缓冲区方式的交互,包括读写,自定义控制等。小坑比较多,大部分是是头文件和设置上的错误,跟着视频敲想快进就跳过了一些细节。包括:

  1. <windef.h> 头文件的引用    //使用DWORD等类型
  2. switch语句中break的设置           //这个总是会容易忽略
  3. 应用层自定义码的设置需要引入头文件winioctl.h 

内核模块代码:

#include <ntifs.h>
#include <windef.h>	//使用DWORD等类型

#define DEVICE_NAME L"\\Device\\MyFirstDevicePlus"		//设备名称
#define SYM_NAME	L"\\??\\MyFirstDevicePlus"			//符号链接

//定义自定义控制码 (做一个减法)
#define IOCTL_MUL	CTL_CODE(FILE_DEVICE_UNKNOWN,0x855,METHOD_BUFFERED, FILE_ANY_ACCESS)

//分发函数
NTSTATUS MyCreate(PDEVICE_OBJECT pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be opened!");
	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 0;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyClearUp(PDEVICE_OBJECT pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyClearUp!");
	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 0;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyClose(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyClose!");
	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 0;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyRead(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyRead!");

	//获取当前IRP堆栈信息
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	//对应用户层的读请求
	ULONG readsize = pstack->Parameters.Read.Length;
	DbgPrint("用户请求读大小为%u\n", readsize);
	//对应用户层所分配的缓冲区的内存位置
	PCHAR readbuffer = pirp->AssociatedIrp.SystemBuffer;	

	// readbuffer的赋值实际上是对用户缓冲区的改变
	RtlCopyMemory(readbuffer,
		"This Message Come From Kernel.",
		strlen("This Message Come From Kernel."));

	pirp->IoStatus.Status = status;
	//对于Information的赋值是返回给用户程序实际读取的长度。
	pirp->IoStatus.Information = strlen("This Message Come From Kernel.");

	//输出下字符串的长度
	DbgPrint("Really Read Info Len is %lld\n", 
		strlen("This Message Come From Kernel."));
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}

NTSTATUS MyWrite(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyWrite!");

	//获取当前IRP堆栈信息
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	//对应用户层的写请求
	ULONG writesize = pstack->Parameters.Write.Length;
	DbgPrint("用户请求写大小为%u\n", writesize);
	//对应用户层所分配的缓冲区的内存位置
	PCHAR writebuffer = pirp->AssociatedIrp.SystemBuffer;

	// 写入扩展设备之前先进行清0操作
	RtlZeroMemory(pdevice->DeviceExtension, 200);
	// writebuffer的数据写入到设备扩展里边
	RtlCopyMemory(pdevice->DeviceExtension,writebuffer,writesize);

	DbgPrint("写缓冲区内存地址:%p,设备扩展内容%s\n", writebuffer,
		(PCHAR)pdevice->DeviceExtension);

	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 13;

	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}

NTSTATUS MyControl(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyControl!");

	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);
	ULONG	iocode = pstack->Parameters.DeviceIoControl.IoControlCode;	// 获取控制码

	ULONG inlen = pstack->Parameters.DeviceIoControl.InputBufferLength;
	ULONG outlen = pstack->Parameters.DeviceIoControl.OutputBufferLength;
	ULONG ioinfo = 0;

	DbgPrint("InputBufferLength is %u\n", inlen);
	DbgPrint("OutputBufferLength is %u\n", outlen);

	switch (iocode)
	{
	case IOCTL_MUL:
	{
		//做一个减法

		DWORD indata = *(PDWORD)pirp->AssociatedIrp.SystemBuffer;
		DbgPrint("--Kernel Indata %d \n", indata);
		indata = indata * 5;
		*(PDWORD)pirp->AssociatedIrp.SystemBuffer = indata;z
		ioinfo = 50;

		//别忘记break!
		break;
	}
	default:
		status = STATUS_UNSUCCESSFUL;
		ioinfo = 0;
		break;
	}

	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = ioinfo;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}

//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
	DbgPrint("basedriver 卸载驱动\n");
	if (DriverObject->DeviceObject)
	{
		IoDeleteDevice(DriverObject->DeviceObject);
	}
	UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYM_NAME);
	IoDeleteSymbolicLink(&symLink);
}

//入口函数
NTSTATUS DriverEntry(
	IN PDRIVER_OBJECT DriverObject, 
	IN PUNICODE_STRING RegistryPath
)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	//注册卸载函数
	DriverObject->DriverUnload = DriverUnload;

	NTSTATUS status = STATUS_SUCCESS;
	PDEVICE_OBJECT pdevice;		// 用来接收创建的设备对象
	UNICODE_STRING devicename = { 0 };
	RtlInitUnicodeString(&devicename, DEVICE_NAME);

	//创建设备对象
	status = IoCreateDevice(DriverObject, 
							200,	// 定义设备扩展的大小,用来存放写入的数据
							&devicename, 
							FILE_DEVICE_UNKNOWN, 
							0, 
							TRUE, 
							&pdevice);

	if (!NT_SUCCESS(status))
	{
		KdPrint(("IoCreateDevice 虚拟设备打开失败 状态码 (0x%08X)\n",status));
		DbgPrint("IoCreateDevice 虚拟设备打开失败 状态码 (0x%08X)\n", status);
	}

	if (pdevice->Flags)
	{
		//不要忘了设备对象的读写方式,否则会蓝屏
		pdevice->Flags |= DO_BUFFERED_IO;	// 缓冲区方式的读写
	}



	//
	//创建成功,创建符号链接
	//
	UNICODE_STRING symname = { 0 };
	RtlInitUnicodeString(&symname, SYM_NAME);
	status = IoCreateSymbolicLink(&symname, &devicename);	// 符号链接名 设备名
	if (!NT_SUCCESS(status))
	{
		KdPrint(("IoCreateSymbolicLink 符号链接创建失败 状态码 (0x%08X)", status));
		DbgPrint("IoCreateSymbolicLink 符号链接创建失败 状态码 (0x%08X)", status);
	}

	//设置分发例程
	DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
	DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MyClearUp;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
	DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
	DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyControl;

	return 0;
}

 应用层代码:

#include <stdio.h>
#include <Windows.h>
#include <winioctl.h>// 使用自定义控制码
#include <stdlib.h>

//定义自定义控制码 (做一个乘法)
#define IOCTL_MUL	CTL_CODE(FILE_DEVICE_UNKNOWN,0x855,METHOD_BUFFERED,	FILE_ANY_ACCESS)

int main()
{
	HANDLE hdevice = NULL;
	CHAR readBuffer[50] = { 0 };
	DWORD bread = 0;


	hdevice = CreateFile(L"\\\\.\\MyFirstDevicePlus", 
		GENERIC_WRITE | GENERIC_READ, 0
		, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hdevice != INVALID_HANDLE_VALUE)
	{
		printf("打开设备SUCCESS!\n");
	}
	else {
		printf("打开设备失败!\n");

		system("pause");
	}
	system("pause");


	//读文件
	if (ReadFile(hdevice, (PVOID)readBuffer, 50, &bread, NULL))
	{
		printf("readBuffer --地址 %p --内容 %s\n", readBuffer, readBuffer);
		printf("真正读取长度 bread is %d\n", bread);
	}
	else {
		printf("ReadFile failed !\n");
	}
	system("pause");


	//写文件
	if (WriteFile(hdevice, "This message come from R3.",
		strlen("This message come from R3."), &bread, NULL))
	{

		printf("返回写入长度 bread is %d\n", bread);	// 写固定了 
	}
	else {
		printf("WriteFile failed !\n");
	}
	system("pause");


	//自定义控制
	printf("自定义控制 !\n");
	DWORD a = 8888, b = 0;
	DeviceIoControl(hdevice, IOCTL_MUL, &a, 4, &b, 4, &bread, NULL);

	printf("in %d  out %d   really info %d\n", a, b, bread);



	//关闭句柄
	if (hdevice != nullptr)
	{
		CloseHandle(hdevice);
		printf("关闭句柄!\n");
	}

	system("pause");

	return 0;
}

运行效果:

 将a乘了5并返回给应用层的b变量。

小结:

视频代码写的很粗糙,不过很感谢锤总能带着敲代码找错误,很有帮助,能让我对与内核开发不再那么畏惧。

收获是:

  1. 用户层读数据的时候,在内核层可以将一些数据放到pirp->AssociatedIrp.SystemBuffer缓冲区中,那么用户层程序就可以获取到这个缓冲区中的数据了。
  2. 用户层写数据的时候,在内核层可以将用户层的写入缓冲区的数据先拷贝到设备扩展的区域中。
  3. 自定义控制的写法是设置好控制码,然后在分发例程中对该控制码进行处理,通过之前定义好的缓冲区的方式,与应用层进行交互。
  4. pirp->IoStatus.Information 这个字段是返回给发出请求的函数一些信息,类似下边这样设置后,函数就能获取到这个数值。
    pirp->IoStatus.Information = 13;
  5. 视频中只是简单介绍,想要写好驱动程序,还需要继续练习,加深理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值