在很多时候,某些用户需要与底层驱动有一个交互式的操作,所以需要寻找一个架构能够做到应用程程序和驱动程序进行有效的沟通,而Microsoft Windows 家族操作系统通过发送 I/O 请求数据包 (IRP) 与驱动程序通信。所以今天我们介绍Windows如何使用DeviceIoControl以及IRP进行User层和Kernel层的数据交流。
首先,为了比较完整的介绍这个部分,我们先看看一些准备工作。要访问底层驱动,我们应该先得到该驱可访问的句柄(Handle)。这里我们使用到了函数CreateFile。
HANDLE CreateFile( LPCTSTR lpFileName, // 文件名 DWORD dwDesiredAccess, // 访问方式 DWORD dwShareMode, // 共享模式 LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 设为NULL DWORD dwCreationDisposition, /// 创建方式 DWORD dwFlagsAndAttributes, // 属性 HANDLE hTemplateFile );
之后,我们在简单介绍下IOCTL,IOCTL通常用于驱动和应用程序的交流,其功能不限于读写数据。更多的时候,驱动都会定义一些列的IOCTL和数据结构来满足通信需求。简单来说,我们可把处于User Level的与IOCIL相关的东西看成一个Windows 信息,该信息通知驱动完成用户预先定义的某种请求。接下来先看看如何顶一个IOCTL. IOCTL是一个32未的数字,头两位的那个一了传输类型(METHOD_OUT_DIRECT,METHOD_IN_DIRECT
, METHOD_BUFFERED
or METHOD_NEITHER
). 2-13bits定义了Function Code,其实你大可不需要在意这么多,用一个宏可以简单帮你完成任务。这个宏就是CTL_CODE.举例:我们定义一个IOCTL,用来查询MiniPort的版本号(5.1,6.0,6.20(Win7))。
#define _NDIS_CONTROL_CODE(request,method) \ CTL_CODE(FILE_DEVICE_PHYSICAL_NETCARD, request, method, FILE_ANY_ACCESS) #define IOCTL_FILTER_QUERY_DRIVER_VERSION _NDIS_CONTROL_CODE(18, METHOD_BUFFERED)
当然我们可能还需要预定义一些数据,比如分配输入和输出的缓冲区。输入缓冲区用于传输数据给底层驱动,输出缓冲区用于保存由底层返回的数据。而这些数据(包括IOCTL Code等)都必须由函数DeviceIoControl传递传递到底层驱动。有兴趣更深入了解的可以看看IRP。因为底层驱动是从IRP的数据结构中找到用户预先定义的数据,然后由驱动调度执行,完成用户的请求。
/** Rturns Ndis version of the current driver uses @return - TRUE - success, FALSE - failure */ BOOL FilterWrapper::QueryNdisVersion() { BOOL result = FALSE; LPVOID versionfoBuffer = NULL; DWORD bytesWritten = 0; //allocate new buffer to store mac info DWORD versionLengthBuffer = (DWORD)sizeof(USHORT); versionfoBuffer = new BYTE[versionLengthBuffer]; if(versionfoBuffer == NULL) { goto Exit; } // pass a new io control to underlying driver to create a new mac if(!DeviceIoControl( m_hFilter, IOCTL_FILTER_QUERY_DRIVER_VERSION, "Request from user mode to ger ndis version.\n", sizeof("Request from user mode to ger ndis version.\n"), versionfoBuffer, versionLengthBuffer, &bytesWritten, NULL)) { printf("Can't get version\n"); goto Exit; } printf("Successfylly get ndis version! And Version Info:\n"); if(!ParseVersionBuffer(versionfoBuffer, bytesWritten)) goto Exit; result = TRUE; Exit: delete[] versionfoBuffer; return result;; }
接下来,我们看看底层驱动是如何调度执行的。刚才说过了,所有的数据都会保存在一个叫做IRP的数据结构里,并且完成状态也是保存在IRP结构中。
NTSTATUS FilterDeviceIoControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PIO_STACK_LOCATION IrpSp; NTSTATUS Status = STATUS_SUCCESS; PFILTER_DEVICE_EXTENSION FilterDeviceExtension; PUCHAR InputBuffer; PUCHAR OutputBuffer; ULONG InputBufferLength, OutputBufferLength; PLIST_ENTRY Link; PUCHAR pInfo; ULONG InfoLength = 0; PMS_FILTER pFilter = NULL; UINT iCnt = 0, iPCnt = 0; PUCHAR temp; //Following variables are add by leyond NDIS_OID Oid; USHORT VersionBuffer = 0;//USHORT for getting version ULONG MethodId = 0; ULONG BytesProcessed = 0; UNREFERENCED_PARAMETER(DeviceObject); DEBUGP(DL_TEST,("==>Filter IO Control dispatch\n")); IrpSp = IoGetCurrentIrpStackLocation(Irp); if (IrpSp->FileObject == NULL) { return(STATUS_UNSUCCESSFUL); } FilterDeviceExtension = (PFILTER_DEVICE_EXTENSION)NdisGetDeviceReservedExtension(DeviceObject); ASSERT(FilterDeviceExtension->Signature == 'FTDR'); Irp->IoStatus.Information = 0; switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_FILTER_QUERY_DRIVER_VERSION://get ndsi version InputBuffer = OutputBuffer = (PUCHAR)Irp->AssociatedIrp.SystemBuffer; InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength; OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength; //pInfo = OutputBuffer; DEBUGP(DL_TEST,("The input length is %u, and inputdata is %s ",InputBufferLength,InputBuffer)); InfoLength = sizeof(USHORT); NdisZeroMemory(OutputBuffer, OutputBufferLength); Link = FilterModuleList.Flink; pFilter = CONTAINING_RECORD(Link, MS_FILTER, FilterModuleLink); /*Check Version */ Oid = OID_GEN_DRIVER_VERSION; Status = filterDoInternalRequest(pFilter, NdisRequestQueryInformation, Oid, &VersionBuffer, sizeof(VersionBuffer), sizeof(VersionBuffer), MethodId, &BytesProcessed); if(Status == NDIS_STATUS_SUCCESS) { DEBUGP(DL_TEST,("Get Ndis Version successfully!\n")); VersionHighByte = (VersionBuffer & 0xFF00) >> 8; VersionLowByte = (VersionBuffer & 0x00FF); DEBUGP(DL_TEST,("Ndis version:High %d, and Low: %d\n",VersionHighByte,VersionLowByte)); if(InfoLength <= OutputBufferLength) { temp = OutputBuffer; *(PUSHORT)OutputBuffer += VersionBuffer; OutputBuffer += sizeof(USHORT); OutputBuffer = temp; }else { Status = STATUS_BUFFER_TOO_SMALL; } Irp->IoStatus.Information = InfoLength; }else{ DEBUGP(DL_TEST,("Get Version Fail\n")); } break; default: break; } Irp->IoStatus.Status = Status; Irp->IoStatus.Information = InfoLength; IoCompleteRequest(Irp, IO_NO_INCREMENT); DEBUGP(DL_TEST,("Filter IO Control dispatch<====\n")); return Status; }
总会调用IoCompleteRequest来结束IRP,返回执行结果给用户。
大概就是这样一个简单的介绍~