设为首页 收藏本站
查看: 1267|回复: 0

[经验分享] Windows驱动开发技术详解__IRP的同步

[复制链接]

尚未签到

发表于 2015-12-16 08:35:43 | 显示全部楼层 |阅读模式
Windows驱动开发技术详解__IRP的同步
http://blog.csdn.net/aksnzhy/article/details/6847969对设备的任何操作都会最终转化为IRP请求,而IRP一般都是由操作系统异步发送的。异步处理IRP有助于提高效率,但是有时异步处理会带来逻辑上的错误,这时需要将异步的IRP同步化。将IRP同步化的方法有StartIO例程,使用中断服务例程等。


应用程序对设备的同步异步操作
大部分IRP都是由应用程序的Win32 API函数发起的。这些Win32 API本身就支持同步和异步的操作。例如:ReadFile,WriteFile和DeviceIoControl等,这些都有两种操作方式。一种同步,一种异步。


1.同步操作和异步操作的原理
操作设备的Win32 API主要是三个函数,即ReadFile函数,WriteFile函数,DeviceIOControl函数。以DeviceIOControl函数为例,当应用程序调用DeviceIoControl函数时,它的内部会创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,并将这个IRP传送到驱动程序的派遣函数中。处理该IRP需要一段时间,直到IRP处理完毕后,DeviceIOControl函数才会返回。
同步操作时,在DeviceIOControl的内部,会调用WaitForSingleObject函数去等待一个事件。这个事件直到IRP被结束时,才会被触发。如果通过反汇编IoCompleteRequest内核函数,就会发现IoComplpeteRequest内部设置了该事件。DeviceIOControl会暂时进入睡眠状态,直到IRP被结束。
而对于异步操作的情况下,当DeviceIOControl被调用时,其内部会产生IRP,并将IRP传递给驱动程序的内部派遣函数。但此时DeviceIOControl不会等待该IRP的结束,而是直接返回。当IRP经过一段时间被结束时,操作系统会触发一个IRP相关事件。这个事件可以通知应用程序IRP请求被执行完毕。


2.同步操作设备
如果需要同步操作设备,在打开设备的时候就要制定以“同步”的方式打开设备。打开设备用CreateFile函数,其函数声明如下:





[cpp] view plaincopy

  • HANDLE  
  •   CreateFile(  
  •                 LPCSTR lpFileName,                                  //设备名  
  •         DWORD dwDesiredAccess,                              //访问权限  
  •         DWORD dwShareMode,                                  //共享模式  
  •         LPSECURITY_ATTRIBUTES lpSecurityAttributes,         //安全属性  
  •         DWORD dwCreationDisposition,                        //如何创建  
  •         DWORD dwFlagsAndAttributes,                         //设备属性  
  •         HANDLE hTemplateFile                                //文件模板  
  •     );  

其中第六个参数dwFlagsAndAttributes是同步异步操作的关键。如果这个参数没有设置FILE_FLAG_OVERLAPPED,则以后对该设备的操作都是同步操作,否则都是异步操作。



对设备的操作Win32 API,例如ReadFile,WriteFile和DeviceIOControl函数,都会提供一个OVERLAP参数,如ReadFile函数:





[cpp] view plaincopy

  • BOOL ReadFile(  
  •     HANDLE hFile,  
  •     LPVOID lpBuffer,  
  •     DWORD nNumberOfBytesToRead,   
  •     LPDWORD lpNumberOfBytesRead,  
  •     LPOVERLAPPED lpOverlapped   
  • );  

在同步操作设备时,其lpOverlapped参数设置为NULL。下面代码演示了应用程序如何对设备进行同步读取:








[cpp] view plaincopy

  • #include   
  • #include   
  •   
  • #define BUFFER_SIZE 512  
  • int main()  
  • {  
  •     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\n");  
  •         return 1;  
  •     }  
  •   
  •     UCHAR buffer[BUFFER_SIZE];  
  •     DWORD dwRead;  
  •     ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,NULL);//这里没有设置OVERLAP参数  
  •   
  •     CloseHandle(hDevice);  
  •   
  •     return 0;  
  • }  






3.异步操作设备(方式一)
异步操作设备时主要需要OVERLAP参数,Windows中用一种数据结构OVERLAPPED表示。



[cpp] view plaincopy

  • typedef struct _OVERLAPPED  
  • {  
  •     ULONG_PTR Internal;  
  •     ULONG_PTR InternalHigh;  
  •     DWORD Offset;  
  •     DWORD OffsetHigh;  
  •     HANDLE hEvent;  
  • }OVERLAPPED;  




