windows xp 驱动开发(五) USB驱动程序、应用软件概述

2023-11-03

转载请标明是引用于 http://blog.csdn.net/chenyujing1234

 

欢迎大家提出意见,一起讨论!

1、USB设备驱动程序(WDM模型)

1、1  分类

USB设备驱动程序的设计是基于微软件的WDM。

WDM采用分层驱动程序模型,对于USB设备来说,可作来两个部分:

USB总线驱动程序

    它由操作系统提供,它位于USB功能驱动程序的下面,负责与实现的硬件打交道,实现烦琐的低层通信。   

USB功能驱动程序

    由设备开发者编写,位于USB总线驱动程序的上面,不与实现的硬件打交道,而是通过USB总线驱动程序

    发送包含URB(USB Request Block, USB请求块)的IRP(I/O Request Packet, I/O请求包)来祥瑞对USB设备

   信息的发送或接收。

注意: USB功能驱动程序调用的API函数都不能与开发Window 应用程序的API(工作于CLR上面)一样,得用自己WDK SDK包里特有的内核API函数才可以。

         

 

在WDM模式中,完成一个设备的操作,至少有两个设备对象共同完成。

(1)物理设备对象(PDO)

(2)另一个是功能设备对象(FDO)

 其关系是“附加”与“被附加”关系。

1、2  对象栈数据结构

下图的左边是WMD模型的设备对象栈。(设备对象是系统为帮助软件管理而创建的数据结构,一个物理硬件可以有多个这样的数据结构)。

PDO(physical device object):  处于堆栈最底层的设备对象。

FDO(functional device object):功能设备对象。

FIDO(filter device object):  过滤器设备对象

 

当PC插入某个设备进,PDO会自动创建,确切说由总线驱动创建的。 

PDO不能单独操作设备,需要配合FDO一起使用。

系统会提示检测到新设备。这时要求安装驱动程序。

驱动程序指的就是WDM程序,此驱动程序负责创建FDO,并附加到PDO上。

当一个FDO附加在PDO上的时候,PDO设备对象的子域AttackedDevice会记录FDO的位置。

PDO被称作底层驱动或下层驱动。而FDO称为上层驱动。

 

1、3 描述符的概念

     USB设备硬件中的数据结构称为描述符,可以被主机软件识别。

    每个描述符开始于一个两字节的头,头中指出该描述符的字节长度(包括头)和描述符类型

   描述符的长度对于相同的描述符类型是固定的。

1、4 设备描述符

     每个设备都有一个唯一 的设备描述符,它向主机软件标识该设备。主机使用GET_DESCRIPTOR控制事务直接

     从设备的0号端点读取该描述符。

   

typedef struct _USB_DEVICE_DESCRIPTOR {
    UCHAR bLength;   // 设备描述符的bLength域等于18
    UCHAR bDescriptorType; // 域为1, 以指出该结构是一个设备描述符还是一个管道描述符
    USHORT bcdUSB;   // 包含描述符遵循的USB规范的版本号( 以BCD编码),
       // 现在,设备可以使用值0x0100 或 0x0110 来指出它是1.0版还是1.1版本.
    UCHAR bDeviceClass;  // 指出设备类型
    UCHAR bDeviceSubClass; // 指出设备的子类型
    UCHAR bDeviceProtocol; // 指出设备所使用的协议
    UCHAR bMaxPacketSize0; // 给出了默认控制端点(端点0)上的数据包容量的最大值
    USHORT idVendor;  // 厂商代码
    USHORT idProduct;  // 厂商专用的产品标识
    USHORT bcdDevice;  // 指出设备的发行版本号(0x0100对应版本1.0)
    UCHAR iManufacturer; // iManufacturer iProduct  iSerialNumber指向一个串描述符,
       // 该串用人可读语言描述设备生产厂商。
    UCHAR iProduct;
    UCHAR iSerialNumber;
    UCHAR bNumConfigurations;// 指出该设备能实现多少种配置。
} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;

 

我们已经知道在控制管道中,传输分为三个阶段.

第一阶段是令牌阶段,这里Host向设备发送"80 06 00 02 00 00 20 00" 8个字节。

