应用程序对设备 + IRP 的同步异步学习

来源:互联网 发布:达芬奇调色软件下载 编辑:程序博客网 时间:2024/06/06 05:21

对设备的的操作转换为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_OVERLAPPEDNULL );if (hDevice == INVALID_HANDLE_VALUE) {printf("Read Error\n");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_OVERLAPPEDNULL );if (hDevice == INVALID_HANDLE_VALUE) {printf("Read Error\n");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_SIZE512//假设该文件大于或等于BUFFER_SIZEVOID CALLBACK MyFileIOCompletionRoutine(  DWORD dwErrorCode,                // 对于此次操作返回的状态  DWORD dwNumberOfBytesTransfered,  // 告诉已经操作了多少字节,也就是在IRP里的Infomation  LPOVERLAPPED lpOverlapped         // 这个数据结构){printf("IO operation end!\n");}HANDLE hDevice = CreateFile("test.dat",GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPEDNULL );if (hDevice == INVALID_HANDLE_VALUE) {printf("Read Error\n");return 1;}UCHAR buffer[BUFFER_SIZE];//初始化overlap使其内部全部为零//不用初始化事件!!OVERLAPPED overlap={0};//这里设置了OVERLAP参数,因此是异步操作ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);//做一些其他操作,这些操作会与读设备并行执行//进入alterableSleepEx(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\n"));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\n"));//返回pending状态return STATUS_PENDING;}

在关闭设备时,产生IRP_MJ_CLEANUP 的IRP,其派遣函数抽取队列中每一个“挂起”   的IRP,并调用IoCompleteRequest设置完成

NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {KdPrint(("Enter HelloDDKCleanUp\n"));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的IRPNTSTATUS status = STATUS_SUCCESS;// 完成IRPpIrp->IoStatus.Status = status;pIrp->IoStatus.Information = 0;// bytes xferedIoCompleteRequest( pIrp, IO_NO_INCREMENT );KdPrint(("Leave HelloDDKCleanUp\n"));return STATUS_SUCCESS;}

应用程序代码:

HANDLE hDevice = CreateFile("\\\\.\\HelloDDK",GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPEDNULL );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\n");}bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);if (!bRead && GetLastError()==ERROR_IO_PENDING){printf("The operation is pending\n");}//迫使程序中止2秒Sleep(2000);//创建IRP_MJ_CLEANUP IRPCloseHandle(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_OVERLAPPEDNULL );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\n");}bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);if (!bRead && GetLastError()==ERROR_IO_PENDING){printf("The operation is pending\n");}//迫使程序中止2秒Sleep(2000);//显式的调用CancelIo,其实在关闭设备时会自动运行CancelIoCancelIo(hDevice);//创建IRP_MJ_CLEANUP IRPCloseHandle(hDevice);
驱动代码(省略):

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {KdPrint(("Enter HelloDDKRead\n"));PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;IoSetCancelRoutine(pIrp,CancelReadIRP); //设置取消IRP请求的回调函数//将IRP设置为挂起IoMarkIrpPending(pIrp);KdPrint(("Leave HelloDDKRead\n"));//返回pending状态return STATUS_PENDING;}VOIDCancelReadIRP(    IN PDEVICE_OBJECT DeviceObject,    IN PIRP Irp    ){KdPrint(("Enter CancelReadIRP\n"));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\n"));}
运行结果相似

























原创粉丝点击