在windows内核中,有一种数据结构IRP(I/O Request Package)输入输出请求包,事输入输出相关的重要数据结构,上层应用程序与底层驱动程序通信时应用程序会发出I/o请求,操作系统将I/O请求转化为相应的IRP数据,不同类型的IRP会根据类型传递到不同的派遣函数内。IRP两个基本类型MajorFunction和MinorFunction分别记录IRP的主类型和子类型,操作系统根据MajorFunction将IRP“派遣”到不同的派遣函数中,在派遣函数中还可以继续判断这个IRP属于哪种MinorFunction。

一、读写设备

驱动程序所创建的设备一般有三种读写方式:

1、缓冲区方式(用户模式地址与内核模式地址的数据复制)

以缓冲区方式写设备时,操作系统将writefile提供的将用户模式缓冲区复制到内核模式地址下以缓冲区方式读设备时,操作系统会分配一段内核模式下的内存,这段内存大小等于readfile或writefile指定的字节数,当IRP请求结束时这段内存地址会被复制到readfile提供的缓冲区;复制过程由操作系统负责,用户模式地址由readfile或者writefile提供,内核模式地址由操作系统负责分配和回收。

2、直接方式

直接方式读写设备,操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍,用户模式的缓冲区和内核模式的缓冲区指向同一块区域物理内存,操作系统先将用户模式的地址锁定后,操作系统用内存描述符(MDL数据结构)记录这段内存,用户模式的这段缓冲区在虚拟内存上是连续的,但是在物理内存上可能是离散的。

3、其他方式

其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址,只有驱动程序与应用程序运行在相同线程上下文的情况下,才使用这种方式,驱动程序使用用户模式地址前,需要探测这段内存是否可读或者可写。

二、I/O设备控制操作

除了ReadFile和WriteFile以外,还可以使用Win32 API DeviceIoControl操作设备,DeviceIoControl内部会使操作系统创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,然后操作系统会将这个IRP转发到派遣函数,使用DeviceIoControl定义读写还可以让程序和驱动程序进行通信。

以I/O设备控制操作为例:

得到当前I/O堆栈、得到输入缓冲区大小、得到输出缓冲区大小、得到IOCTL码、缓冲区方式、操作输出缓冲区、设置实际操作输出缓冲区长度、设置IRP完成状态、设置IRP请求操作的字节数、结束IRP请求

应用程序——>内核