第三个参数Offset:操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量用一个64位整型表示,Offset就是偏移量的低32位。
第四个参数OffsetHigh是偏移量的高32位。
第五个参数hEvent:这个事件用于该操作完成后通知应用程序。程序员可以初始化该事件为未激发,当操作设备结束后,即在驱动程序中调用IoCompleteRequest后,设置该事件为激发态。
在使用OVERLAPPED结构前,要先对其内部清零,并为其创建事件。
下面代码演示如何在应用程序中使用异步操作:





[cpp] view plaincopy

  • #include   
  • #include   
  •   
  • #define BUFFER_SIZE 512  
  • //假设该文件大于或等于BUFFER_SIZE  
  •   
  • #define DEVICE_NAME "test.dat"  
  • int main()  
  • {  
  •     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\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);  
  •   
  •     CloseHandle(hDevice);  
  •   
  •     return 0;  
  • }  






4.异步操作设备(方式二)
除了ReadFile和WriteFile函数外,还有两个API也可以实现异步读写,这就是ReadFileEx和WriteFileEx函数。ReadFile和WriteFile既可以支持同步读写操作,又可以支持异步读写操作。而ReadFileEx和WriteFileEx函数时专门用于异步操作的,不能进行同步读写。ReadFileEx的声明如下:





[cpp] view plaincopy

  • ReadFileEx(  
  •         HANDLE hFile,  
  •         LPVOID lpBuffer,  
  •         DWORD nNumberOfBytesToRead,  
  •         LPOVERLAPPED lpOverlapped,   
  •         LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine  
  •     );  

第一个参数hFile:要操作的设备句柄



第二个参数lpBuffer:读入数据的缓冲区
第三个参数nNumberOfBytesToRead:需要读取的字节数
第四个参数lpOverlapped:一个OVERLAPPED指针
第五个参数lpComletionRoutine:完成历程
需要注意的是,这里提供的OVERLAPPED不需要提供事件句柄。ReadFileEx将读请求传递到驱动程序后立刻返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调历程(CALL BACK FUNCTION)。这类似一个软中断,也就是当读操作结束后,系统立刻回调ReadFileEx提供的回调历程。Windows将这种机制称为异步过程调用(APC AsynchronousProcedureCall)
然后,APC的回调函数被调用是有条件的。只有线程处于警惕状态(Alert)时,回调函数才有可能被调用。有多个API可以使系统进入警惕状态,如SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectEx函数等。这些Win32 API都会有一个BOOL型的参数bAlertable,当设置TRUE时,就进入警惕模式。
当系统进入警惕模式后,操作系统会枚举当前线程的APC队列。驱动程序一旦结束读取操作,就会把ReadFileEx提供的完成历程插入到APC队列。
回调历程会报告本次操作的完成状况,比如是成功或是失败。同时会报告本次读取操作实际读取字节数等。下面是一般回调历程的声明:





[cpp] view plaincopy

  • VOID CALLBACK FileIOCompletionRoutine  
  • (  
  • DWORD dwErrorCode,  
  • DWORD dwNumberOfBytesTransfered,  
  • LPOVERLAPPED lpOverlapped  
  • );  

第一个参数dwErrorCode:如果读取错误,会返回错误码



第二个参数dwNumberOfBytesTransfered:返回实际读取操作的字节数
第三个参数lpOverlapped:OVERLAP参数,指定读取的偏移量等信息
下面代码演示如何在应用程序中使用ReadFileEx进行异步读操作:





[cpp] view plaincopy

  • #include   
  • #include   
  •   
  • #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!\n");  
  • }  
  •   
  • int main()  
  • {  
  •     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\n");  
  •         return 1;  
  •     }  
  •   
  •     UCHAR buffer[BUFFER_SIZE];  
  •   
  •     //初始化overlap使其内部全部为零  
  •     //不用初始化事件!!  
  •     OVERLAPPED overlap={0};  
  •   
  •     //这里没有设置OVERLAP参数,因此是异步操作  
  •     ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);  
  •   
  •     //做一些其他操作,这些操作会与读设备并行执行  
  •   
  •     //进入alterable  
  •     SleepEx(0,TRUE);  
  •   
  •     CloseHandle(hDevice);  
  •   
  •     return 0;  
  • }  






IRP的同步和异步完成


