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

五.设备控制

上一章结束后,我们已经完成了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.  注意点

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

我举一个导致系统崩溃,由用户层通信不慎重引起的案例。

猜你喜欢

转载自blog.csdn.net/zb774095236/article/details/83819117