您的位置:首页 > 产品设计 > UI/UE

WDF开发USB设备驱动教程(5)

2010-02-24 20:25 218 查看

PDF全文下载:http://bbs.driverdevelop.com/read.php?tid-120461.html

 


五.设备控制

上一章结束后,我们已经完成了USB
设备的配置,完成了配置工作的
USB
设备,就已经具备了正常工作的能力。这一章中,我们讲解一些
USB
设备控制的内容。包括:

Pipe的重置;设备的重置;
Pipe
操作的终止
(abort)
;设备的反配置。

 

1. 
I/O Target对象

 
WDF在框架中引入了很多基本结构,用来统一管理驱动程序对系统资源的访问。这里我们先讲一下
I/O Target
对象。

在KMDF
中,通过
I/O Target
完成驱动请求的传递。
I/O Target
是一个抽象的概念,程序中用来体现它的,是
I/O Target Object


回到概念本身:Target
即目标,
I/O Target

I/O
操作的目标对象。驱动中,我们大部分情况下总是要把收到的驱动请求传递到设备栈中的下一层设备对象。用来表现设备栈中下一层设备对象的
I/O Target
,被我们称之为

本地I/O Target



另外一种情况下,我们并不在当前设备栈中传递驱动请求,而是要向一个任意的驱动程序所在设备栈发送请求。这样的操作也必须通过I/O Target
来完成,但因为它不位于当前设备栈中,所以被称之为

远程I/O Target



 

图x  I/O Target
和设备栈

 
上面的图片能形象地描述本地I/O Target
和远程
I/O Target
的不同作用。可以看到,本地
I/O Target
总是只有一个,而远程
I/O Target
则可以多个,这取决于需要也就是你可能向多少个远程设备栈发送驱动请求。

可以有更多的知识点用来区别本地I/O Target
和远程
I/O Target
。首先,本地
I/O Target
是由
KMDF
框架为你创建,而保存在
WDFDEVICE
中(
WDFDEVICE
可能只保存了它的一个指针,但总而言之,
WDFDEVICE
唯一对应了一个本地
I/O Target
,并能方便地找到本地
I/O Target
)。而远程
I/O Target
由用户自己手动创建。

调用WdfIoTargetCreate
可以新建一个远程
I/O Target
对象,新建的
I/O Target
对象是一个空的对象,它被关联到当前的
WDFDEVICE
对象上。要初始化空的
I/O Target
对象,要接着调用
WdfIoTargetOpen
函数,并向
WdfIoTargetOpen
函数传入可以标志目标设备对象的标志。这个标志可以是目标设备对象的
Interface Name
,或者传入一个目标对象的
WDM
设备对象。驱动程序可以向系统中的任何驱动对象的设备发送请求,但必须首先为这个目标对象创建(如果已创建,则打开)
I/O Target
对象。

我在做图的时候,为了布局方便,把远程I/O Target
移动到了设备对象框图的外面。但实际上远程
I/O Target
和本地
I/O Target
一样,都是隶属于创建它的设备对象的。这种隶属关系,导致了当设备对象被删除的时候,所有与他相关的
I/O Target
都被删除。

 

2. 
获取USB
版本

 

 
USB设备有一些协议相关的属性,包括这样一些信息:
USB
版本、供电状况、远程唤醒。

USB设备为
1.1
还是
2.0
,关系到驱动程序处理上的不同策略。
2.0
设备的处理速度是
1.1
设备的
8
倍,驱动实现中需要更快的数据吞吐需反应。

供电状况是说设备的电源供应,由系统总线提供,还是自身提供(外部电源)。系统总线有能力通过USB
接口的电源
Pin

2.0
设备提供
5V
电压,或者为
1.1
设备提供
4.5V
电压。对于功率更大的设备,总线供电远远不能满足需求,则要求其自己准备外接电源。

