zoukankan      html  css  js  c++  java
  • 应用程序对设备 + IRP 的同步异步学习

    对设备的的操作转换为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
    "));
    }
    运行结果相似

























  • 相关阅读:
    解决WordPress不能发邮件,WordPress 无法发送邮件
    WordPress 显示 Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 2097160 bytes)解决办法
    怎么优雅的取消重复请求ajax
    手拉手搭建一个脚手架
    数据库隔离级别RC与RR区别——MVCC、ReadView
    整理一下下一步的计划
    减肥
    EBS: Shift + F6: 当复制上行记录
    Oracle 表值函数之多表关联使用
    EBS: 序号授权 GRANT
  • 原文地址:https://www.cnblogs.com/zcc1414/p/3982460.html
Copyright © 2011-2022 走看看