Windows Driver Samples剖析之Echo (三)

来源:互联网 发布:遗传算法的matlab程序 编辑:程序博客网 时间:2024/05/17 14:14

        本文还是遵循”由表入里“的原则,先讲解Echo工程中的”echoapp“,从与驱动交互的应用程序入手逐步了解驱动程序的机制。

一、程序流程

        如下图,该程序先通过”GetDevicePath“函数,使用与驱动绑定(共用Public.h文件)的GUID,去注册表中获取”设备的符号名“(字符串)。然后调用”CreateFile“函数,通过该符号名打开设备,获得设备句柄。拿到设备句柄后,就可以进行读写操作。

        该程序演示了”同步“和”异步“两种读写操作测试。其中,”异步读写“是将读写操作放到不同的线程中,并发执行。




二、关键函数

1,GetDeviePath

        GetDevicePath的功能就是通过”驱动程序的GUID“,去查询注册表,获取”设备的符号链接“,它是应用程序可识别的设备路径(可自行google"设备链接和设备名”)。摘要如下:

————————————————————————————————

        windows下的设备是以"\Device\[设备名]”形式命名的。例如磁盘分区的c盘,d盘的设备名称就是
"\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”。当然也可以不指定设备名称,I/O管理器会自动分配一个数字作为设备的名称。例如"\Device\00000001"。
        \Device\[设备名],不容易记忆,通常符号链接可以理解为设备的别名,更重要的是设备名,只能被内核模式下的其他驱动所识别,而别名可以被用户模式下的应用程序识别,例如c盘,就是名为"c:"的符号链接,其真正的设备对象是"\Device\HarddiskVolume1”。
        驱动中符号链接名是这样写的: L"\\??\\HelloDDK" ---> \??\HelloDDK    或L"\\DosDevices\\HelloDDK" ---> \DosDevices\HelloDDK
        在应用程序中,符号链接名: L"\\\\.\\HelloDDK"-->\\.\HelloD

————————————————————————————————

        GetDevicePath通过两步获得设备链接,先调“CM_Get_Device_Interface_List_Size”获得路径字符串长度,开好空间,再调 “CM_Get_Device_Interface_List”函数获得路径。

         获得路径之后,还要判断是否有其他设备。主要是通过偏移,检测字符串是否为空来判断。代码如下:

<span style="font-size:14px;">    nextInterface = deviceInterfaceList + wcslen(deviceInterfaceList) + 1;    if (*nextInterface != UNICODE_NULL) {        printf("Warning: More than one device interface instance found. \n"            "Selecting first matching device.\n\n");    }</span>

        如果当前系统下有多个设备,比如,我将上一篇博客中介绍的install.bat运行两次,安装两次驱动。它就会在设备管理器的设备列表中看到两个设备,这个时候就会触发上面代码中warning。  

        在MSDN搜索CM_Get_Device_Interface_List ,说明如下(第三和第四个参数):

Buffer [out]

Caller-supplied pointer to a buffer that receives multiple, NULL-terminated Unicode strings, each representing the symbolic link name of an interface instance.

BufferLen [in]

Caller-supplied value that specifies the length, in characters, of the buffer pointed to by Buffer. Call CM_Get_Device_Interface_List_Size to determine the required buffer size.

        从上面可知,它会获得多个以0结尾的字符串。

 下面再看一下“设备的实例路径”和“设备的符号链接”


        上图,右边可以看到,该设备名是“0000”。如果再安装一次驱动,第二个设备显示的是“ROOT\SAMPLE\0001”。此外,设备的符号链接实际上就是“实例路径+GUID”。

最后,将路径字符串进行转换,并传给调用者。


2,AsyncIo函数

该函数被调用了两次,分别进行连续多次的“异步读写请求”。其中该,第一次是作为线程函数被调用,通过线程参数决定是进行“Read IO Request”还是"Write IO Request"(所有的这些“Request”都是异步进行的);第二次是直接在主线程中作为本地函数被调用。它的大致流程如下:


关于异步IO Request,后面我会单独写一篇文章来说明,在此就不展开了,仅说明一下该函数需要注意的地方。

1)整个echoapp中共调用了三次“CreateFile”函数,打开的都是同一个设备,且三次调用之间没有调“CloseHandle”函数。我debug的时候,观察了三次调用返回的句柄值都不相同。查看MSDN对CreateFile的说明:

dwShareMode [in]

The requested sharing mode of the file or device, which can be read, write, both, delete, all of these, or none (refer to the following table). Access requests to attributes or extended attributes are not affected by this flag.

If this parameter is zero and CreateFile succeeds, the file or device cannot be shared and cannot be opened again until the handle to the file or device is closed. For more information, see the Remarks section.

You cannot request a sharing mode that conflicts with the access mode that is specified in an existing request that has an open handle. CreateFile would fail and the GetLastError function would return ERROR_SHARING_VIOLATION.

只有将它的第三个参数设置为“shared"模式才可以对设备同时进行多项操作。此外,它的第六个参数决定对设备的操作类型 —— 同步或异步。在本例中,第一次设置的是0,即同步IO;后两次设置的是”FILE_FLAG_OVERLAPPED“,即异步IO。


2)在CreateFile之后,调用了CreateIoCompletionPort函数,创建了一个”完成端口“,并将设备句柄与port关联。此处可参考博客:点击打开链接 和MSDN对该函数的说明:CreateIoCompletionPort 。

3)该函数创建了一个OVERLAPPED结构体List,它类似一个资源池。MSDN对 OVERLAPPED 结构体的说明如下:

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

Syntax

C++
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。

4)本例是一次发布所有的IO Request,然后再在while循环调用GetQueuedCompletionStatus函数来等待IO Request的完成,并逐个出队和处理。MSDN对GetQueuedCompletionStatus的说明如下:

Attempts to dequeue an I/O completion packet from the specified I/O completion port. If there is no completion packet queued, the function waits for a pending I/O operation associated with the completion port to complete.


5)本例完美的演示了”多线程“和”异步IO“的区别。多线程指的是task执行的方式,并发执行;”异步”指的是对IO设备的调用或函数调用或网络请求的操作方式,实时响应或非实时响应,阻塞或不阻塞。


至此,对Echo工程的应用层程序介绍完毕,后面开始介绍内核代码。

0 0