1.IRP的同步完成
下面介绍Win32 API函数是如何一层层通过调用进入到派遣函数的。


(1)在应用程序中调用CreateFile Win32 API函数,这个函数用于打开设备
(2)CreateFile Win32 API函调用ntdll.dll中的NtCreateFile函数
(3)ntdll.dll中的NtCreateFile函数进入内核模式,然后调用ntoskrnl.exe中的NtCreateFile函数
(4)内核模式中ntoskrnl.exe的NtCreateFile函数创建IRP_MJ_CREATE类型的IRP,然后调用相应驱动程序的派遣函数,并将IRP的指针传递给该派遣函数
(5)派遣函数调用IoCompleteRequest,将IRP结束
(6)操作系统按原路返回,一直退到CreateFile Win32 API函数。至此CreateFile函数返回
(7)如果需要读取设备,应用程序调用ReadFile Win32 API函数
(8)ReadFile Win32 API调用ntdll.dll中的NtReadFile函数
(9)NtReadFile函数进入内核模式,调用ntoskenl.exe中的NtReadFile函数
(10)ntoskrnl.exe中的NtReadFile函数创建IRP_MJ_READ类型的IRP,并将其传入相应的派遣函数中
对设备进行读取可以有三种方法,第一种方式是用ReadFile函数进行同步读取,第二种是通过ReadFile方式进行异步读取,第三种方法是用ReadFileEx函数进行异步读取。
如果是用ReadFile进行同步读取时:
(1)ReadFile函数内部会创建一个事件,这个事件连同IRP一起被传到派遣函数中(这个事件是IRP的UserEvent子域)
(2)派遣函数用IoCompleteRequest时,IoCompleteRequest内部会设置IRP的UserEvent事件
(3)操作系统按照原路一只返回到ReadFile函数,ReadFile等待这个事件,因为该事件已经被设置,所以无需等待
(4)如果在派遣函数中没有调用IoCompleteRequest函数,该事件就没有被设置,ReadFile会一直等IRP结束
如果使用ReadFile进行异步读取:
(1)这时,ReadFile内部不会创建事件,但ReadFile函数会接受overlap参数,overlap参数中会提供一个事件,这个事件被用作同步处理
(2)IoCompleteRequest内不会设置overlap提供的事件
(3)在ReadFile函数退出前后,它不会检测该事件是否被设置,因此可以不等待操作是否被完成
(4)当IRP操作被结束后,overlap提供的事件被设置,这个事件会通知应用程序IRP请求被完成
如果使用ReadFileEx函数进行异步读取:
(1)ReadFileEx不提供事件,但是提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给驱动程序
(2)IoCompleteRequest会将这个函数插入APC队列
(3)应用程序只要进入警惕模式,APC队列会自动出队列,完成函数会被执行,这相当于通知应用程序操作已完成


2.IRP的异步完成
IRP被“异步完成”指的是不在派遣函数中调用IoCompleteRequest内核函数。调用IoCompleteRequest意味着IRP请求的结束,也标志着本次对设备操作的结束。
IRP是被异步完成,而发起IRP的应用程序会有三种发起IRP的形式,分别是ReadFile同步读取,ReadFile异步读取,ReadFileEx异步读取。


IRP是由ReadFile的同步操作引起的:当派遣函数退出时,由于IoCompleteRequest没有被调用,IRP请求没有被结束,ReadFile会一直等待,知道操作结束。
IRP是由ReadFile的异步操作引起的:当派遣函数退出时,由于IoCompleteRequest没有被调用,IRP请求没有被结束,但是ReadFile会立刻返回,返回值为失败,但代表操作没有完成。通过调用GetLastError函数,可以得到这时的错误代码是ERROR_IO_PENDING。这不是真正的错误,而是意味着ReadFile并没有真正完成操作,ReadFile只是异步返回。当IRP请求被真正的结束,即调用了IoCompleteRequest,ReadFile提供的overlap的事件才会被设置。这个事件可以通知了应用程序ReadFile的请求真正被执行完毕。
IRP是由ReadFileEx操作异步引起的:ReadFileEx会立刻返回,但是返回值是FALSE,说明操作没有成功。调用GetLastError会发现错误码是ERROR_IO_PENDING,表明当前操作被“挂起”。当IRP结束后,ReadFileEx提供的回调历程会被插入到APC队列中。一旦操作系统进入警惕状态,线程的APC队列会自动出列。


