DDK驱动开发笔记
1、windows驱动分为NT式驱动和WDM式驱动,前者为非即插即用,后者为即插即用驱
动。需要头文件分别为NTDDK.h和WDM.h
2、驱动的入口函数均为extern "C" NTSTA TUS DriverEntry(IN PDRIVER_OBJECT
pDriverObject, IN PUNICODE_STRING pRegistryPath),它由I/O管理器负责调用,前参数为传递进来的驱动对象,后参数为Unicode字符串,指向此驱动的注册表。
3、驱动程序向windows的I/O管理器注册一些回调函数,回调函数是由程序员定义的函数,
由操作系统负责调用,只要把地址告诉操作系统即可如:pDriverObject->DriverUnload=HelloDDKUnload;
4、使用CreateDevice函数创建驱动设备对象如:CreateDevice(pDriverObject);返回
NTSTA TUS类型
5、KdPrint是一个宏,用于打印输出信息,在Checked中会使用DbgPrint代替,在Free版
本中无效果,用法和TRACE一致。
6、Windows的设备管理是使用线性链表进行管理,每一个节点记录了设备对象的地址,每
次要对指定驱动进行操作,就必须先遍历设备对象链表。
7、设备对象函数NextDevice域记录下一个设备对象的地址,IoDeleteDevice用于删除设备
对象如:IoDeleteDevice(pDevExt->pDevice),IoDeleteSymbolicLink用于删除设备符号链接。
8、DDK环境编译驱动源程序,需要使用两个自己创建的脚本makefile和Sources,最好使
用二进制文本格式,makefile的内容固定为:!INCLUDE $(NTMAKEENV)\makefile.def。
sources文件记录了驱动的名称、驱动类型、编译输出目录、include目录、指定源文件。
编译好的文件会再工程目录的objchk_wxp_x86\i386文件夹里生成.sys文件。
9、对于使用其他编译环境,只能使用VS编译环境,vc6编译环境只能支持到win2000的
DDK。
10、配置VS+DDK+DDKWizard:
11、使用DriverMonitor安装驱动,对于NT驱动,在设备管理器中默认是隐藏的(可
更改),本软件用于测试驱动。
12、WDM中使用AddDevice回调函数创建设备对象并由PNP(即插即用plug and play)
管理器调用,然后设置对IRP_MJ_PNP的IRP(I/O Request Packages)的回调函数,对PNP的IRP处理是WDM和NT驱动的重大区别之一。在WDM程序中,大部分卸载工作放在对IRP_MN_REMOVE_DEVICE的IRP的处理函数中处理。
13、在WDM的驱动程序中,创建设备对象需要驱动程序向系统注册一个称作
AddDevice的例程,由PNP调用如:NTSTA TUS HelloAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject); 对象->AddDevice=HelloAddDevice;
14、WDM驱动的安装需要使用INF文件安装,其中编译的时候其Sources文件有所不
同。要安装WDM驱动,要先为驱动程序编写一个inf文件,该文件描述了驱动的操作硬件设备信息和驱动的一些信息,并存放在源文件的同一目录下。Inf的信息是提供给SDK使用的。
15、安装WDM驱动,由于该驱动是一个虚拟设备,因此需要使用添加硬件的方式使
用inf安装。快速安装的话可以使用DriverStudio的EzDriverInstaller工具直接安装。16、Windows系统的设计思想采用CS架构,内核到硬件之间使用HAL(硬件抽象层)
作为过渡。Native API穿越了用户层和内核层。为能将其他操作系统程序移植到windows
上才采用了子系统的设计模式。
17、Windows API分为三类:USER函数(窗口控件)、GDI函数(绘图)、KERNEL函
数(其他有关内核操作的一些进程、线程之类的操作)。在运行应用程序的时候,系统都会把这三种DLL装载到内存中(user32.dll\gdi32.dll\kernel.dll)。user32和gdi32模块是在内核模式下实现的,所以图形效率非常高。
18、除了WIN32其他子系统都不是WINDOWS的纯正系统。有OS/2子系统(苹果)、
POSIX子系统(UNIX),WOW子系统(是使低bit的win程序在高bit的win上运行)、VDW子系统(Virtual DOS Machine),但这些系统都要调用win32的特定api才能运行,属于一个虚拟机概念。
19、Windows API是调用Native API的,对应的函数都是在API函数前加Nt,如
NtCreateFile(由I/O管理器调用),Native API是用户模式通往内核模式的大门,它通过软件中断方式进入内核模式,并调用内核的系统服务。Native API没有文档,适当时候可以使用,不推荐。
20、Windows规定,将4G的虚拟内存分成两部分,0~0x7fffffff为用户模式地址,以上
为内核模式地址。
21、I/O管理器,用来发出IO请求,让用户发出的IO请求独立与设备,使用IRP的请
求形式,把操作的参数内容存进缓冲区再读到内存中,IRP被传递到具体的设备驱动程序中,驱动程序完成IRP并返回到用户程序中,实际上,I/O管理器担当着用户模式代码和设备驱动程序之间的接口。
22、配置管理程序,它用来记录计算机软硬件的配置信息,使用叫“注册表”的数据库
保存数据,驱动程序根据表中信息进行加载操作。
23、内核为执行组件提供最基本的支持,负责提供进程和线程的调度,通过自旋锁(spin
lock)提供多CPU同步支持。功能:对内核对象的支持、对线程的调度、对多处理器同步的支持、中断处理函数的支持、对错误陷阱的支持、对其他硬件特殊功能的支持。24、Windows与微内核:微内核可以看做是组件化内核,每个组件都独立运行,降低模
块间的耦合性,单一内核是可以看做所有组件都与一个中央内核有联系而存在高耦合性,linux就是典型的单内核系统。Windows是单内核与微内核并存的系统(由user32和gdi32可以由内核模式实现说明)。
25、Windows为了简化对不同设备的操作,实现对不同设备的统一接口,将所有设备以
普通文件看待,都用操作文件的办法去操作设备。
26、DbgView可以用于监听内核和win32上层程序的调试信息(免费)。比WinDbg好
用。
27、设备驱动程序的动态加载由服务控制管理程序(server control manage,SCM)系统
组件完成,其实就是修改注册表实现。操作是四个步骤:为NT驱动创建新的服务、开启此项服务、关闭此项服务、删除N T驱动所创建的服务。
28、SCM函数:
A、SC_HANDLE OpenSCManager(…):打开SCM管理器用于SCM的初始化
B、B OOL CloseServiceHandle(…):关闭SCM管理器用于SCM的清除工作
C、S C_HANDLE CreateService(…):创建SCM管理器句柄,或说创建服务,所有操作都
基于此进行
D、SC_HANDLE OpenService(…):打开服务
E、B OOL ControlService(…):控制服务,发送控制码进行操作服务。
29、使用VC提供的一个附加工具GUIDGEN.exe可以产生一组新的GUID,工具在
Microsoft Visual Studio\Common\Tools里。
30、驱动对象DRIVER_OBJECT由I/O管理器负责加载,由DriverEntry对其进行初始
化。Typedef struct _DRIVER_OBJECT{…};驱动对象可以对应多个设备对象,在系统中是使用链表存储驱动对象。
31、设备对象DEVICE_OBJECT,在系统中使用链表存储设备对象。在驱动程序中尽
量避免全局变量的使用,因为全局变量设计不容易同步问题,可以将全局变量存放在设备扩展里。
32、设备扩展:设备对象记录“通用”设备的信息,而另外一些“特殊”信息记录在设
备扩展里。设备扩展由程序员指定内容和大小,由I/O管理器创建并保存在非分页内存中。在驱动程序中,尽量避免使用全局函数,因为全局函数往往导致函数的不可重入性。
重入性指在多线程的程序中,多个函数并行运行,函数的运行结果不会根据函数的调用先后顺序而导致不同。解决的办法是,将全局变量以设备扩展的形式存储,并加以适当的同步保护措施。由于设备扩展是驱动程序专用的,它的结构必须在驱动程序的头文件中定义。
33、设备扩展定义:
在设备扩展仲,记录以上几个信息,以备其他回调函数或者派遣函数使用。使用的时候,只需从驱动设备中获取,类似以下代码:
34、WinObj.exe和DeviceTree.exe用于观察驱动对象和设备对象。它由windows内核专
家编写。
35、WDM模型中完成一个设备的操作,至少有PDO和FDO两个设备对象共同完成,
两者的关系是附加与“被附加”的关系。WDM程序负责创建FDO,PDO由总线驱动创建,FDO创建后被附加到PDO上。当FDO附加在PDO上的时候,PDO设备对象的子域AttachedDevice会记录FDO的位置。PDO被称作底层\下层驱动,FDO被称作高层\上层驱动。通过函数IoAttachDeviceToDeviceStack实现FDO附加到PDO上。
36、内存管理
37、要指定某个例程和某个全局变量是载入分页内存还是非分页内存,需要做如下定
义:
#define PAGEDCODE code_seg(“PAGE”)
#define LOCKEDCODE code_seg()
#define INITCODE code_seg(“INIT”)
#define PAGEDCODE data_seg(“PAGE”)
#define LOCKEDCODE data_seg()
#define INITCODE data_seg(“INIT”)
如果将某个函数载入到分页内存中,我们需要在函数的实现中加入代码:
#pragma PAGEDCODE
VOID SomeFunction()
{
PAGED_CODE();
//内容
}
PAGED_CODE宏会检验函数是否运行低于DISPA TCH_LEVEL的中断请求级,等于或高于这个请求级将产生一个断言用来防止蓝屏。
如果是加载到非分页内存:
#pragma LOCKEDCODE
VOID SomeFunction()
{
//内容
}
要是某个例程需要在初始化的时候载入内存,然后就可以从内存中卸载掉。这种情况指出现在DriverEntry情况下,尤其是NT驱动,DriverEntry会很长,占据很大的空间,为了节省内存,需要及时从内存中卸载,代码如下:
#pragma INITCODE
Extern “C” NTSTA TUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN
PUNICODE_STRING RegistryPath){}
38、分配内核内存:内核的内存非常稀少,局部变量存放在Stack中,所以不适合用递
归或大的局部结构体,如果要用大结构体,可以在Heap中申请:
PVOID ExAllocatePool(IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes);
PVOID ExAllocatePoolWithTag(IN POOL_TYPE PoolType,IN SIZE_T NumberOfBytes, IN ULONG Tag);
PVOID ExAllocatePoolWithQuota(IN POOL_TYPE PoolType, IN SIZE_T
NumberOfBytes);
PVOID ExAllocatePoolWithQuotaTag(IN POOL_TYPE PoolType, IN SIZE_T
NumberOfBytes, IN ULONG Tag);
39、
40、