对设备的的操作转换为IRP请求,而一般IRP都是由操作系统异步发送的。
异步处理IRP有助于提高效率,但有时会导致逻辑错误,需要将异步的IRP进行同步化
StartIOl例程,使用中断服务例程等。
应用程序对设备的同步+异步操作
大部分IRP是由应用=程序的WIN32 API 函数发起的。这些API EG: ReadFile,WriteFile,DeviceIOControl 等 都有同步异步操作
同步操作(等待继续·····)
HANDLE CreateFile( LPCTSTR lpFileName, // file name DWORD dwDesiredAccess, // access mode DWORD dwShareMode, // share mode LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD DWORD dwCreationDisposition, // how to create DWORD dwFlagsAndAttributes, // file attributes ////////下面那个参数/////////////////////// HANDLE hTemplateFile // handle to template file );FILE_FLAG_OVERLAPPED为异步标志
BOOL ReadFile( HANDLE hFile, // handle to file LPVOID lpBuffer, // data buffer DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // number of bytes read LPOVERLAPPED lpOverlapped // overlapped buffer );If hFile was not opened with FILE_FLAG_OVERLAPPED and lpOverlapped is NULL 同步操作
演示代码:
HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,//此处没有设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Read Error "); return 1; } UCHAR buffer[BUFFER_SIZE]; DWORD dwRead; ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,NULL);//这里没有设置OVERLAP参数 CloseHandle(hDevice);
异步操作设备1(不等待 继续运行·····)
typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; DWORD Offset; //指定一个偏移量,设备的偏移量开始读取 64位整形表示,参数是该参数的32位整形 DWORD OffsetHigh; //偏移量的高32位 HANDLE hEvent; //这个事件用于该操作完成后通知应用程序,可以初始化改事件为未激发,当操作设备结束后,驱动调用IoCompleteRequest后,设置该设备为激发态 } OVERLAPPED;演示代码:
HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Read Error "); return 1; } UCHAR buffer[BUFFER_SIZE]; DWORD dwRead; //初始化overlap使其内部全部为零 OVERLAPPED overlap={0}; //创建overlap事件 自动事件 overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); //这里没有设置OVERLAP参数,因此是异步操作 ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap); //做一些其他操作,这些操作会与读设备并行执行 //等待读设备结束 WaitForSingleObject(overlap.hEvent,INFINITE);异步操作设备2(不等待 继续运行·····)
除了 ReadFile WriteFile 外,还有 ReadFileEx + WriteFileEx 函数
不同的是 Ex只支持 异步读写操作的
BOOL ReadFileEx( HANDLE hFile, // handle to file LPVOID lpBuffer, // data buffer DWORD nNumberOfBytesToRead, // number of bytes to read LPOVERLAPPED lpOverlapped, // offset //指针 !!不需要提供事件句柄 LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine //完成例程 );驱动程序在读操作后,会通过调用 上面那个回调例程。 类似一个软中断
WINDOWS将这种机制称为 异步过程调用 APC asynchronousProcedure
回调函数被调用条件 只有先成功处于警惕(alert) 状态,回调函数才有可能被调用。有多个API可以使系统进程警惕状态 SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectsEx函数等
当系统进入警惕模式后,操作系统会枚举当前线程的APC队列。驱动程序一旦结束读写操作,就会把ReadFileEx提供的的完成例程插入到APC队列
回调例程会报告本次操作的完成状况 + 本次读取操作实际读取的字节数等。
VOID CALLBACK FileIOCompletionRoutine( //一般回调例程的声明 DWORD dwErrorCode, // completion code //读取错误,会返回错误码 DWORD dwNumberOfBytesTransfered, // number of bytes transferred //返回实际读取操作的字节数 LPOVERLAPPED lpOverlapped // I/O information buffer //OVERLAP参数,指定读取的偏移量等信息 );下面是演示代码:
#define DEVICE_NAME "test.dat" #define BUFFER_SIZE 512 //假设该文件大于或等于BUFFER_SIZE VOID CALLBACK MyFileIOCompletionRoutine( DWORD dwErrorCode, // 对于此次操作返回的状态 DWORD dwNumberOfBytesTransfered, // 告诉已经操作了多少字节,也就是在IRP里的Infomation LPOVERLAPPED lpOverlapped // 这个数据结构 ) { printf("IO operation end! "); } HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Read Error "); return 1; } UCHAR buffer[BUFFER_SIZE]; //初始化overlap使其内部全部为零 //不用初始化事件!! OVERLAPPED overlap={0}; //这里设置了OVERLAP参数,因此是异步操作 ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine); //做一些其他操作,这些操作会与读设备并行执行 //进入alterable SleepEx(0,TRUE); CloseHandle(hDevice);
IRP的同步完成 + 异步完成
上面的这些操作必须得到驱动程序的支持,有两种方式处理IRP请求:
1)在派遣函数中直接结束IRP请求,可以认为是一种同步处理
2)不结束IRP请求,让派遣函数直接返回, IRP在以后的某个时候再进行处理。
对设备读取3个方法:
1)ReadFIle 如果同步读取时,创建一个事件(IRP 的 UserEvent),派遣函数调用IoCompleteRequest时内部会设置IRP的UserEvent
如果派遣函数没有调用IoCompleteRequest函数,那么ReadFile一直等待
2)ReadFile 异步处理,内部不会创建事件,但ReadFile函数会接受 overlap参数。参数会提供一个事件,被用作 同步处理
IoCompleteRequest内部设置overlap 提供的事件
在ReadFile函数退出之前不会检测该事件是否被设置,因此可以不等待操作是否真的被完成
当IRP操作被完成后,overlap提供的事件被设置,通知应用程序IRP请求被完成
3)ReadFileEx 异步读取时,不提供事件,但提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给派遣函数
IoCompleteRequest会将这个完成函数插入APC队列
应用程序只要进入警惕模式,APC队列会自动出队列,完成函数会被执行,这相当于通知应用程序操作已经完成
前几次所有例子都是 同步读取,IRP的同步处理在派遣函数中,将IRP处理完毕,这里指的完毕就是调用IoCompleteRequrst函数
IRP的异步完成:
指的就是不在派遣函数中调用IoCompleteRequest内核函数,调用IoCompleteRquest函数意味着IRP请求的结束,也标志着本次对设备操作的结束
借用上面的例子~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1)没有调用IoCompleteRequest,IRP请求没有被结束。ReadFIle会一直等待,知道操作被结束
2)没有调用IoCompleteRequest,IRP请求没有被结束。ReadFile会立刻返回失败,GetLastError函数得知 ERROR_IO_PENDING 不是真正的操作错误
3)没有调用IoCompleteRequest,IRP请求没有被结束。立刻返回FALSE,跟(2)一样,表明当前操作被“挂起”。
下面介绍如何将“挂起”的IRP插入队列,并在关闭设备的时候将“挂起”的IRP结束:
如果不调用 IoC~R~函数,则需要告诉操作系统处于“挂起”状态。
VOID IoMarkIrpPending( //告诉操作系统处于挂起状态 同时返回STATUS_PENDING IN OUT PIRP Irp );
NTSTATUS HelloDDKRead(IN PDEV0CE_OBJECT pDevObj, IN PIRP pIrp) { IoMarkIrkPending(pIrp); return STATUS_PENDING; }队列的数据结构:
typedef struct _MY_IRP_ENTRY { PIRP pIrp; //记录IRP指针 LIST_ENTRY ListEntry; }MY_IRP_ENTRY,*PMY_IRP_ENTRY;在设备扩展力加入 “队列” 这个变量。所有派遣函数都可以使用这个队列
DriverEntry中初始化 该队列 DriverUnload回收该队列
在IRP_MJ_READ的派遣函数中,将IRP插入堆栈,然后返回“挂起”状态:
异步处理IRP的派遣函数:
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { KdPrint(("Enter HelloDDKRead ")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; PMY_IRP_ENTRY pIrp_entry = (PMY_IRP_ENTRY)ExAllocatePool(PagedPool,sizeof(MY_IRP_ENTRY)); pIrp_entry->pIRP = pIrp; //插入队列 InsertHeadList(pDevExt->pIRPLinkListHead,&pIrp_entry->ListEntry); //将IRP设置为挂起 IoMarkIrpPending(pIrp); KdPrint(("Leave HelloDDKRead ")); //返回pending状态 return STATUS_PENDING; }
在关闭设备时,产生IRP_MJ_CLEANUP 的IRP,其派遣函数抽取队列中每一个“挂起” 的IRP,并调用IoCompleteRequest设置完成
NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { KdPrint(("Enter HelloDDKCleanUp ")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; //(1)将存在队列中的IRP逐个出队列,并处理 PMY_IRP_ENTRY my_irp_entry; while(!IsListEmpty(pDevExt->pIRPLinkListHead)) { PLIST_ENTRY pEntry = RemoveHeadList(pDevExt->pIRPLinkListHead); my_irp_entry = CONTAINING_RECORD(pEntry, MY_IRP_ENTRY, ListEntry); my_irp_entry->pIRP->IoStatus.Status = STATUS_SUCCESS; my_irp_entry->pIRP->IoStatus.Information = 0; // bytes xfered IoCompleteRequest( my_irp_entry->pIRP, IO_NO_INCREMENT ); ExFreePool(my_irp_entry); } //(2)处理IRP_MJ_CLEANUP的IRP NTSTATUS status = STATUS_SUCCESS; // 完成IRP pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; // bytes xfered IoCompleteRequest( pIrp, IO_NO_INCREMENT ); KdPrint(("Leave HelloDDKCleanUp ")); return STATUS_SUCCESS; }
应用程序代码:
HANDLE hDevice = CreateFile("\\.\HelloDDK", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Open Device failed!"); return 1; } OVERLAPPED overlap1={0}; OVERLAPPED overlap2={0}; UCHAR buffer[10]; ULONG ulRead; BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1); if (!bRead && GetLastError()==ERROR_IO_PENDING) { printf("The operation is pending "); } bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2); if (!bRead && GetLastError()==ERROR_IO_PENDING) { printf("The operation is pending "); } //迫使程序中止2秒 Sleep(2000); //创建IRP_MJ_CLEANUP IRP CloseHandle(hDevice);所以 当应用程序 异步读设备时,创建两个IRP_MJ_READ,这两个IRP被插入队列,显示 Pending
停顿2秒 关闭设备的时候,导致驱动程序调用IRP_MG_CLEANUP的派遣函数 显示 Completed
下面介绍将“挂起”的IRP逐个结束,取消IRP请求:
PDRIVER_CANCEL IoSetCancelRoutine( //可以设置取消IRP请求的回调函数 IN PIRP Irp, //需要取消的IRP请求的回调函数 IN PDRIVER_CANCEL CancelRoutine //取消函数的函数指针,一旦IRP请求被取消的时候,操作系统会调用这个取消函数 );可以将一个取消例程与该IRP关联,一旦取消IRP请求的时候,这个取消例程会被执行
这个函数也可以用来删除取消例程,当输入的 第二指针为空, 则删除原来设置的取消例程
BOOLEAN IoCancelIrp( //指针取消IRP请求,zai IoCancelIrp内部,需要进行同步。 DDK在IoCancelIrp内部使用一个叫做cancel的自旋锁用来进行同步 IN PIRP Irp );
在内部首先得到该自旋锁,IoCancelIrp会调用 取消回调 例程,因此,释放该自旋锁的任务就留给了取消回调例程。
VOID IoAcquireCancelSpinLock( //获得取消自旋锁 OUT PKIRQL Irql );
VOID IoReleaseCancelSpinLock( //释放取消自旋锁 IN KIRQL Irql );
BOOL CancelIo( HANDLE hFile // handle to file // 这个是WIN32 API 取消IRP请求 内部会调用所有没有被完成的IRP,然后依次调用IoCancelIrp.如果没有调用它, 在关闭设备同意自动调用CancelIo );
应用程序代码:
HANDLE hDevice = CreateFile("\\.\HelloDDK", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Open Device failed!"); return 1; } OVERLAPPED overlap1={0}; OVERLAPPED overlap2={0}; UCHAR buffer[10]; ULONG ulRead; BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1); if (!bRead && GetLastError()==ERROR_IO_PENDING) { printf("The operation is pending "); } bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2); if (!bRead && GetLastError()==ERROR_IO_PENDING) { printf("The operation is pending "); } //迫使程序中止2秒 Sleep(2000); //显式的调用CancelIo,其实在关闭设备时会自动运行CancelIo CancelIo(hDevice); //创建IRP_MJ_CLEANUP IRP CloseHandle(hDevice);驱动代码(省略):
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { KdPrint(("Enter HelloDDKRead ")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; IoSetCancelRoutine(pIrp,CancelReadIRP); //设置取消IRP请求的回调函数 //将IRP设置为挂起 IoMarkIrpPending(pIrp); KdPrint(("Leave HelloDDKRead ")); //返回pending状态 return STATUS_PENDING; } VOID CancelReadIRP( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { KdPrint(("Enter CancelReadIRP ")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension; //设置完成状态为STATUS_CANCELLED Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; // bytes xfered IoCompleteRequest( Irp, IO_NO_INCREMENT ); //释放Cancel自旋锁 IoReleaseCancelSpinLock(Irp->CancelIrql); //一定要释放cancel自旋锁!!否则系统崩溃,另外cancel自旋锁是全局自旋锁, 所有驱动程序都会使用这个自旋锁,因此自旋锁时间不宜过长 KdPrint(("Leave CancelReadIRP ")); }运行结果相似