如果派遣函数不调用IoCompleteRequest函数,则需要告诉操作系统此IRP处于“挂起”状态。这需要调用内核函数IoMarkIrpPending。同时,派遣函数应该返回STATUS_PENDING。下面代码演示了派遣函数异步处理IRP。




[cpp] view plaincopy

  • 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_READ被挂起,这里使用一个队列,也就是把每个挂起的IRP_MJ_READ的指针都插入队列,最后IRP_MJ_CLEANUP的派遣函数将一个个IRP出队列,并且调用IoCompleteRequest函数将他们结束。

首先要定义队列的数据结构




[cpp] view plaincopy

  • typedef struct _MY_IRP_ENTRY  
  • {  
  •     PIRP pIRP;  
  •     LIST_ENTRY ListEntry;  
  • } MY_IRP_ENTRY, *PMY_IRP_ENTRY;  


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




[cpp] view plaincopy

  • NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)  
  • {  
  •     KdPrint(("Enter HelloDDKCleanUp\n"));  
  •   
  •     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  •         pDevObj->DeviceExtension;  
  •   
  •     //将存在队列中的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,  
  •                                   LIST_ENTRY);  
  •         my_irp_entry->pIRP->IoStatus.Status = STATUS_SUCCESS;  
  •         my_irp_entry->pIRP->IoStatus.Information = 0;  
  •         IoCompleteRequest(my_irp_entry->pIRP,IO_NO_INCREMENT);  
  •   
  •         ExFreePool(my_irp_entry);  
  •     }  
  •     //处理IRP_MJ_CLEANUP的IRP  
  •     NTSTATUS status = STATUS_SUCCESS;  
  •     //完成IRP  
  •     pIrp->IoStatus.Status = status;  
  •     pIrp->IoStatus.Information = 0;  
  •     IoCompleteRequest(pIrp,IO_NO_INCREMENT);  
  •   
  •     KdPrint(("Leave HelloDDKCleanUp\n"));  
  •     return STATUS_SUCCESS;  
  • }  


3.取消IRP

除了将“挂起”的IRP插入队列,并在关闭设备时,将“挂起”的IRP结束,还有另外一个办法可以将“挂起”的IRP逐个结束,这就是取消IRP请求。内核函数IoSetCancelRoutine可以设置取消IRP请求的回调函数,其声明如下:




[cpp] view plaincopy

  • PDRIVER_CANCEL  
  •     IoSetCancelRoutine(  
  •          IN PIRP Irp,  
  •          IN PDRIVER_CANCEL CancelRoutine  
  •     );  


第一个参数Irp:这个参数是需要取消的IRP

第二个参数CancelRoutine:这个是取消函数的函数指针。一旦IRP取消的时候,操作系统会调用这个取消函数。

返回值:标志是否操作成功

下面代码演示如何编写取消历程:




[cpp] view plaincopy

  • VOID  
  • CancelReadIRP(  
  •     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;  
  •     IoCompleteRequest(Irp,IO_NO_INCREMENT);  
  •   
  •     //释放Cancel自旋锁  
  •     IoReleaseCancelSpinLock(Irp->CancelIrql);  
  •     KdPrint(("Leave CancelReadIRP\n"));  
  • }  
  •   
  • NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)  
  • {  
  •     KdPrint(("Enter HelloDDKRead\n"));  
  •     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  •         pDevObj->DeviceExtension;  
  •   
  •     IoSetCancelRoutine(pIrp,CancelReadIRP);  
  •     //将IRP设置为挂起  
  •     IoMarkIrpPending(pIrp);  
  •   
  •     KdPrint(("Leave HelloDDKRead\n"));  
  •     //返回pending状态  
  •     return STATUS_PENDING;  
  • }  


StrartIO历程

StartIO历程能够保证各个并行的IRP顺序执行,即串行化。


1.并行执行与串行执行
在很多情况下,对设备的操作必须是串行执行而不是并行执行。因此,驱动程序有必要讲并行的请求变换成串行请求。这需要用到队列,如果想依次处理每个IRP,必须采用队列将处理串行化。采用原则是“先来先服务”。
当一个新的IRP请求到来时,首先检查设备是否处于“忙”状态。设备在初始化的时候设置为“空闲”状态。当设备处于“空闲”状态时,可以处理一个IRP得请求,并改变当前设备为“忙”状态。如果设备处于“忙”状态,则将新来的IRP插入队列,并立刻返回,IRP留在以后处理。
当设备由“忙”转向“空闲”状态时,则从队列取出一个IRP进行处理,并重新将状态变为“忙”。
DDK为程序员提供了一个内部队列,并将IRP用StartIO历程串行处理。