“远程唤醒”和“休眠”是一对,如果设备具有远程唤醒功能,则设备自身可以休眠,并且休眠后可重新醒来。并且如果主机进入了休眠状态,它也有能力把主机唤醒。USB
鼠标就是拥有远程唤醒功能的
USB
设备。远程唤醒主机与设备的唤醒能力相关,如果主机仅仅进入休眠状态,设备一般可将主机唤醒;如果主机进入待机甚至关机状态,设备一般不能将主机唤醒。

下面分别让读者了解,在WDM

WDF
两种框架下如何获取
USB
版本信息。

 

1.1 
WDM环境下的实现方法

 

底层的USB
控制器驱动暴露了一组回调函数,功能驱动可通过程序方式获取它们。在
WDM
环境下,正式通过这一组回调函数来获取设备属性的。

需要发送子功能号为IRP_MN_QUERY_INTERFACE

IRP_MJ_PNP
命令到控制器驱动。比较重要的地方是,控制器驱动内部有多个类似的回调函数组,每个组由一个
GUID
标识。所以这个
GUID
也必须随同一起传给底层驱动。控制器驱动收到此命令后,判断
GUID
是否可识,然后返回相关的结构体(

USB_BUS_INTERFACE_USBDI_V1
)。随着操作系统的升级,这个内核结构体也存在版本上的升级。我们一般使用V1
版本就可以了。下面的代码:

 
    USB_BUS_INTERFACE_USBDI_V1 busInterfaceVer1;

 
nextStack = IoGetNextIrpStackLocation(irp);

    ASSERT(nextStack);

    nextStack->MajorFunction = IRP_MJ_PNP;

    nextStack->MinorFunction = IRP_MN_QUERY_INTERFACE;

 
    //

    //
 设置接口(即QueryInterface
所指的那个目标
Interface
)在这个
IRP
中的版本和结构长度

    nextStack->Parameters.QueryInterface.Size =

        sizeof(USB_BUS_INTERFACE_USBDI_V1);

    nextStack->Parameters.QueryInterface.Version = USB_BUSIF_USBDI_VERSION_1;

    

    // 下面的
Interface
参数最为重要。

// 可以把
INTERFACE
结构看成

USB_BUS_INTERFACE_USBDI_V1
的父结构;

// INTERFACE定义了

USB_BUS_INTERFACE_USBDI_V1
结构的起始部分。

nextStack->Parameters.QueryInterface.Interface =

                                (PINTERFACE) &busInterfaceVer1;

 
nextStack->Parameters.QueryInterface.InterfaceSpecificData = NULL;

 
// 要注意下面的
GUID
值。系统为不同的设备类型预定义了不同的
GUID
,以区分
Port
接口类型。

// USB 接口的
GUID

usbbusif.h
中定义。

// 如果你像我一样,应用
usbbusin.h
中的
GUID
值有问题,可以自己如下定义:

// 
static GUID UsbInterfaceGuid = 

// 
{ 0xb1a96a13,0x3de0, 0x4574, 

// 

{ 0x9b, 0x01, 0xc0, 0x8f, 0xea, 0xb3, 0x18, 0xd6 } };

nextStack->Parameters.QueryInterface.InterfaceType =
 
&UsbInterfaceGuid;

 
// p
NextDeviceObject
指向驱动中保存的设备栈中的下一层(Next
)驱动对象

ntStatus = IoCallDriver(
p
NextDeviceObject,
 
irp);

 
 
如上面的代码操作成功,那么QueryInterface
结构体的
Interface
指向那个内核结构体,里面包含了一系列的回调函数指针。调用

IsDeviceHighSpeed
函数可判断USB版本。

 
BOOL b
IsHighSpeed
 = FALSE;

 
// 下面的代码调用
Port
驱动的

IsDeviceHighSpeed
回调函数,判断USB
版本。

b
IsHighSpeed = 
 
InterfaceVer1.IsDeviceHighSpeed(busInterfaceVer1.BusContext)?

