• 驱动学习7


    上层应用程序和底层驱动程序通信时,应用程序会发出I/O请求,操作系统将I/0请求转化为相应的IRP,不同类型的IRP根据类型传递给不同的派遣函数

    IRP有两个基本属性,一个是MagorFunction,一个是MinorFunction,分别记录IRP的主类型和子类型,操作系统根据MajorFunction将IRP派遣到不同的派遣函数中,在派遣函数中还可以判断这个IRP属性哪个MinorFunction


    一般来说,NT式驱动和WDM驱动都是在DriverEntry中注册派遣函数的,在驱动对象中,有个函数指针数组MajorFunction,每个元素记录了一个函数地址,通过设置这个数组,可以把IRP类型和派遣函数关联起来,对于没有设备的IRP类型,系统默认这些IRP类型与_IopINvalidDeviceRequest关联,在进入DriverEntry之前,操作系统就会把_IopINvalidDeviceRequest的地址填满整个MajorFunction数组,IRP和派遣函数的联系如下:



    IRP的概念类似于Windows应用程序中的消息,不同的消息会被分发到不同的消息处理函数中,如果没有对应的处理函数,它会进入系统默认的消息处理函数中

    IRP类似,文件I/O的相关函数,如CreateFile,ReadFile,WriteFile,CloseHandle会使操作系统产生出IRP_MJ_CREATE,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_CLOSE等不同的IRP,将把IRP传送到相应驱动的相应派遣函数中.


    对派遣函数的最简单处理是:把IRP的状态设置为成功,然后结束IRP的请求,并让派遣函数返回成功,结束IRP请求使用函数IoCompleteRequest

    #pragma PAGEDCODE
    NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
    								 IN PIRP pIrp) 
    {
    	KdPrint(("Enter HelloDDKDispatchRoutine
    "));
    	NTSTATUS status = STATUS_SUCCESS;
    	// 设置IRP完成状态
    	pIrp->IoStatus.Status = status;
    
    	// 设置IRP操作了多少字节
    	pIrp->IoStatus.Information = 0;	// bytes xfered
    
    	// 结束IRP请求
    	IoCompleteRequest( pIrp, IO_NO_INCREMENT );
    	KdPrint(("Leave HelloDDKDispatchRoutine
    "));
    	return status;
    }

    IoCompleteRequest的第二个参数是指被阻塞的线程以何种优先级恢复运行,一般情况下,优先级设置为IO_NO_INCREMENT



    7.1.4通过设备链接打开设备

    在编写程序时,可以把符号链接的写法稍微改一下,把前面的??改为\.\,如\.HellloDDK

    int _tmain(int argc, _TCHAR* argv[])
    {
    	// 打开设备句柄,会触发IRP_MJ_CREATE
    	HANDLE hDevice = CreateFileA
    		("\\.\HelloDDK",
    		GENERIC_READ | GENERIC_WRITE,
    		0, //非共享
    		NULL, 
    		OPEN_EXISTING,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL
    		);
    	if (INVALID_HANDLE_VALUE == hDevice)
    	{
    		printf("Fail to open file handle
    ");
    	}
    	else
    	{
    		printf("hDevice:0x%08x
    ", hDevice);
    	}
    	
    
    	CloseHandle(hDevice);
    	system("pause");
    
    	return 0;
    }
    


    7.1.5编写一个更通用的派遣函数

    驱动对象会创建一个个的设备对象,并把这些设备对象叠成一个垂直结构,这种垂直结构很像栈,所以被称为设备栈

    ,IRP会被操作系统发送到设备栈的顶层,如果顶层的设备对象的派遣函数结束了 IRP的请求,则这此I/0请求结束,如果没有结束,操作系统会把IRP转发到设备栈的下一层设备处理,如果这个设备的派遣函数依然没有结束IRP请求,则会继续向下层设备转发,

    因此,一个IRP可能被转发多次,为了记录IRP在每层设备中做的操作,IRP会有一个IO_STACK_LOCATION数组,数组的元素数应大于IRP穿越过的设备数,每个IO_STACK_LOCATION元素记录着对应设备中做的操作,对于本层设备对应的IO_STACK_LOCATION,可以通过

    PIO_STACK_LOCATION 
      IoGetCurrentIrpStackLocation(
        IN PIRP
      Irp
        );
    来得到,IO_STACK_LOCATION结构中记录了IRP的类型,即IO_STACK_LOCATION中的MajorFunction子域

    PIO_STACK_LOCATION isl = IoGetCurrentIrpStackLocation(pIrp);
    	KdPrint(("IO_STACK_LOCATION.MajorFunction:%u
    ", isl->MajorFunction));

    设备对象有三种读写方式:缓冲区方式读写,直接方式读写,其他方式读写,FLAG分别对应为DO_BUFFERED_IO, DO_DIRECT_IO和0

    缓冲区方式:操作系统把应用程序提供缓冲区的数据复制到内核模式下的地址中,这样,无论操作系统怎么切换进程,内核模式地址不会改变,IRP的派遣函数将会对内核模式下缓冲区操作,而不是操作用户模式地址的缓冲区

    比如WriteFile,这个地址由IRP的AssociatedIrp.SystemBuffer子域记录

    以缓冲区方式读写设备,都会发生用户模式地址和内核模式地址的数据复制,复制的过程由操作系统负责,用户模式地址由ReadFile或WriteFile提供,并且ReadFile或WriteFile创建的IRP的AssociatedIrp.SystemBuffer记录了这段内存地址,最后由操作系统负责分配和回收

    测试代码:

    Driver:

    #pragma PAGEDCODE
    NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
    								 IN PIRP pIrp) 
    {
    	KdPrint(("Enter HelloDDKRead
    "));
    	NTSTATUS status = STATUS_SUCCESS;
    
    	// 得到当前堆栈
    	PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
    
    	// 得到需要读设备的字节数
    	ULONG ulReadLength = stack->Parameters.Read.Length;
    	KdPrint(("[HelloDDKRead]--ulReadLength:%d
    ", ulReadLength));
    
    	// 完成IRP
    	pIrp->IoStatus.Status = status;
    	// 设置IRP操作了多少字节
    	pIrp->IoStatus.Information = ulReadLength;
    
    	// 设置内核下的缓冲区
    	memset(pIrp->AssociatedIrp.SystemBuffer, 0xAA, ulReadLength);
    
    	// 完成IRP处理
    	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    
    	KdPrint(("Leave HelloDDKRead
    "));
    	return status;
    }

    test:

    UCHAR buffer[10] = {0};
    		ULONG ulRead;
    		BOOL bRet = ReadFile(hDevice, buffer, 10, &ulRead, NULL);
    		if (bRet)
    		{
    			for (int i=0; i<10; i++)
    			{
    				printf("%02x", buffer[i]);
    			}
    		}

    // 设置IRP操作了多少字节
    	pIrp->IoStatus.Information = ulReadLength;
    其实这里可以随意设置,比如设置为90,那么test的ulRead就返回90了:


    又比如写驱动,我们可以接管IRP_MJ_WRITE,使用一个扩展结构体保存传入的数据:

    typedef struct _DEVICE_EXTENSION {
    	PDEVICE_OBJECT pDevice;
    	UNICODE_STRING ustrDeviceName;	//设备名称
    	UNICODE_STRING ustrSymLinkName;	//符号链接名
    	CHAR buffer[260];//用来保存写的
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    #pragma PAGEDCODE
    NTSTATUS HelloDDKWrite(IN PDEVICE_OBJECT pDevObj,
    					  IN PIRP pIrp)
    {
    	NTSTATUS status = STATUS_SUCCESS;
    	KdPrint(("Enter HelloDDKWrite
    "));
    
    	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 > 260)
    	{
    		status = STATUS_FILE_INVALID;
    		ulWriteLength = 0;
    	}
    	else
    	{
    		memcpy(pDevExt->buffer+ulWriteLength, pIrp->AssociatedIrp.SystemBuffer, ulWriteLength);
    	}
    
    	pIrp->IoStatus.Status = status;
    	pIrp->IoStatus.Information = ulWriteLength;
    
    	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    	KdPrint(("Leave HelloDDKWrite
    "));
    	return status;
    }

    7.3.1

    直接方式读写设备

    在创建设备后,设置设备属性为DO_DIRECT_IO,缓冲区读写方式是把内存从ring3复制到ring0,而直接读写方式是把ring3的缓冲区锁住,然后把它映射到ring0,这样,两者指向的是同一块物理内存.(注意是指向同一块物理地址,虚拟地址一个在ring3,一个在ring0)

    操作系统使用内存描述符表(MDL)来记录这段内存,大小为mdl->ByteCount,起始页地址是mdl->StartVa,首地址相对第一个页偏移为mdl->ByteOffset,因此,首地址就是mdl->StartVa+mdl->ByteOffset,注意的是直接方式取的是pIrp的mdl,而ring3取的是_IO_STACK_LOCATION

    	if (pIrp->MdlAddress)
    	{
    		ULONG ulWriteLength = MmGetMdlByteCount(pIrp->MdlAddress);
    		ULONG ulWriteOffset = MmGetMdlByteOffset(pIrp->MdlAddress);
    		PVOID pWrite = MmGetMdlVirtualAddress(pIrp->MdlAddress);

    7.4.1,其他方式读写设备

    如果不设置DO_BUFFERED_IO,也不设置DO_DIRECT_IO,则条用其他读写方式



    7.5.1 DeviceIoControl与驱动交互

    应用程序可以通过DeviceIoControl操作设备,它会使操作系统创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,然后操作系统会把这个IRP转发到派遣函数,一般用它使应用程序和驱动程序进行通信,如,要对一个设备进行初始化操作,自定一种I/O控制码,然后用DeviceIoControl将这个控制码和请求一起传给驱动程序,在派遣函数中,分别对不同的I/O控制码进行处理

    控制码也称IOCTL值,是32位无符号整型,IOCTL需符合DDK的规定,如下:


    DDK提供了一个宏CTL_CODE

    #define CTL_CODE( DeviceType, Function, Method, Access ) (                 
        ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) 

    DeviceType:设备对象类型,这个应和创建设备(IoCreateDevice)时的类型相匹配,

    Function:0x800到0xFFF,由程序员自己定义

    Method:这个是操作模式,

    METHOD_BUFFERED:缓冲区方式操作

    METHOD_IN_DIRECT:直接写方式操作

    METHOD_OUT_DIRECT:直接读方式操作

    METHOD_NEITHER:使用其他方式操作

    Access:访问权限,如FILE_ANY_ACCESS

    #define IOCTL_TESSAFE_INIT 
                CTL_CODE(FILE_DEVICE_UNKNOWN, 0x921, METHOD_BUFFERED, FILE_READ_ACCESS|FILE_WRITE_ACCESS)

    一般建议使用METHOD_BUFFERED, 驱动中最好不要直接访问用户模式下的内存地址

    BOOL DeviceIoControl(
      HANDLE hDevice, //已打开的设备句柄
      DWORD dwIoControlCode, //IO控制码
      LPVOID lpInBuffer, //输入buffer
      DWORD nInBufferSize, //输入大小
      LPVOID lpOutBuffer, // 输出buffer
      DWORD nOutBufferSize, //输出buffer大小
      LPDWORD lpBytesReturned, //实际返回字节数,
      LPOVERLAPPED lpOverlapped//设为NULL
    );
    

    缓存读取的示例代码:

    #define  IOCTL_TEST1
    			 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS|FILE_WRITE_ACCESS)
    
    int main()
    {
    	HANDLE hDevice = CreateFileA
    		("\\.\DDKTest",
    		GENERIC_READ | GENERIC_WRITE,
    		0,
    		NULL,
    		OPEN_EXISTING,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL
    		);
    
    	if (INVALID_HANDLE_VALUE == hDevice)
    	{
    		printf("Fail to open device with err:%d
    ", GetLastError());
    		getchar();
    		return 1;
    	}
    
    	UCHAR InBuf[100] = {0};
    	memset(InBuf, 0x41, 100);
    
    	UCHAR OutBuf[100] = {0};
    	DWORD dwOutput;
    	DWORD dwOutBuf=100;
    	BOOL bRet = DeviceIoControl
    		(hDevice,
    		IOCTL_TEST1,
    		InBuf,
    		100,
    		OutBuf,
    		dwOutBuf,
    		&dwOutput, 
    		NULL
    		);
    	if (bRet)
    	{
    		printf("IOCTL_TEST1 dwOutBuf:%d, dwOutput:%d
    ", dwOutBuf, dwOutput);
    		for (int i=0; i<(int)dwOutput; i++)
    		{
    			printf("%02X", OutBuf[i]);
    		}
    		printf("
    ");
    	}
    
        getchar();
    	return 0;
    }

    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
    	ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;
    	ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;
    	ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
    	ULONG info = 0;
    
    	switch (code)
    	{
    	case IOCTL_TEST1:
    		{
    			UCHAR* InBuf = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
    			memset(InBuf, 0x61, cbin);//全写成a
    			stack->Parameters.DeviceIoControl.OutputBufferLength = cbout-2;//随机测试
    			info = cbout-1;//随机测试
    		}
    		break;
    		
    	default:
    		status = STATUS_INVALID_VARIANT;
    	}
    
    	// 完成IRP
    	pIrp->IoStatus.Status = status;
    	pIrp->IoStatus.Information = info;
    	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    

    结果 如下:























  • 相关阅读:
    整理一批 国内外优秀设计团队 & 设计相关网站
    国内技术团队博客盘点(不只是前端!)
    【技能大赛笔记01】Zigbee点对点按键控制程序开发
    【网络爬虫入门05】分布式文件存储数据库MongoDB的基本操作与爬虫应用
    【网络爬虫入门04】彻底掌握BeautifulSoup的CSS选择器
    【网络爬虫入门03】爬虫解析利器beautifulSoup模块的基本应用
    【网络爬虫入门02】HTTP客户端库Requests的基本原理与基础应用
    【网络爬虫入门01】应用Requests和BeautifulSoup联手打造的第一条网络爬虫
    【Zigbee技术入门教程-02】一图读懂ZStack协议栈的核心思想与工作机理
    【Zigbee技术入门教程-号外】基于Z-Stack协议栈的抢答系统
  • 原文地址:https://www.cnblogs.com/hgy413/p/3693354.html
Copyright © 2020-2023  润新知