最近一段时间,我一直在研究 windows 驱动开发,下面跟大家简单聊聊:
对比 linux,windows 驱动无论是市面上的书籍,视频还是社区,博文以及号主,写的人很少,导致学习曲线直线上升;
windows 驱动 从业 人员就更少了;
开发环境部署麻烦;
驱动安装发布麻烦,需要数字签名。如果是发布到windows update 库里面,还需要做微标认证。
为什么还要写?因为在学习的过程中,发现很多东西还是很相同的,如果你是从事linux 开发,可能会有些启发,如果是对windows 驱动开发有需求,可能提供一些不成熟的建议。
接下来进入正文,给大家简单介绍一下windows 驱动。
2. windows 体系架构
2.1 操作系统与应用程序
在许多现代操作系统中,应用程序和操作系统是相互隔离的。操作系统的核心代码运行在特权模式下,即内核模式。而应用程序运行在非特权模式下,即用户模式。
操作系统和应用程序的关心类似于服务器和客户端的关系,这点在windows 平台下显得更加突出:
几个概念:
system 进程:windows 操作系统本身会起一个 system 进程(加载kernel32.dll),有点类似于 linux 下的 init进程,具体细节不展开。
FDO (Function Driver Object):设备功能驱动
FiDO (Filter Driver Object):过滤驱动
PDO (Physical Driver Object):物理设备驱动,真正访问硬件的地方。
IRP (I/O Request Packet),应用程序 想要访问内核数据,必须通过IRP 传递。又叫IRP请求,当应用程序和驱动交互时,发送一个IRP 请求,IRP 会在各层设备驱动之间来回传动与转发。
2.2 操作系统分层
windows 的设计思想是将内核设计的尽可能的小,并且采用“客户端-服务器”的结构。操作系统各个组件或者模块是通过消息进行通信的。
嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!
无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。
点击这里找小助理0元领取:加微信领取资料
win32 子系统:是最纯正的windows 系统,其他子系统都是通过win32 子系统的接口来实现的,一般很少用到。
Natvie API :在win32 api 基础上加上Nt 前缀,基于版本兼容考虑
系统服务:Native API 从用户模式进入内核模式,调用系统服务。(软中断方式实现,陷入内核)
执行组件:内核模式下的一组服务函数。
对象管理程序:windows 操作系统提供的服务几乎都是以对象的形式存在的,这里的对象类似于面向对象语言中对象的概论。如驱动对象,设备对象等管理。
进程管理程序:负责创建和终止进程,线程调度是由内核负责的。进程管理程序依赖于其他执行组件。
虚拟内存管理程序:在CPU的内存管理单元(MMU)的协助下,通过某种映射将物理内存和虚拟内存关联起来。
I/O 管理器:负责发起I/O 请求,并管理请求。它由一系列内核模式下的例程所组成,这些例程为用户模式下的进程提供了统一接口。I/O 管理器的目标是使来自用户模式的I/O请求独立于设备。
配置管理程序:配置管理程序,记录所有计算机软,硬件的配置信息。它使用一个被称为注册表的数据库保存这些数据。设备驱动程序根据注册表中的信息进行加载
驱动程序:I/O 管理器接收应用程序后,创建相应的 IRP,并传送至驱动程序进行处理:
根据IRP的请求,直接操作硬件,然后完成此IRP,并返回
根据IRP的请求,转发到更底层的驱动中去,并等待底层驱动的返回。
接受到IRP驱动后,不着急于完成。而是分配新的IRP法定其他驱动程序,并等待返回。
内核:内核被认为是 Windows 操作系统的心脏。Windows 的内核从执行组件分割出来。和执行组件相比,内核是非常小的:
对内核对象的支持。
对线程的调度
对多处理器同步支持。
中断处理函数的支持。
对异常陷阱的支持。
对其他硬件特殊功能的支持。
硬件抽象层:不同的硬件平台,提供不同的硬件抽象层,并对上层提供统一的操作硬件的接口。
2.3 应用程序和驱动
3. windows 驱动框架
3.1 驱动模型:
windows 驱动大致分为这几类:
function driver:设备功能驱动
filter driver:设备辅助驱动
software driver:软件模块驱动
bus driver:总线设备驱动
3.2 驱动演变
NT 模型 :2000以前,不支持 PNP (即插即用设备)
WDM 模型:NT 基础上,支持 PNP
WDF:WDM的重封装
KMDF:内核模式(sys)
UMDF:用户模式(dll)
win7 x64 划时代操作系统,开始数字签名了
win10 :双认证签名,也就是说对驱动的安全性要求更高了。
3.3 驱动垂直层次结构
设备的创建顺序,先创建底层PDO,在创建高层的FDO,即从底层设备到高层设备。
在PDO 和 FDO之间可能有各种过滤驱动。每层设备对象由不同的驱动程序创建,或者说每层的设备对应着不同的驱动程序。
底层设备对象寻找上一层的设备对象,是依靠底层设备对象的AttachedDevice 来寻找的。
3.4 驱动水平层次结构
同一驱动程序创建出来的设备对象的关系称之为水平层次关系。
每一个设备通过NextDevice可以寻找水平层次的下一个设备对象。
3.5 一个复杂的驱动结构
4. windows 开发环境搭建
4.1 开发环境部署
以 win10 为例,列出需要安装的东西,详细过程限于篇幅以后更新
安装VS2019
安装 Windows SDK:VS 2019 顺带安装
安装 WDK
安装 VMware + Win10 虚拟系统
4.2 常用调试工具
windbg:调试内核。WDK 自带,配合串口或者网络调试windows 内核
DebugViewer:查看内核打印
driverMonitor:驱动安装
PCHunter_free:驱动强制卸载,不支持2004版本
devicetree:设备枚举
winobj:查看符号链接
5. windows 驱动学习建议
有关Windows 驱动开发书籍,博文,教程甚少。如果有这方面需求的可以给以下几个建议:
环境搭建:win7 32 + vs2013 即可。不建议 win10 + vs2019,比较新,遇到问题不好解决。其次早期的一些调试工具,win10 最新版本不一定支持。
绕开数字签名:先使用测试模式,安装驱动。否则光安装部署就够折腾的。
入门采用 WDM 驱动模型。市面上将WDM 的书籍和资料相对多些,相对来说,WDF 开发资料来不少。
多看书,windows 驱动不想linux 资料一大把,遇到不懂得,加技术群,啃书本。
推荐书籍:
《Windows 驱动开发技术详解》:已绝版
《竹林蹊径:深入理解windows 驱动开发》:已绝版
《windows 7:设备驱动程序开发 》:wdf 讲的比较多得书
原文链接:https://mp.weixin.qq.com/s/qGydT22PNfbvhxUT4pNmJw
转载自:嵌入式微处理器
原文链接:浅谈Windows驱动开发
https://www.bbsmax.com/A/pRdBxE07Jn/
windows驱动开发详解学习笔记
1. windows驱动分两类,NT式驱动和WDM驱动,后者支持即插即用;
2. DriverEntry是入口函数,传入参数:pDriverObject由IO管理器传入;
3. WDM驱动中,AddDevice创建设备对象,由PnP管理器调用;传入参数:(DriverObject, PhysicalDeviceObject),第一个参数是DriverEntry的传入参数,第二个参数由总线驱动创建的PDO;
4. IRP_MJ_PNP分很多子类,包括IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE、IRP_MN_STOP_DEVICE等等;
5. PE格式(Portable Execute),二进制可执行格式;
6. 函数调用这一过程用汇编语言展现出来是这样子的:参数入栈-->ebp入栈-->将esp作为ebp-->esp减一定空间(增长)-->处理-->将ebp作为esp-->ebp出栈-->返回。
7. 函数调用约定,重点区分_cdecl和_stdcall。函数在调用前后需要保持esp平衡,_cdecl是C语言默认调用约定,函数返回后由调用者将esp+参数占用字节数,保持平衡,例如调用int add(int, int)后,调用者执行 add,esp 8;_stdcall是标准调用约定,函数返回时执行ret x(参数占用字节数),自助保持堆栈平衡,例如ret 8。不同调用约定会使函数在编译阶段产生差异的符号链接名。 例如_cdecl约定下为_add,而_stdcall约定下为_add@8. 不同的符号链接名可能导致link阶段的无法解析外部符号错误。
8. windows以树形结构组织系统内的设备,称之为设备树。垂直结构,从底到上的构建设备树,总线驱动构建设备的PDO,设备驱动构建设备对象,这种垂直结构成为设备堆栈。平行结构,相同的设备拥有一致的设备堆栈。
9. 在windows系统内,每个进程有自己独立的4GB虚拟内存空间,其中低2GB(0~0x7FFFFFFF)为用户模式空间,高2GB是内核模式空间,用户态程序只能访问用户模式空间,内核程序可以访问整个4GB空间。进程切换发生时,内核空间不切换,之切换用户模式空间。
10. 在驱动程序中,DriverEntry和AddDevice是由系统进程调用的,运行在系统进程上下文;而其他的派遣函数例程运行在程序上下文。
11. 分页内存和非分页内存。在虚拟内存管理中,分页内存会被交换出物理内存,非分页内存会一直驻留在物理内存中。分页内存只能被运行在DISPATCH_LEVEL级别以下的函数使用,如果程序运行在DISPATCH_LEVEL以上,一定要用非分页内存。因为缺页异常的回调函数运行在DISPATCH_LEVEL上, DISPATCH_LEVEL以上的程序使用分页内存会导致计算机蓝屏。
12. 内核堆内存分配的函数使用ExAllocatePool和ExFreePool, 需要指定内存分配的类型。在内核模式下,无法使用C++提供的new或delete操作,因为在windows平台下,new实现依赖于win32 API,而在内核模式下是无法使用win32 API。
13. windows DDK实现了一个内置的通用双向链表结构,LIST_ENTRY,类似于Linux中使用的双向链表结构,list_head
14. windows DDK内置了内存池Lookaside,只能的避免内存空洞。
15. 微软编译器提供的结构化异常处理机制,当程序在执行过程中遇到异常,就会在当前try块外寻找except块,如果当前try块没有设置except捕获异常块,则进入上一层try块,直至交由操作系统处理。这一过程成为回卷。
16. 如果if或者else,只有单个函数或语句的情况下是允许的。但是如果函数本质是一个多行的宏定义,则容易出现很难察觉的问题。所以在每次if或者else时,都要加一个{},是非常必要的。
17. 应用程序向CreateFile传入符号链接名打开设备,一般是这个样子:
\\.\helloWDM
,写成C语言字符串成”\\\\.\\helloWDM”
18. 缓冲区读写/直接读写的区别:需要简单说明一下windows IO读写的机制。用户态程序调用win32 API WriteFile,对应到内核的Native API NtWriteFile,NtWriteFile负责创建IRP包分发给响应的Dispatch。用户态程序需要向WriteFile传入1个用户空间的数据缓冲区buf1,假设起始地址0x400。windows是多任务环境,当进程切换时,用户空间发生切换,所以NtWriteFile直接操作0x400就很可能进入其他进程的用户空间。
缓冲区读写,指的是windows负责在内核空间开辟一段相同大小的缓冲区,并将WriteFile的缓冲区复制过去,这样用户进程切换共用内核空间,不会出问题。读操作也是类似的操作。这种方式存在内核的缓冲区复制,效率较低,适合在小块内存的情况下。
直接读写:指的是,windows先锁住(不交换出物理内存)空间的缓冲区,然后将这块物理内存映射到内核空间,这样NtWriteFile操作的就是同一块物理内存,不会出问题。这种方式,涉及的过程比起简单的内存复制来说要复杂,但是效率高,适合数据量大的情况。另外,说明一下:windows内核采用MDL记录用户缓冲区到物理内存的映射关系。
19. PIC与APIC区别:PIC(Programable Interrupt Controller),是传统PC的方案,使用2片8259级联实现最多16个中断信号;目前大部分机器采用APIC(Advanced Programable Interrupt Controller),兼容PIC模式,实现最多24个中断信号。
20. 在PC机24个中断信号的基础上,windows设计了32级的IRQL。关心IRQL最低的PASSIVE_LEVEL,APC_LEVEL,DISPATCH_LEVEL。用户模式程序运行在PASSIVE_LEVEL,驱动程序的派遣函数、AddDevice、DriverEntry等一般函数也运行于PASSIVE_LEVEL,DPC和StartIO运行于DISPATCH_LEVEL,OS的线程调度程序运行于DISPATCH_LEVEL。对于线程来说,高的IRQL级别可以有更多的机会获得CPU。当线程执行ReadFile,其IRP对应的响应派遣函数运行于PASSIVE_LEVEL,与ReadFile同属于一个线程的上下文。
常常采用提高线程的IRQL的方式,实现多线程资源同步,避免切换。可是对于多核处理器,这并不好使。