2.StartIO历程
操作系统为程序员提供了一个IRP队列来实现串行,这个队列用KDEVICE_QUEUE数据结构表示:



[cpp] view plaincopy

  • typedef struct _KDEVICE_QUEUE  
  • {  
  •     CSHORT Type;  
  •     CSHORT Size;  
  •     LIST_ENTRY DeviceListHead;  
  •     KSPIN_LOCK Lock;  
  •     BOOLEAN Busy;  
  • }KDEVICE_QUEUE,*PKDEVICE_QUEUE,*RESTRICTED_POINTER;  

这个队列的列头保存在设备对象Device_Object->DeviceQueue子域中。插入和删除队列中的元素都是由操作系统负责的。在使用这个队列的时候,需要向系统提供一个StartI例程,并将这个函数的函数名传递给操作系统,代码如下:




[cpp] view plaincopy

  • extern "C" NTSTATUS DriverEntry(  
  •                         IN PDRIVER_OBJECT pDriverObject,  
  •                         IN PUNICODE_STRING pRegistryPath  
  •                                 )  
  • {  
  •          //.....  
  •          //设置StartIO例程  
  •     pDriverObject->DriverStartIo = HelloDDKStartIO;  
  •     //.....  
  • }  

这个StartIO历程运行在DISPATCH_LEVEL级别,要在声明时加上#pragram LOCKEDCODE,因此这个例程是不会被线程所打断的。




[cpp] view plaincopy

  • #pragma LOCKEDCODE  
  • VOID HelloDDKStartIO(  
  •             IN PDEVICE_OBJECT DeviceObject,  
  •             IN PIRP Irp  
  •                      )  
  • {  
  •     //...........  
  • }  

派遣函数如果想把IRP串行化,只需要加入IoStartPacket函数,就可以将IRP插入队列。并且IoStartPacket函数还可以让程序员指定其取消例程。IoSatrtPacket首先判断当前设备处于“忙”还是“空闲”状态。如果设备“空闲”,则提升当前IRQL到DISPATCH_LEVEL级别,并进入StartIO例程“串行”处理该IRP。如果设备“忙”,则将IRP插入后返回。

在StartIO例程结束前,应该调用IoStartNextPacket函数,其作用是从队列中抽取下一个IRP,并将这个IRP作为参数调用StartIO例程。

3.示例
在使用StartIO例程时,需要IRP的派遣函数返回挂起状态,然后调用IoStartPacket内核函数。下面代码演示了如何编写这样的派遣函数



[cpp] view plaincopy

  • NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)  
  • {  
  •     KdPrint(("Enter HelloDDKRead\n"));  
  •   
  •     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  •            pDevObj->DeviceExtension;  
  •   
  •     //将IRP设置为挂起  
  •     IoMarkIrpPending(pIrp);  
  •   
  •     //将IRP插入系统队列  
  •     IoStartPacket(pDevObj,pIrp,0,OnCancelIRP);  
  •   
  •     KdPrint(("Leave HelloDDKRead\n"));  
  •   
  •     //返回pending状态  
  •     return STATUS_PENDING;  
  • }  

在派遣函数中调用IoStartPacket内核函数指定取消例程。下面代码演示了如何编写取消例程




[cpp] view plaincopy

  • VOID OnCancelIRP(IN PDEVICE_OBJECT DeviceObject,  
  •                  IN PIRP Irp)  
  • {  
  •     KdPrint(("Enter CancelReadIRP\n"));  
  •   
  •     if (Irp == DeviceObject->CurrentIrp)  
  •     {  
  •         //标明当前正在改由StartIO处理  
  •         //但StartIO并没有获取cancel自旋锁之前  
  •         //这时需要  
  •         KIRQL oldirql = Irp->CancelIrql;  
  •   
  •         //释放Cancel自旋锁  
  •         IoReleaseCancelSpinLock(Irp->CancelIrql);  
  •          
  •         IoStartNextPacket(DeviceObject,TRUE);  
  •   
  •         KeLowerIrql(oldirql);  
  •     }  
  •     else  
  •     {  
  •         //从设备队列中将该IRP抽取出来  
  •         KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,&Irp->Overlay.DeviceQueueEntry);  
  •         //释放cancel自旋锁  
  •         IoReleaseCancelSpinLock(Irp->CancelIrql);  
  •     }  
  •     //设置完成状态为STATUS_CANCELLED  
  •     Irp->IoStatus.Status = STATUS_CANCELLED;  
  •     Irp->IoStatus.Information = 0;  
  •     IoCompleteRequest(Irp,IO_NO_INCREMENT);  
  •   
  •     KdPrint(("Leave CancelReadIRP\n"));  
  • }  