TRUE
:
FALSE
;

 

1.2 
WDF方法

 
WDF框架意识到这些属性的常用性,对它们做了接口封装。专门定义的结构体

WDF_USB_DEVICE_INFORMATION
用来保存这些设备属性。

 
typedef struct _WDF_USB_DEVICE_INFORMATION {

  ULONG  
Size

;

  USBD_VERSION_INFORMATION  
UsbdVersionInformation

;
 // 总线驱动版本

  ULONG  
HcdPortCapabilities

;
// 控制器属性

  ULONG  
Traits

;

} WDF_USB_DEVICE_INFORMATION
;

 
第二个参数
UsbdVersionInformation

表示总线驱动版本信息。对上层驱动而言,底层总线驱动应当是透明的。但随着版本的不同,总线驱动为上层驱动暴露的接口会有一定的增长。上一节中的
USB_BUS_INTERFACE_USBDI
就属于这种总线接口之一


第三个参数
HcdPortCapabilities

表示控制器设备的属性,目前只有一个可用值:
USB_HCD_CAPS_SUPPORTS_RT_THREADS
,它表明了控制器设备是否只是实时(RT

Real-Time
)线程



最后一个参数Traits
是这一小节需要用到的值,它表示了设备相关属性。
Traits
是一些掩码位的集合,存在如下一些位值:

typedef enum _WDF_USB_DEVICE_TRAITS {

  
WDF_USB_DEVICE_TRAIT_SELF_POWERED

 = 0x00000001,
// 总线电源还是外接电源

  
WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE

 = 0x00000002,
//支持远程唤醒否?

  
WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED

 = 0x00000004,
//高速设备还是全速设备?

} WDF_USB_DEVICE_TRAITS;

 
上面三个值分表代表了设备的供电状况、协议版本、远程唤醒。只要用Traits
值和这些位值分别进行“与操作”即可判断了。

下面的代码首先从系统处获取
WDF_USB_DEVICE_INFORMATION
结构,然后判断并分析结构中的数据:

 
WDF_USB_DEVICE_INFORMATION  info;

BOOL bIsHighSpeed = FALSE;

BOOL bIsSelfPower = FALSE;

BOOL bCanRemoteWake = FALSE;

 
// 一定要调用下面的这个初始化宏。

// 如果你要手动初始化这个结构体,则应该设置
info->Size
值。

WDF_USB_DEVICE_INFORMATION_INIT(&info);

 
// 调用
WDF
函数

WdfUsbTargetDeviceRetrieveInformation
,从底层USB Port
驱动

// 那里得到

WDF_USB_DEVICE_INFORMATION
信息。

status = WdfUsbTargetDeviceRetrieveInformation(

pDeviceContext->WdfUsbTargetDevice, &info);

 
if (NT_SUCCESS(status)) {

    // 把
Trait
作为掩码值,判断与

WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED
相与的结构。

// 其实是判断
Trait
值中第三个比特位是否被设置。

    
bI
s
H
ighSpeed =

        (info.Traits & WDF_USB_DEVICE_TRAIT_AT_HIGH_SPEED) ? TRUE : FALSE;

    

bIsSelfPower = 

    
    
(info.Traits & WDF_USB_DEVICE_TRAIT_SELF_POWERED) ? TRUE : FALSE;

 
bCanRemoteWake = 

    
    
(info.Traits & WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE) ? TRUE : FALSE;



 

2. 
管道重置

 
重置(Reset
)操作能让管道或者设备从一个错误中恢复过来,重新恢复正常的工作。不当的
USB
错误,可能导致管道或者设备进入
Stall
状态。比如在
USB

set feature
操作中,如果驱动向设备发送了一个错误的
selector
值,
USB
设备会因此而进入
stall
状态。
STALL
状态能导致设备终止一切命令的处理,直到
stall
错误被解决为止。

我不知道有没有更好的办法来处理各种不同错误导致的stall
错误。但一种万能的处理方法是调用重置(
reset
)操作。

对管道进行重置操作,要针对目标管道向USBD

USB
总线驱动)发送