第二阶段是数据传输阶段,方向是由设备传给主机,在我们的例子中给主机传递了18个字节。

这18字节就是对应于USB_DEVICE_DESCRIPTOR数据结构的.


第三阶段是握手阶段.

 

1、5  配置描述符

        每个设备有一个或多个配置描述符,它描述了设备能实行的各种配置方式。

   

typedef struct _USB_CONFIGURATION_DESCRIPTOR {
    UCHAR bLength;          // 应该是9
    UCHAR bDescriptorType;  // 应该是2,即是一个9字节的配置描述符
    USHORT wTotalLength;    // 为该配置描述符长度加上该配置内所有接口和
                         // 端点描述符长度的总和。
       // 通常,主机在发出一个GET_DESCRIPTOR请求并正确接收到9字节长的
       // 配置描述符后,就会再发出一个GET_DESCRIPTOR请求并指定这个总长度。
       // 第二个请求把这个大联合描述符传输回来
    UCHAR bNumInterfaces;   // 指出该配置有多少个接口
    UCHAR bConfigurationValue;// 该配置的索引值
    UCHAR iConfiguration;   // 是一个可选的串描述符索引,指向描述该配置的Unicode字符串
    UCHAR bmAttributes;
    UCHAR MaxPower;         // 指出要从USB总线上获取的最大电流量
} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR; 

 

1、6 接口描述符
typedef struct _USB_INTERFACE_DESCRIPTOR {
    UCHAR bLength;			// 应该是9
    UCHAR bDescriptorType;	// 
    UCHAR bInterfaceNumber; // 是索引值
    UCHAR bAlternateSetting;// 是索引值。用在SET_INTERFACE控制事务中以指定要激活的接口
    UCHAR bNumEndpoints;    // 指出该接口有多少端点,不包括端点0.
    UCHAR bInterfaceClass;  // 为接口类
    UCHAR bInterfaceSubClass;// 为子接口类
    UCHAR bInterfaceProtocol;// 为协议
    UCHAR iInterface;        // 一个串描述符。
} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;


 

1、7  端点描述符
typedef struct _USB_ENDPOINT_DESCRIPTOR {
    UCHAR bLength;
    UCHAR bDescriptorType;
    UCHAR bEndpointAddress;
    UCHAR bmAttributes;
    USHORT wMaxPacketSize;
    UCHAR bInterval;
} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR;


 

1、8  USB请求使用总线驱动程序
1、8、0  USB设备请求

USB的请求是通过管道传输的,请求是8个字节

 

其中bRequest代表不同的USB请求.定义在USB100.h中

 

 1、8、1 初始化请求

         为了创建一个URB,首先应该为URB分配内存,然后调用初始化例和把URB结构中的各个域填入内容。

        eg: 当你为响应IRP_START_DEVICE请求而配置设备时,首要的任务就是读取该设备的设备描述符.

            