然后编写StartIO例程,注意StartIO运行在DISPATCH_LEVEL级别,因此不能使用分页内存,否则会引起页故障,从而导致系统崩溃。下面代码演示了如何编写StartIO例程




[cpp] view plaincopy

  • #pragma LOCKEDCODE  
  • VOID HelloDDKStartIO(  
  •                 IN PDEVICE_OBJECT DeviceObject,  
  •                 IN PIRP Irp)  
  • {  
  •     KIRQL oldirql;  
  •     KdPrint(("Enter HelloDDKStartIO\n"));  
  •   
  •     //获取cancel自旋锁  
  •     IoAcquireCancelSpinLock(&oldirql);  
  •     if (Irp != DeviceObject->CurrentIrp || Irp->Cancel)  
  •     {  
  •         //如果当前有正在处理的IRP,则简单的入队列,并直接返回  
  •         //入队列的工作是由系统完成的,在StartIO中不用负责  
  •         IoReleaseCancelSpinLock(oldirql);  
  •         KdPrint(("Leave HelloDDKStartIO\n"));  
  •         return;  
  •     }  
  •     else  
  •     {  
  •         //由于正在处理该IRP,所以不允许调用取消例程  
  •         //因此将此IRP的取消例程设置为NULL  
  •         IoSetCancelRoutine(Irp,NULL);  
  •         IoReleaseCancelSpinLock(oldirql);  
  •     }  
  •   
  •     KEVENT event;  
  •     KeInitializeEvent(&event,NotificationEvent,FALSE);  
  •   
  •     //等待3秒  
  •     LARGE_INTEGER timeout;  
  •     timeout.QuadPart = -3*1000*1000*10;  
  •   
  •     //定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右的时间  
  •     KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout);  
  •   
  •     Irp->IoStatus.Status = STATUS_SUCCESS;  
  •     Irp->IoStatus.Information = 0;  
  •     IoCompleteRequest(Irp,IO_NO_INCREMENT);  
  •   
  •     //在队列中读取一个IRP,并进行StartIO  
  •     IoStartNextPacket(DeviceObject,TRUE);  
  •   
  •     KdPrint(("Leave HelloDDKStartIO\n"));  
  •   
  • }  

4.自定义的StartIO

系统定义的StartIO例程只能使用一个队列,这个队列会将所有的IRP进行处理化。例如,读,写操作都会混在一起进行串行处理。然而,在有些情况下,需要将读,写分别进行串行处理。这时候就需要自定义StartIO例程。

中断服务例程
中断服务例程是设备触发中断后进入的例程。当进入中断服务例程后,IRQL会提升到设备对应的IRQL级别。
1.中断操作的必要性
“中断”设备比“轮询”的效率高,不会浪费太多的CPU时间。另外,中断可以请求嵌套。

2.中断优先级
Windows将中断的概念进行了扩展,扩展为32个中断级别(IRQL)。其中0~2级别,级PASSIVE_LEVEL到DISPATCH_LEVEL级别为软中断。从3~31级别为硬件中断。优先级从0~31,级别逐渐升高。
一般线程运行在PASSIVE_LEVEL级别,而负责调度线程的内核代码运行在DISPATCH_LEVEL级别。如果希望线程不被切换到别的线程,可以将线程从PASSIVE_LEVEL提升到DISPATCH_LEVEL级别。

3.中断服务例程(ISR)
当硬件设备的中断信号发生后,IRQL会提升至相应的DIRQL级别,操作系统会调用相应的中断服务例程(ISR)。在驱动程序中使用ISR,首先要获得中断对象,该中断对象是一个名为INTERRUPT的数据结构。

DPC例程
DPC例程一般和中断服务例程配合使用。中断服务例程处于很高的IRQL,会打断正常运行的线程。而DPC例程运行在相对于较低的DISPATCH_LEVEL级别。因此,一般将不需要紧急处理的代码放在DPC中,而将需要紧急处理的代码放在中断服务例程中。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-151754-1-1.html 上篇帖子: VS2010 Windows API 串口编程 (二) 下篇帖子: Windows驱动开发WDM (7)- 异步IRP?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表