URB_FUNCTION_RESET_PIPE
命令,并且要传入目标管道的句柄。熟悉WDF
编程的读者,往往看不到类似
URB_FUNCTION_*
这样的关键字,这是由于
WDF
的封装做得太齐全了。

为了读者更好地理解这部分内容,我先演示WDM
代码:

 
NTSTATUS
 ResetPipe_WDM
(HANDLE PipeHandle)

{

NTSTATUS ntStatus;

PURB urb;

struct _URB_PIPE_REQUEST urb_pipe;

 
        // 
创建URB

urb = &urb_pipe;
// URB结构体是各种不同
URB
请求的
union


        RtlZeroMemory(urb,sizeof(struct _URB_PIPE_REQUEST));

        urb->UrbHeader.Length   = sizeof(struct _URB_PIPE_REQUEST);

        urb->UrbHeader.Function = URB_FUNCTION_RESET_PIPE;

        urb->UrbPipeRequest.PipeHandle = PipeHandle;

 
        // 向底层
USB
总线驱动发送
urb
请求。函数细节省略。

        ntStatus = SendUsbRequest(urb);

   
return ntStatus;

}

 
相对WDF
来说,在
WDM
驱动中做任何事情都显得复杂。上面函数中的
SendUsbRequest
命令虽然要实现的任务很简单,就是创建一个
IRP
,通过这个
IRP

URB
命令发送给
USB
总线驱动。但就此必须写
20
行以上的代码。整个实现合并在一起,代码量在
40
行左右。而下面的
WDF
实现,就显得非常清晰简单了:

 
NTSTATUS ResetPipe
_WDF
(IN WDFUSBPIPE Pipe)

{

    NTSTATUS          status;

 
    // 下面的函数同步地发送
URB_FUNCTION_RESET_PIPE
命令到
USB
总线驱动

    status = WdfUsbTargetPipeResetSynchronously(Pipe,

                                WDF_NO_HANDLE, // 
就是NULL

                                NULL  // PWDF_REQUEST_SEND_OPTIONS

                                );

    if(NT_SUCCESS(status)) status = STATUS_SUCCESS;

    return status;

}

最主要的代码,只有一行WDF API
调用而已。

WdfUsbTargetPipeResetSynchronously
的调用有几个注意点:

1) 
此API
调用同步完成管道的
reset
操作。
WDF
没有提供专门针对
reset
操作的异步版本。如果要进行异步操作的话,只能写
WDM
代码。
WDK
中有说到它的异步方法,但我认为是
WDK
弄错了。

2) 
Pipe句柄必须是有效句柄,否则会
Bug Check


3) 
第二个参数,我们上例中传入了NULL
值,这导致这个
URB
请求不能被中途取消(
Cancel
)。另外一种方法是使用
WdfRequestCreate
调用创建一个
WDFREQUEST
对象。这使得我们可以在请求被发送后,如果需要的话随时取消它。

4) 
第三个参数,可以被用来设备命令的timeout
时间。如果你不使用这个参数,则函数调用知道
URB
命令被处理结束才会返回。

 