urb = ExAllocatePool(NonPagedPool, 
                         sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));

    if(urb)
	{

        siz = sizeof(USB_DEVICE_DESCRIPTOR);
        deviceDescriptor = ExAllocatePool(NonPagedPool, siz);

        if(deviceDescriptor) 
		{

			// 构造请求
            UsbBuildGetDescriptorRequest(
                    urb, 
                    (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
                    USB_DEVICE_DESCRIPTOR_TYPE, 
                    0, 
                    0, 
                    deviceDescriptor, 
                    NULL, 
                    siz, 
                    NULL);


#define UsbBuildGetDescriptorRequest(urb, \
                                     length, \
                                     descriptorType, \
                                     descriptorIndex, \
                                     languageId, \
                                     transferBuffer, \
                                     transferBufferMDL, \
                                     transferBufferLength, \
                                     link) { \
            (urb)->UrbHeader.Function =  URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE; \
            (urb)->UrbHeader.Length = (length); \
            (urb)->UrbControlDescriptorRequest.TransferBufferLength = (transferBufferLength); \
            (urb)->UrbControlDescriptorRequest.TransferBufferMDL = (transferBufferMDL); \
            (urb)->UrbControlDescriptorRequest.TransferBuffer = (transferBuffer); \
            (urb)->UrbControlDescriptorRequest.DescriptorType = (descriptorType); \
            (urb)->UrbControlDescriptorRequest.Index = (descriptorIndex); \
            (urb)->UrbControlDescriptorRequest.LanguageId = (languageId); \
            (urb)->UrbControlDescriptorRequest.UrbLink = (link); }

Urb: 用来输出的URB结构的指针

Length: 用来描述该UR结构的大小

DecscriptorType: 描述该URB的类型。它可以是USB_DEVICE_DESCRIPTOR_TYPE\

USB_CONFIGURATION_DESCRIPTOR_TYPE和USB_STRING_DESCRIPTOR_TYPE

Index : 用来描述设备描述符的索引

LanguageId: 用来描述语言的ID

TransferBuffer: 如果用缓冲区读取设备,TransferBuffer是缓冲区内存指针

TransferBufferMDL:  如果用直接读取内存时,TransferBufferMDL是直接读取内存指针时MDL的指针

transferBufferLength : 对于该URB所操作内存的大小

 

 

 

1、8、2  发送URB

创建完URB后,需要创建并发送一个内部I/O控制(IOCTL)请求到USBD驱动程序

USBD驱动程序位于驱动程序层次结构的低端。需要等待设备回应。

NTSTATUS SendAwaitUrb(PDEVICE_OBJECT fdo, PURB urb)
{
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
	KEVENT event;
	KeInitializeEvent(&event, NotificationEvent, FALSE):
	IO_STATUS_BLCOK iostatus;
	PIRRIrp = IoBuildDeviceIoControlRequest(IOCTRL_INTERNAL_USB_SUBMIT_URB,
		pdx->LowerDeviceObject, NULL, 0, TRUE, &event, &iostatus);
	PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
	stack->Parameters.Others.Argumentl = (PVOID)urb;
	NTSTATUS status = IoCallDriver(pdx->LowerDeviceObject, Irp);
	if(status == STATUS_PENDING)
	{
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
		status = iostatus.Status;
	}
	return status;
}

1、8、3  URB返回的状态

当提交一个URB到USB总线驱动程序时,我们将收到一个描述该操作结果的NTSTATUS代码。状态码的定义你可以在DDK的头文件NTSTATUS.H中找到)

当USBD完成一个URB时,它就把URB的UrbHeader.Status域设置为某个USBD_STATUS值,

DDK中的URB_STATUS宏可以简化这个值的提取:

 

NTSTATUS status = SendAwaitUrb(fdo, &urb);

USBD_STATUS ustatus = URB_STATUS(&urb);

 

1、9 关于USB设备的配置

1、9、1 

USB总线驱动程序自动检测新插入的USB设备。然后读取设备内的设备描述符以查明插入的是何种设备,

描述符中的厂商和产品标识及其它描述符一同决定具体安装哪一个驱动程序。

1、9、2

配置管理器调用驱动程序的AddDevice函数AddDevice做所有你已知的任务:

创建设备对象、

把设备对象连接到驱动程序堆栈上

。。。。。

1、9、3

配置管理器向驱动程序发送一个即插即用请求IRP_MN_START_DEVICE.

1、9、4

通过调用StartDevice函数并传递一些参数,这些参数描述了赋予设备的经过

转换的未经转换的I/O资源

StartDevice执行过程:

首先为设备选择一个配置。

接着选择配置中的一个或多个接口。

然后向总线驱动程序发送配置选择URB。

最后,总线驱动程序向设备发出命令使能选定的配置和接口。

补充:总线驱动程序负责驱动程序与选定接口端点之间的通信,

            它同时还创建配置句柄和接口句柄,我们可以从完成的URB中提取这些句柄并保存为以后使用。

           也可以通过URB成员UrbSelectConfiguration.ConfigurationHandle返回该配置句柄;

           USBD_INTERFACE_INFORMATION结构中的InterfaceHandle返回接口句柄;

           每个USBD_PIPE_INFORMATION结构中都含有与每个端点对应的管道句柄PipeHandle.

 

 


 

NTSTATUS StartDevice(PDEVICE_OBJECT fdo)
{
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
	<configure device)
    return STATUS_SUCCESS;
}


 

 2、应用软件设计

