介绍三种应用程序对设备异步操作的方法

原创 2016年06月01日 10:58:28

        应用程序对设备的操作就要是通过“ReadFile"、”WriteFile“和”DeviceIOControl“等Win32 API发起。这些Win32 API都支持同步和异步两种操作方式。从应用程序的调用来看,其主要差异是:1)调用CreateFile函数的时候,是否设置了它的第六个参数”dwFlagsAndAttributes“(设备属性)。同步操作设为NULL,异步操作设为FILE_FLAG_OVERLAPPED。2)调用实际操作请求函数时,如DeviceIoControl,是否设置了lpOverLapped参数。


一、同步操作和异步操作的原理

        以DeviceIoControl函数为例,它的同步操作如下图所示:


        

         当应用程序调用DeviceIoControl函数时,它的内部会创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,并将这个IRP传递到驱动函数的派遣函数中。处理该IRP需要一段时间,直到该IRP处理完毕后(调用了IoCompleteRequest),DeviceIOControl函数才会返回。

        同步操作时,在DeviceIoControl的内部,会调用WaitForSingleObject函数去等待一个事件。这个事件直到IRP被结束时,才会被触发。如果通过反汇编IoCompleteRequest内核函数,就会发现在其内部设置了该事件。DeviceIoControl会暂时进入休眠状态,直到IRP被结束。

        在异步操作的情况下,当DeviceIoControl被调用时,其内部会产生IRP,并将该IRP传递给驱动程序内部的派遣函数。但此时DeviceIoControl函数不会等待该IRP结束,而是直接返回。当IRP经过一段时间被结束时,操作系统会触发一个IRP相关事件。这个事件可以通知应用程序IRP请求被执行完毕。

        Win32 API的异步操作必须得到驱动程序的支持,如果驱动程序不支持异步操作,Win32 API的异步操作将会失败。


二、自定义事件异步操作

        异步操作时,主要需要设置OVERLAP参数,Windows用数据结构”OVERLAPPED“来表示。摘要如下:

Contains information used in asynchronous (or overlapped) input and output (I/O).

Syntax

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;


        其中,成员Internal显示该次IO操作的结果,成员InternalHigh表示该次IO传输的字节数。成员hEvent用来从应用层向内核层传递事件,也可以将它设为0。

        如果使用”自定义事件异步操作“的方式,则需要在使用OVERLAPPED之前,先对它进行清理,并为其创建事件。如下例:

<span style="font-size:18px;">#include <windows.h>
#include <stdio.h>

#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;
}</span>

注:    

1)自定义事件需要为每一个请求(或一组请求)创建一个自定义事件;

2)从应用层到驱动层,传递的data buffer和overlap等,都是地址。因此,在请求完成并返回之前,不能销毁这些变量。

三、自定义回调例程(Callback Function)异步操作

        前面提到的“ReadFile"、”WriteFile“和”DeviceIOControl“等Win32 API既可以支持同步操作,又可以支持异步操作。而ReadFileEx和WriteFileEx函数是专门用于异步读写操作的,不能进行同步读写。MSDN摘要如下:

Reads data from the specified file or input/output (I/O) device. It reports its completion status asynchronously, calling the specified completion routine when reading is completed or canceled and the calling thread is in an alertable wait state.

Syntax

BOOL WINAPI ReadFileEx(
  _In_      HANDLE                          hFile,
  _Out_opt_ LPVOID                          lpBuffer,
  _In_      DWORD                           nNumberOfBytesToRead,
  _Inout_   LPOVERLAPPED                    lpOverlapped,
  _In_      LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);


The ReadFileEx function ignores the OVERLAPPED structure's hEvent member. An application is free to use that member for its own purposes in the context of a ReadFileEx call. ReadFileEx signals completion of its read operation by calling, or queuing a call to, the completion routine pointed to bylpCompletionRoutine, so it does not need an event handle.

lpCompletionRoutine [in]

    A pointer to the completion routine to be called when the read operation is complete and the calling thread is in an alertable wait state. For more information about the completion routine, see FileIOCompletionRoutine.

    需要注意的是,这里提供的OVERLAPPED不需要提供事件句柄。ReadFileEx将请求传递到驱动程序后立即返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调例程(第五个参数,lpCompletionRoutine)。这类似一个软中断,也就是当读操作结束后,系统立即回调ReadFileEx提供的回调例程。Windows将这种这种机制称为异步过程调用(APC,Asynchronous Procedure Call)。

    然而,APC的回调函数被调用也是有条件的。只有线程处于警惕(Alert)状态时,回调函数才有可能被调用。有多个API可以使线程进入警惕状态,如SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectEx等。这些函数都会有一个BOOL型参数bAlertable,当设置为TRUE时,就进入警惕状态。

    当系统进入警惕模式后,操作系统就会枚举当前线程的APC队列。驱动程序一当结束读取操作,就会将ReadFileEx提供的回调例程插入到APC队列。

    回调例程会报告本次操作完成的状态,成功或失败,还会报告本次读取操作实际读取的字节数。示例如下:

#include <windows.h>
#include <stdio.h>

#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;
}


    在《Windows核心编程》一书中,对这种自定义回调例程的方法有了详细说明,该书称它为“可提醒IO”。我将它总结如下:

1)当一个线程被创建的时候,它会附带一个APC队列。

2)当进行“可提醒IO”时,驱动层完成某次request,它会在发起请求的线程APC队列中插入一项。该项包括:回调函数的地址和OVERLAPPED结构体的地址。

3)当线程不忙的时候,系统会从线程的APC队列中取出一项,调用该项的回调函数。事实上,这个线程不忙的时候,正是在线程运行到某个节点,线程调用了SleepEx函数(或者类似的函数)。操作系统的调度器会先检查线程是否处于“不忙”的状态,如果处于该状态,系统会再检查线程的APC队列。


四、完成端口

    参考我之前写的关于Echo工程的文章,它的调用过程如下

五、后记

    在我阅读Jeffrey Richter的《Windows核心编程(第5版)》一书第10章——异步设备IO时,才发现:异步设备IO的发起方式都是相同的(在CreateFile时,设置Flag),关键是接收IO请求完成通知的方式。该书的P297页用一张表列出了“用来接收IO完成通知的方法”,共四种方法,分别是:触发设备内核对象、触发事件内核对象、使用可提醒IO和使用IO完成端口。其中触发设备内核对象和触发事件内核对象类似,前者监视的是设备对象,一次只能处理一个请求,后者监视的是自定义事件对象,一次可以处理多个请求。

    事实上,Jeffrey极力推荐的异步IO方法只有一种,那就是“IO Completion Port”。它可以完全替代其他三种方法的应用场景,并创建“high-performance, scalable application”(高性能,可升缩的应用程序)。

    此外,关于“IO Completion Ports”,博客园的oayx同学的博文“完成端口(Completion Port) ”进行了较详细的探讨,值得一读。也可以参考MSDN的资料:点击打开链接


版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

linux 下对 flash 设备操作的应用程序(嵌入式参考用)

linux下对dataflash设备操作(擦除、写入文件)的应用程序,注意,所有的操作必须要使用mtdx的字符设备。     放出这个程序,帮助大家理解一些mtdchar的字符设备驱动! ...

linux中驱动异步通知应用程序的方法

原文:http://blog.chinaunix.net/uid-21714580-id-119967.html 驱动程序运行在内核空间中,应用程序运行在用户空间中,两者是不能直接通信的。但...
  • liuzq
  • liuzq
  • 2017-06-20 18:33
  • 61

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

linux中驱动异步通知应用程序的方法

驱动程序运行在内核空间中,应用程序运行在用户空间中,两者是不能直接通信的。但在实际应用中,在设备已经准备好的时候,我们希望通知用户程序设备已经ok,用户程序可以读取了,这样应用程序就不需要一直查询该设...
  • harhy
  • harhy
  • 2016-07-26 10:29
  • 238

63 linux内核的SPI设备驱动模型及应用程序调用SPI控制器的方法

SPI设备驱动模型与I2C设备驱动模型基本一样.SPI的控制器驱动由平台设备与平台驱动来实现. 驱动后用spi_master对象来描述. spi的设备也是先用spi_board_info来描述,在s...

Android 应用程序查找设备的方法——以串口为例

Android 应用程序要经常访问底层设备,访问设备就要用到设备文件描述符,在应用 open ()函数打开设备前,需要先找到设备的路径,因此我们需要通过 JDK 中各种访问文件的方法来查找到设备的路径...

wince6.0下将应用程序放到指定存储设备下运行的方法

种方法: 1、 将应用程序和应用程序快捷方式添加到映像里,再将快捷方式添加到StartUp目录下,这样当系统运行后应用程序就能自动运行; 2、 直接替换Wince的SHELL,即修改注册表: ...

在WinCE下,应用程序直接读/写/擦除flash设备的方法

作者:ARM-WINCE 在网上的很多论坛中都看到有人提问:应用程序如何直接读写Flash的扇区,或者是类似的问题。总之,就是希望应用程序能够直接访问Flash设备,直接读写扇区的数据,或者作其...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)