3. 
设备重置(Reset


 
上面讲了Pipe
的重置,下面说设备的重置。
USB
设备重置不是通过发送
URB
命令来进行的,他向
Host Controlor
发送一个
Device I/O
控制命令

IOCTL_INTERNAL_USB_RESET_PORT


什么时候需要对设备进行重置?我认为当对USB
设备进行重新配置的时候,或者设备从睡眠中被唤醒的时候,都有必要进行重置操作。当针对
USB
设备的
in/out
操作发生某些特点错误,并且不能恢复的时候,可以进行重置操作。

设备的重置操作有这样几个特点:

1. 
设备重置,则所有的接口和管道都被重置。所以如果一个功能驱动,仅仅负责USB
设备的某一个接口,那么他最好不要贸然做设备重置操作,否则会导致其他接口的功能驱动产生错误。

2. 
承第一点。复杂设备的功能驱动,一般谨慎使用设备重置操作,代而遍历接口的所有管道,对管道全部做一次重置操作。

3. 
USB总线驱动负责
USB
设备的重置操作。他先保留设备当前的配置。重置操作结束后,再把这些配置还原。并且保证原来的所有设备、配置、接口、管道句柄,都依然有效。

4. 
进行设备重置前,最好保证USB
设备上的所有
I/O
操作都已经完成。对此驱动实现中应该具备相关的同步设施。这一点在
Win2K
上特别明显。如果有
I/O
操作为完成的话,重置请求会一直阻塞在那里等待所有
I/O
操作的结束。
Win2K
以后的系统似已没有这种限制。

 
NTSTATUS ResetDevice_WDF(IN WDFDEVICE Device)

{

    PDEVICE_CONTEXT pDeviceContext;

    NTSTATUS status;

    BOOLEAN win2k;    

    

    // 判断
USB
设备是否连接在
USB
总线上

    status = WdfUsbTargetDeviceIsConnectedSynchronous(

GetWdfUsbDevice(Device)

);

 
    if(NT_SUCCESS(status)) {

// 我们仅在
Win2K
的情况下,把系统当前所有的
I/O
操作都结束。

        win2k = IsItWin2k();

        if (win2k) {

            StopAllPipes(
RetrieveUsbInterface(Device), // Interface句柄

RetrievePipeNumber(Device)
);
// Interface上的管道数

        }

 

// 这里发送设备重置命令。

        status = WdfUsbTargetDeviceResetPortSynchronously(

GetWdfUsbDevice(Device)

);

 

// 如果是
Win2K
系统,最后还需要恢复所有
I/O
操作

        if (win2k) {

            StartAllPipes(
RetrieveUsbInterface(Device), // Interface句柄

RetrievePipeNumber(Device)
); 

        }

}

 
    return status;

}

 
于此同时,我们也看看WDM
中是如何做类似操作的。
WDM
实现中最关键的一步是创建
IRP_INTERNEL_DEVICE_CONTROL
命令,并且其
IO_CTL
值为

OCTL_INTERNAL_USB_RESET_PORT
。创建IRP的示例代码如下:

 
irp = IoBuildDeviceIoControlRequest(

                    IOCTL_INTERNAL_USB_RESET_PORT,
// IO_CTL值

                    
pLowerDeviceObject
,
// 设备栈中的下一个设备

                    NULL,

                    0,

                    NULL,

                    0,

                    TRUE,

                    &
syncEvent
,
// 同步事件

                 
   
&ioStatus);

 
IRP创建完成后,只要调用
IoCallDriver
把它向下层驱动发送就可以了。

 

4. 
Pipe操作的中止与终止

 
中止(abort
)操作和终止(
stop
)操作有这样的不同之处:中止操作让所有“此前”的
I/O
操作无效,然后

继续
处理此后的I/O
操作;终止操作在处理完当前的所有
I/O
操作后,

终止
此后的一切I/O
操作;对于当前未完成的
I/O
餐桌,它提供了几种可选方案:可以和中止操作一样,令所有
I/O
操作无效,也可以等待它们全部完成,并设置一个等待时间。

所以必须明白,中止和终止,是两个完全不同的操作。

 

5.1 
中止操作

 
Abort操作的
WDF
版本示例代码如下所示:

 
NTSTATUS
 
AbortPipes
_WDF
(IN WDFDEVICE Device)

{

    UCHAR              i;

    ULONG              count;

    NTSTATUS           status;

 
    count =
 
RetrievePipeNumber
(Device)
;

 
    for (i = 0; i < count; i++) {

        WDFUSBPIPE pipe;

        pipe = WdfUsbInterfaceGetConfiguredPipe(

RetrieveUsbInterface(Device)
,

                 i, //PipeIndex,

                 NULL);

 
        status = WdfUsbTargetPipeAbortSynchronously(pipe,

                                    WDF_NO_HANDLE, // 
就是NULL

                                    NULL);//PWDF_REQUEST_SEND_OPTIONS

 
        if (!NT_SUCCESS(status))break;

    }

 
    return STATUS_SUCCESS;

}

 

5.2 
终止操作

 
Stop操作只有在
WDF
中才有,这是因为它借助了
I/O Target
这个有利工具。
WDM
没有专门的对应操作,但当然也是可以实现的。
I/O Target
对它所管理的
I/O
设备进行了封装,并全程管理对应的
I/O
操作。试问
I/O Target
是怎么做到终止
I/O
设备上的所有
I/O
操作请求的?其实答案非常简单:它令所有针对其所管理的
I/O
设备的请求,都以失败返回,以造成
I/O
操作被终止的效果。

Stop操作的
WDF
版本示例代码如下所示:

 
VOID StopAllPipes(IN WDFUSBINTERFACE UsbInterface, UCHAR count)

{

    UCHAR i;

    

    for (i = 0; i < count; i++) {

        WDFUSBPIPE pipe;

        pipe = WdfUsbInterfaceGetConfiguredPipe(UsbInterface,

                                                i, //PipeIndex,

                                                NULL

                                                );

        WdfIoTargetStop(WdfUsbTargetPipeGetIoTarget(pipe),

                        WdfIoTargetCancelSentIo);
// 中止所有先前的
I/O
操作

    }

}

 
那么如何重庆启动被终止的I/O
操作呢?只要调用
WDF API 

WdfIoTargetSt
art即可。调用这个
API
时,需要通过管道句柄获取对应的
I/O Target
对象。

WdfIoTargetStop
和WdfIoTargetStart
函数可以用来终止和开启任意的
I/O Target
对象。

接口函数相应地,我们来看看如果再次开启管道上被终止的I/O
操作,示例代码如下:

VOID St
art
AllPipes(IN WDFUSBINTERFACE UsbInterface, UCHAR count)

{

    UCHAR i;

    

    for (i = 0; i < count; i++) {

        WDFUSBPIPE pipe;

        pipe = WdfUsbInterfaceGetConfiguredPipe(UsbInterface,

                                                i, //PipeIndex,

                                                NULL

                                                );

        WdfIoTargetSt
art
(WdfUsbTargetPipeGetIoTarget(pipe));

    }

}

 
 
 
 
 
 
 
 
 
 
 


六.用户程序
 

继续来学习如何编写正确的用户程序,与内核代码交互信息。用户代码欲和内核通信,方法不外乎这样几种:

1. 
用Read/Write
函数传递读写命令

2. 
用DeviceIoControl
方法传送控制命令

3. 
用内核同步方法传递少量同步信息

对这些耳熟能详的做法,我今天和大家一起来温习。

 

1. 
内核读写

读写之先需要获取设备句柄。设备句柄也就是我们的读写对象。Windows
系统的实现,一贯就有这样的规定:所谓文件对象,并不单指文件系统中的文件或目录,而是广义地包括这些内容:文件系统所指的狭义文件或目录、设备对象、管道对象、邮件漕、控制台对象。

所以第一步是用打开文件的方式获得内核设备的句柄。调用CreateFile
不成困难,唯一值得注意的是设备名称。设备名称位于系统的全局命名空间中,其格式如:
//./DeviceName
。如系统中的第一个磁盘设备,其名称即为:

//./PhysicalDrive0
。我的CY001
在系统中的名称为:
//./CY001_X(X
为从
0-8
的数值
)
。下面即以打开一个
CY001
设备为例,看下面的代码:

 
得到设备句柄之后,即可对之操作。譬如鱼的烹饪方法有蒸有煎,读写分别对应的函数为ReadFile