2、1  分类

      应用软件分两部分组成:

   链接库程序

       它负责与USB功能驱动程序通信并接受应用程序的各种操作请求

   应用程序

       负责对所采集的数据进行分析处理

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

windows xp 驱动开发(五) USB驱动程序、应用软件概述 的相关文章

  • python+win32:检测窗口拖动

    有没有办法检测何时使用 python pywin32 在窗口中拖动不属于我的应用程序的窗口 我想对其进行设置 以便当我拖动标题与桌面边缘附近的图案匹配的窗口时 当松开鼠标时它会捕捉到边缘 我可以编写代码 以便在释放鼠标时将所有具有该标题的窗
  • 如何在Windows上分离“Git bash”中启动的“git gui”?

    例如 我开始 git bash 我导航到某个目录 I start git gui 我关闭控制台窗口或按 Ctrl C Git gui 的窗口消失了 即使我用过git gui disown 即使当我按 Ctrl C 时它不在前台 如何正确分离
  • Qt 支持 Windows 蓝牙 API 吗?

    谁能告诉我 Qt 是否支持 Windows 蓝牙 API 如果是这样 您能否分享一些有关如何使用它的信息 自上次答复以来 这个问题的答案发生了一些变化 Qt 5 2 版为 Linux BlueZ 和 BlackBerry 设备实现了蓝牙 A
  • 如何在批处理文件中回显换行符?

    如何从批处理文件输出中插入换行符 我想做类似的事情 echo hello nworld 这会输出 hello world Use echo hello echo echo world
  • 对于多重继承,使用隐式转换而不是 QueryInterface() 是否合法?

    假设我有一个类实现两个或多个 COM 接口 正如here https stackoverflow com questions 1742848 why exactly do i need an explicit upcast when imp
  • 调用 printf 系统子例程在汇编代码中输出整数错误[重复]

    这个问题在这里已经有答案了 来回 在windows7控制台窗口中运行gcc s2 asm 然后生成一个exe文件 运行a exe 然后崩溃 为什么 s2 asm 代码由以下源代码生成 int m m 1 iprint m s2 asm请参考
  • 不在焦点时响应键盘? (C#、Vista)

    我正在尝试编写一个应用程序 只要按下 Shift 键 无论当前哪个应用程序具有焦点 它都会做出响应 我尝试过这个SetWindowsHookEx 与GetKeyboardState 但这两种方法仅在应用程序窗口具有焦点时才有效 我需要它在全
  • SetCurrentDirectoryW 中的错误 206

    在我之后之前不清楚的问题 https stackoverflow com questions 44389617 long path name in setcurrentdirectoryw 我以某种方式能够创建一个具有长路径名的目录 但是
  • 如何向未知用户目录读取/写入文件?

    我正在尝试从用户目录 C Users USERNAME Test Source 读取和写入文件 但我未能成功找到任何有关如何自动检测用户名的资源 其中的 USERNAME上面的例子 或者无论如何 我可以让它读取和写入目录 而不需要知道用户名
  • 在 Win7 登录屏幕上运行应用程序[重复]

    这个问题在这里已经有答案了 我想通过服务在 Windows 7 的登录屏幕上运行应用程序 我对此进行了长期研究并尝试了不同的方法 但不幸的是到目前为止还没有完全成功 我设法在当前登录用户的锁定屏幕上运行该应用程序 起初我认为这就是我基本上试
  • 尽管 if 语句,Visual Studio 仍尝试包含 Linux 标头

    我正在尝试创建一个强大的头文件 无需更改即可在 Windows 和 Linux 上进行编译 为此 我的包含内容中有一个 if 语句 如下所示 if defined WINDOWS include
  • Sencha Cmd 5 + Java 8 错误

    在我的 Windows 构建服务器上安装 Java 8 JDK 后 执行以下命令时遇到以下错误sencha命令 C gt sencha Error Registry key Software JavaSoft Java Runtime En
  • Windows 10 上的 LibPNG 构建问题

    我试图在 Windows 10 上构建 libpng 以获取 win32 二进制文件 但我认为有一个与 awk 解析带有 CRLF 行结尾的文件相关的问题 我尝试使用 dos2unix 命令转换文件 但没有成功 结果相同 在 make 命令
  • 生成尽可能最快的可执行文件

    我有一个非常大的程序 我一直在 Visual Studio 下编译 v6 然后迁移到 2008 我需要可执行文件尽可能快地运行 该程序大部分时间都花在处理各种大小的整数上 并且执行很少的 IO 显然 我会选择最大优化 但似乎可以做很多不属于
  • SetWindowsHookEx 函数返回 NULL

    我正在研究 DLL 注入 但收到错误如下 挂接进程失败 87 参数不正确 目标进程和dll都是64位的 注入代码为 BOOL HookInjection TCHAR target TCHAR dll name https msdn micr
  • TRACKER:错误TRK0005:无法找到:“CL.exe”。该系统找不到指定的文件

    我尝试在 Windows 8 上的 Node js 项目中执行以下命令 npm 安装 电子邮件受保护 cdn cgi l email protection 但我收到一个错误 我不知道如何处理 TRACKER 错误TRK0005 无法找到 C
  • 将 OpenBLAS 链接到 MinGW

    我正在尝试链接OpenBLAS https www openblas net 图书馆与明GW w64 https mingw w64 org Windows 上的编译器 这是我的代码 include
  • 为什么 Git Bash 无法运行我的可执行文件?

    I am on git for windows https github com git for windows 吉特 巴什 我无法在命令行上运行可执行文件 Pedr Abc 07 MINGW64 c dev ls sqlite3 exe
  • 如何处理来自单独线程的窗口消息?

    我希望启动一个单独的线程来处理窗口消息 通过阻塞 GetMessage 循环 但之后仍然在初始线程中创建窗口 在单独的线程中 一旦启动 我就会调用PeekMessage使用 PM NOREMOVE 确保消息队列存在 有必要吗 然后 Atta
  • npm package.json bin 无法在 Windows 上运行

    我正在尝试通过 package json 启动我的 cli 工具bin财产 我有以下内容 name mycli bin bin mycli 当我在包路径中打开 cmd 并输入 mycli 时 它表示该命令无法识别 我应该运行 npm 命令吗

随机推荐

  • Out-Of-Vocabulary(OOV)的理解

    OOV 问题是NLP中常见的一个问题 其全称是Out Of Vocabulary 下面简要的说了一下OOV 怎么解决 下面说一下Bert中是怎么解决OOV问题 如果一个单词不在词表中 则按照subword的方式逐个拆分token 如果连逐个
  • 汉字简/繁体转换

  • C语言:删除字符

    本题要求实现一个删除字符串中的指定字符的简单函数 函数接口定义 void delchar char str char c 其中char str是传入的字符串 c是待删除的字符 函数delchar的功能是将字符串str中出现的所有c字符删除
  • 【kernel envirment】config tiny X86 kernel with vfs

    Automatically generated file DO NOT EDIT Linux x86 4 19 0 Kernel Configuration Compiler gcc Ubuntu 7 3 0 27ubuntu1 18 04
  • cocos creator 血条跟随3d convertToUINode导致的问题,使用worldToScreen解决跟随偏离问题

    cocos creator3 3 2 实现血条跟随 一开始使用的camera的converToUINode 也是按照麒麟子大师的博客操作 结果最终的效果 在屏幕中间 血条显示正常 在屏幕边缘就开始出现偏差 x和y都有的偏差 最终也没有解决方
  • 择后自动上传html代码,GitLab + Jenkins + Webhook 实现Push代码后自动更新

    一 介绍 通常是开发后的代码先推到Gitlab上管理 然后在Jenkins里通过脚本构建代码发布 这种方式每次在发版的时候 需要人工去执行jenkins上的构建动作 有时显得过于繁琐 Gitlab的Webhook功能 通过Webhook的相
  • 九月份参加OPPO和腾讯Android面试:技术一面+二面+三面+HR四面,我的面经总结!

    之前很多时候我是拒绝说我的面试经验的 因为我们简历经历不一样问的问题也会不一样 且大厂面试光靠背几个面试题就想过还是比较难的 因此在这里提醒一下大家不要临时抱佛脚 你花几天能背下的东西 别人花几天一定能超过你的 但我们花几年沉淀的东西 人家
  • 公司企业微信小程序创建步骤

    随着新一代互联网的发展 小程序已经成为当今社会不可或缺的重要部分 它的简单易用 公司企业小程序是一种基于微信平台构建的应用程序 旨在为企业提供灵活便捷的营销服务 关于公司企业微信小程序创建步骤 可分为以下几个部分 一 申请微信公众号并创建小
  • 2022年,软件测试还能学吗?别学了,软件测试岗位饱和了...

    8年前 我懵懂的选择了软件测试这个行业 穷困潦倒的时候 爸妈给我付了2万块钱进入了一家培训机构 我怀着感激和破釜沉舟的情绪开始学习软件测试 3个月的学习时间 住群租宿舍 吃盒饭 平时上课认真听讲 周末就跑自习室 在学了基础课程之后 找工作的
  • vue中纯JS调用自定义组件

    案例以vant为例 1 首先创建index vue index js文件 2 index vue跟我们平常写的组件是一样的
  • 51单片机学习笔记(十二) - 红外遥控

    文章目录 前言 一 红外遥控的背景知识 1 人机交互 2 红外遥控的相关知识 二 原理图电路分析 三 NEC协议讲解 1 逻辑1与逻辑0的表示 2 NEC协议格式 3 NEC协议的关键点 四 代码实现 总结 前言 随着科技的发展 红外遥控器
  • 【Spring Boot整合MyBatis教程】

    Spring Boot是由Pivotal团队提供的全新框架 其设计目的是用来简化新Spring应用的初始搭建以及开发过程 该框架使用了特定的方式来进行配置 从而使开发人员不再需要定义样板化的配置 通过这种方式 Spring Boot致力于在
  • 用4种语言编写端口扫描器(Java、C、Python、Go)

    Java import java net InetSocketAddress import java net Socket import java util concurrent ExecutorService import java ut
  • Ghost-Docker(五)Nginx+SSL+Https

    使用 Ngins SSL 证书 为 Ghost 实现 Https 访问 HTTPS 协议是由 HTTP 加上 TLS SSL 协议构建的可进行加密传输 身份认证的网络协议 主要通过数字证书 加密算法 非对称密钥等技术完成互联网数据传输加密
  • ElementUI过渡动画篇

    ElementUI过渡动画篇 element官方提供的过渡动画并不能很好的满足使用 我尝试过几种过渡动画的设置方式 最终选择了Animate css 一 使用方法 引入 引入 npm install animate css save 然后在
  • 保姆级连载讲义学Python:第四篇多文件项目的演练

    多文件项目的演练 开发 项目 就是开发一个 专门解决一个复杂业务功能的软件 通常每 一个项目 就具有一个 独立专属的目录 用于保存 所有和项目相关的文件 一个项目通常会包含 很多源文件 目标 在项目中添加多个文件 并且设置文件的执行 多文件
  • FastAPI从入门到实战(10)——响应模型与状态码

    前面一直记录的是请求相关的内容 这篇文章开始记录一下响应相关的内容 包括请求模型和模型继承以及状态码等相关的内容 一个任意dict构成的基本响应 任意dict构成的响应 app06 get stu06 dict response model
  • STL容器使用中的拷贝成本

    include stdafx h include
  • ESP32基础应用之HTTP 服务器

    文章目录 1 HTTP服务器简介 2 ApiPost测试工具 3 HTTP服务器实验 3 1 ApiPost之GET测试 3 2 ApiPost之POST测试 3 3 ApiPost值PUT测试 参考资料 esp32 http服务器编程指南
  • windows xp 驱动开发(五) USB驱动程序、应用软件概述

    转载请标明是引用于 http blog csdn net chenyujing1234 欢迎大家提出意见 一起讨论 1 USB设备驱动程序 WDM模型 1 1 分类 USB设备驱动程序的设计是基于微软件的WDM WDM采用分层驱动程序模型