CreateFile函数
HANDLE CreateFile(
  LPCSTR                lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
#define MYDEVICE L"\\\\.\\MyWDF_LINK"  //以“\\\\ .”开头并加上我们为它提供的符号链接名
HANDLE hDevice = CreateFile(MYDEVICE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

倒数第二参数不能为空,在win10下能正常运行,在win7下运行会阻塞,原因目前木知啊;

*备注:*
要检索设备句柄,必须使用设备名称或与设备关联的驱动程序名称来调用CreateFile函数。 要指定设备名称,请使用以下格式:
*\\\\.\DeviceName*

\1. 应用层调用CreateFile函数
\2. 这个函数实际上被封装到了kernel32.dll中,在这个函数中调用NtCreateFile,这就是调用ntdll.dll中的native api ,ntdll.dll中一般又两组函数——以Nt开头,以Zw开头的,这两组函数本身没有什么太大的区别。
\3. native api中通过中断 int 2eh(windows 2000 及以下),或者通过sysenter指令(windows xp及以上)进入内核,这种方式称为软中断,在产生中断时会带上一个服务号,根据服务号在ssdt表中以服务号进行查找(类似与8086中的中断机制)
\4. 根据SSDT表中记录的服务函数地址,调用相关的服务函数。
\5. 然后进入到执行组件中,对于CreateFile的操作,这个时候会调用IO管理器,IO管理器负责发起IO操作请求,并管理这些请求,主要时生成一个IRP结构,系统中有不同的管理器,主要有这样几个——虚拟内存管理器,IO管理器,对象管理器,进程管理器,线程管理器,配置管理器。
\6. 管理器生成一个IRP请求,并调用内核中的驱动,来相应这个操作,对于CreateFile来说会调用NtCreateFile函数。
\7. 最后调用内核实现部分,也就是硬件抽象层。最后由硬件抽象层操作硬件,完成打开或者创建文件的操作。

DeviceIoControl函数:
BOOL WINAPI DeviceIoControl(
  _In_        HANDLE       hDevice,             //已经打开的设备
  _In_        DWORD        dwIoControlCode,     //I/O控制码IOCTL
  _In_opt_    LPVOID       lpInBuffer,          //输入缓冲区
  _In_        DWORD        nInBufferSize,       //输入缓冲区大小
  _Out_opt_   LPVOID       lpOutBuffer,         //输出缓冲区
  _In_        DWORD        nOutBufferSize,      //输出缓冲区大小
  _Out_opt_   LPDWORD      lpBytesReturned,     //实际返回字节数
  _Inout_opt_ LPOVERLAPPED lpOverlapped         //是否OVERLAP操作
CTL_CODE(DeviceType, Function, Method, Access);
 DeviceType: 设备对象的类型,这个类型应和创建设备(IoCreateDevice)时的类型相匹配,
 Function: 这是驱动程序定义的IOCTL码
 	0X0000到0X7FFF微软自己保留;0X8000到0XFFFF程序员自己定义
 Method:操作模式
 (缓冲区METHOD_BUFFERED、
 直接写METHOD_IN_DIRECT、
 直接读METHOD_OUT_DIRECT、
 其他方式METHOD_NEITHER)
 Access:访问权限,没特殊要求,一般FILE_ANY_ACCESS
 

*返回值:*
如果操作成功完成,DeviceIoControl将返回一个非零值。

如果操作失败或正在等待,则DeviceIoControl返回零。 要获得扩展的错误信息,请调用GetLastError。

应用程序:
state = DeviceIoControl(DeviceHandle, PCIe_DMA_BUFFER_SIZE_READ,
NULL, NULL, &outBuf, 4, NULL, NULL); 
内核读操作
status = WdfRequestRetrieveOutputBuffer(
   Request,
   sizeof(ULONG64),
   &outBuffer,
if (!NT_SUCCESS(status)) {
   goto Exit;
value_u64 = READ_REGISTER_ULONG64((PULONG64)&pDevContext->DMABufferSize);
WRITE_REGISTER_ULONG64((ULONG64*)outBuffer, value_u64*pDevContext->DMABufferNum);
WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
KdPrint(("PCIe_DMA_BUFFER_SIZE_READ %llu\n", value_u64));
KdPrint(("PCIe_DMA_BUFFER_SIZE_READ OK\n"));

内核——>应用程序

内核写操作
status = WdfRequestRetrieveInputBuffer(  // 检索I/O请求的输入缓冲区
    Request,
    sizeof(ULONG),
    &inBuffer,
value = READ_REGISTER_ULONG((ULONG*)inBuffer); //计数单位为双字
KdPrint(("DMA_WRITE_SIZE %d \n", value));
WRITE_REGISTER_ULONG((PULONG)&pDevContext->Regs->RD_MEM_SIZE_L, value);
WRITE_REGISTER_ULONG((PULONG)&pDevContext->Regs->WR_MEM_SIZE_L, value); 
pDevContext->Length = value * 4; 
WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
if (!NT_SUCCESS(status)) {
	goto Exit;
KdPrint(("PCIe_DMA_WRITE_SIZE OK\n"));
应用程序:
state = DeviceIoControl(DeviceHandle, PCIe_WRITE_SIZE,
&inData, 4, NULL, NULL, NULL, NULL);
NTSTATUS WdfRequestRetrieveOutputBuffer(
  WDFREQUEST Request,
  size_t     MinimumRequiredSize,
  PVOID      *Buffer,
  size_t     *Length
NTSTATUS WdfRequestRetrieveInputBuffer(
  WDFREQUEST Request,
  size_t     MinimumRequiredLength,
  PVOID      *Buffer,
  size_t     *Length
 

将write请求的数据写入设备对象上下文,然后当read请求过来的时候,将设备上下文里面的数据读出来并且返回给用户模式程序,wdf提供两个函数,这两个函数可以获取请求的输入输出缓冲,read请求只有outptbuffer,write请求只有inputbuffer;IOcontrol请求同事拥有输入输出缓冲;

status = WdfRequestRetrieveOutputBuffer(
    Request,
    sizeof(ULONG64),
    &outBuffer,
 

这段代码获取request的输出请求,outbuffer是一个指针,如果函数调用成功,那么outbuffer将指向一个输出缓冲,指向一个内核模式下的地址,可直接读取或写入数据,read请求函数成功,write请求则失败。

上篇中,我们设备打开函数已经得到了我们PCIe设备的句柄了。接下来我们来看看,设备打开之后,上层软件是怎样利用该句柄实现对设备上具体寄存器的访问的。 1:寄存器写操作 上层应用程序写操作函数代码: /********************************************************************/ /* Write register 3 本文为看雪论精华文章看雪论坛作者ID:低调putchar 一、概述Windows操作系统中,文件系统过滤驱动的过滤设备绑定在文件系统(FSD)设备之上,监视和过滤我们的文件访问。当应用层发起文件操作调用时内核层都会转换为IRP发送到设备栈中位于栈顶的设备,然后通过IO栈单元(IO_STACK_LOCATION)保存一些参数把IRP请求继续向下层设备发送,最后至FSD,由FSD完成实际的文... 驱动程序和客户应用程序经常需要进行数据交换,但我们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操作系统必须解决两者之间的数据交换。 驱动层和应用通信,主要是靠DeviceIoControl函数,下面是该函数的原型: BOOL DeviceIoControl (  HANDLE hDevice, // 设备句柄  DWORD dwIoControlCode, // IOCTL请求 不应该以ret来判断DeviceIoControl的结果,而应该以lpBytesReturned。 READ操作,lpBytesReturned返回实际读的字节数;WRITE操作,lpBytesReturned返回实际写的字节数。 句柄, Handle, 表达处理、控制之意。内核不会直接暴露指针给用户空间,这样会增大内核风险。相反,内核抽象出Handle给用户态,不管是文件、进程、线程等对象,通过Handle可以隐藏内核细节,统一控制对象。不可避免,内核态对于Handle必须转换成指针才能处理。 windows驱动编程-应用内核通信: 在编写windows驱动的时候,用户层和内核层的通信必不可少(胡扯-…-)。本文实现,用户层发送数据给内核驱动->内核驱动保存数据->用户层再原样从内核驱动中将数据读取出来。 本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处: 在上面的两篇博文中,介绍了IRP与派遣函数,以及我们通过了一个例子“磁盘设备的绝对读写”来演示了在应用程序中是如何向一个设备发出I/O请求的。这篇博文将演示在驱动程序中处理一个非常简单的I/O请求——由DeviceIoControl这个Win32API经过一系列的调用,在内核中 前言: 最近需要对Windows中的设备进行编程操作,其中涉及到非常重要的函数DeviceIoControl,在使用的时候也比较的复杂,国内这一块中文资料比较少,在学习之余顺便将其翻译出来,以供参考,如有错误,欢迎指正。lcb0281@163点comMSDN原文地址:DeviceIoControl function DeviceIoControl 将控制代码直接发送到指定的设备驱动... IOCP 是一种基于操作系统内核的异步 I/O 实现方式,它利用操作系统内核提供的异步 I/O 接口来实现异步 I/O 操作。等待异步 I/O 操作完成。当I/O操作完成时,操作系统将完成的I/O请求从内核的I/O请求队列中取出,并将其结果信息(如读取的数据、错误码等)放入到一个称为完成包(Completion Packet)的数据结构中。通过使用GetQueuedCompletionStatus函数,应用程序可以实现高效的异步I/O操作,避免了阻塞等待I/O操作完成的情况,提高了系统的吞吐量和响应速度。