WriteFile

CY001
中实现的批量管道读写,即使用了这种方法。要做的事情是,先往
CY001
写入“
ChinaHearing
”这几个字,然后再读出他们,看读者看下面的代码。

 
读写很简单,惯于Win32
编程的朋友对此可谓信手拈来。唯一关键处是需注意设备名称的格式。

 

2. 
控制命令

任何命令都可以通过DeviceIoControl
传送到内核,只不过首先要确定所这是些内核能够识别的命令罢了。关于控制命令,一向都有三种格式的区分,即所谓缓冲格式、
Direct IO
格式,
Neither
格式。介绍这三种格式的资料和文章已经汗牛充栋,我不妨也简说几句,说的不好,量也不会有多少人注意吧。

所谓缓冲格式,可理解为“拷贝格式”。即所传入的内容被拷贝一份后,再往内核传送;而从内核传出的内容,也是先被放到了别处,最后才复制一份给你。

所谓Direct IO
格式,是将你的
I/O
缓冲区地址,由用户空间地址转换为内核空间地址后,发给内核“直接”使用。这样内核就能直接在你的
Buffer
上操作,从而节省了空间。

最后那个Neither
格式,最是简单。它直接把用户缓冲区的地址不做任何处理,即交由内核使用。一般情况下,这种方法最经济;但在内核空间使用用户层地址,需要确保处于当前进程空间,否则会导致错误。为了保证这一点,最简单的做法是用“不”异步调用
DeviceIoControl


最后需要明确的是,使用何种方法不由调用者决定,亦即不由DeviceIoControl
决定,而由命令本身决定。控制命令是一个
32
位数字,它的
3

4
两位即表示了缓冲格式。系统在获取到你将要发送的控制命令后,会分析这个命令格式,然后自行决定用何种缓冲格式。

现在为大家演示三个格式不同的控制命令定义:

1.

2.

3.

有了对应的命令后,现在看看如何发送它们。发送可分为同步和异步。可以同步的地方,自然同步来得方便;但有些命令是不能即刻完成的,同步会导致阻塞,就需要用异步方法。先看同步方式,比较简单;再演示异步方式,需要别用一个等待线程: 

 

3. 
内核同步

 
内核同步听起来比上面两者高级一些,原因是使用的人不太多。原理却很简单:创建一个事件对象传递给内核,用户层一直等待它的完成。这和用户层的两个线程之间的同步,并无差别。但在内核那边,有一些技巧。

问题在于,用户创建的事件,是一个Win32
句柄,并且这个句柄还是进程相关的(即放到别一个进程中去,这个句柄就是非法的了)。内核所在的地址却是全局性的(亦即一个地址需在此进程中正确,亦在所有其他的进程中也正确),要让全局性的内核可以安全使用局部性的用户句柄,唯一的办法是将用户句柄转换成全局对象地址。且让我们看看这个转换过程:

 
至于用户层的操作,是非常简便的。不过这样两步而已:创建事件、发送事件。除此之外,多出的一步是等待内核事件完成。一般应该单独创建线程来完成。我们来看整个的实现代码:

 
编写用户程序,不外乎是上述三种办法的翻用。有些代码看上去眼花缭乱,剥了皮来看,实质却都是一样的。我写的UsbKitApp
用来和内核
CY001
驱动进行交互,代码量过万行,用到的也就只是这几种方法。

 

4. 
注意点

常说的一句话是:用户层的动作,不会引起系统崩溃。话是不错的,但如果用户层的动作带来了内核错误,内核错误即能导致系统崩溃;故而和内核通信,需比“用户模块”间通信格外多一分慎重。

我举一个导致系统崩溃,由用户层通信不慎重引起的案例。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息