* 通过 NTSTATUS 获得相应的字符串
使用函数RtlNtStatusToDosError可以获得与NTSTATUS相对应的Windows错误码。
微软网站上说用API函数 FormatMessage 可获得相应的字符串,但是在调用前必须先用 LoadLibrary 载入 "NTDLL.DLL"。
文章链接 - http://support.microsoft.com/kb/259693
* 内存操作
尽量不要使用C运行时提供的内存操作函数,应该使用 RtlCopyMemory;RtlFillMemory;RtlZeroMemory 以便于程序移植。
* 字符串操作
尽量不要使用C运行时提供的字符串操作函数,应该使用 RtlString{xxx};RtlUnicodeString{xxx} 系列安全字符串操作函数,以便于程序移植。
在包含安全字符串操作的头文件 ntstrsafe.h 之前必须定义宏 NTSTRSAFE_LIB,以下是 ntstrsafe.h 里的原文:
//
// The following steps are *REQUIRED* if ntstrsafe.h is used for drivers on:
// Windows 2000
// Windows Millennium Edition
// Windows 98 Second Edition
// Windows 98
//
// 1. #define NTSTRSAFE_LIB before including this header file.
// 2. Add ntstrsafe.lib to the TARGET_LIBS line in SOURCES
//
// Drivers running on XP and later can skip these steps to create a smaller
// driver.
//
* 卷上的文件系统路径
用户模式下的文件系统路径必须在前面加上"\??\"才能被内核模式的程序识别,例如"\??\C:\file"。
* 操作文件
用 Zw{xxx}File 系列函数可以实现类似用户模式的文件操作。
* 获得系统时间
用 KeQuerySystemTime 获得从 1601-01-01 以来的时间计数,单位为100纳秒,时区为格林威治。
用 ExSystemTimeToLocalTime 将格林威治时间值转换成本地时区时间值。
用 RtlTimeToTimeFields 将时间值转换成 TIME_FIELDS 结构。
* 测试当前操作系统的版本信息
使用 PsGetVersion 获得操作系统版本。
最低兼容到Windows XP的程序应该使用 RtlGetVersion 获得操作系统版本。
使用 RtlIsNtDdiVersionAvailable 获得设备驱动接口版本。
* 文件系统驱动处理相对路径
处理IRP_MJ_CREATE时,如果IRP的FileObject使用的相对路径,可以通过FileObject的RelatedFileObject获得相对路径所在的目录信息。
* 链表操作
Windows DDK提供了SINGLE_LIST_ENTRY结构和LIST_ENTRY结构来支持单向和双向的链表操作。
* 操作线程间的共享资源
在用户模式下通常使用互斥对象来确保共享资源在同一时间下只能一个线程访问,其它要访问共享资源的线程则进入睡眠状态。
在内核模式中虽然也有互斥对象,但是还提供了自旋锁。
自旋锁和互斥对象的功能类似,但是它不会让等待的线程进入睡眠状态,而是让等待的线程一直在循环查询自旋锁的状态。
所以当访问共享资源的过程占用时间很短时,应该使用自旋锁,这样可以降低系统开销。
注意:获得自旋锁的所有权时,当前IRQL会变为DISPATCH_LEVEL,直到释放所有权为止。
* FILE_SUPERSEDE 和 FILE_OVERWRITE_IF 的区别
ZwCreateFile 函数的 CreateDisposition 参数可以设置为 FILE_SUPERSEDE 或者 FILE_OVERWRITE_IF,似乎都是覆盖老文件,但还是有一些小区别的。
FILE_SUPERSEDE - 先将老文件删除,在新建一个文件。
FILE_OVERWRITE_IF - 不删除老文件,只是清空它的内容,其它属性保留。
* 线程操作
使用 PsCreateSystemThread 创建线程。
使用 PsGetCurrentThread 获得当前线程句柄。
使用 KeDelayExecutionThread 让当前线程睡眠一段指定的时间。
* 内核模式里的几种互斥对象
Mutex - 是标准的信号对象,支持所有等待信号相关的函数;可以在一个线程内递归获取所有权而不被阻塞;操作速度慢;所有者能够收到APC;所有者不能被换出内存。
Fast Mutex - 不是标准的信号对象;不能被递归获取所有权;操作速度快;取得所有权后IRQL会变为APC_LEVEL直到释放所有权为止;从Windows 2000开始提供。
Guarded Mutexes - 不是标准的信号对象;不能被递归获取所有权;操作速度更快;所有者不能够收到APC;从Windows Server 2003开始提供。
* 访问注册表
所有注册表路径必须以"\REGISTRY"开头,可打开的根注册表键有"\REGISTRY\MACHINE"和"\REGISTRY\USERS",分别对应用户模式下的"HKEY_LOCAL_MACHINE"和"MACHINE HKEY_USERS"。
用 Zw{xxx}Key 和 Zw{xxx}Value 系列函数可以实现类似用户模式的注册表操作。
* 用DDK工具编译时显示编译器警告信息
使用DDK的build编译程序时默认情况下不会显示编译的警告信息,需要加上-w参数才行。
* 打开文件系统对象的特殊方式
文件系统驱动接收到IRP请求IRP_MJ_CREATE时,如果IrpSp->Flags指定了SL_OPEN_TARGET_DIRECTORY,则表示并不是真的要打开指定的文件系统对象,而是要检查对象是否可以删除已经它所在的目录是否可以进行创建操作。 通常这样的请求会发生在重命名文件系统对象之前。
* 通过句柄获得对象信息
通过函数ObReferenceObjectByHandle可以获得句柄引用的对象信息。
* IRQL
IRQL(Interrupt ReQuest Level)表示代码运行的中断请求等级,Windows定义了0-31个IRQL,除了16个硬件中断,还虚拟了16个软件中断,IRQL越高代码运行的优先级也就越高。
可以使用函数KeGetCurrentIrql、KeLowerIrql和KeRaiseIrql对IRQL进行相应的操作。
* 奇怪的IO控制码
存储设备的驱动程序有时候会收到0x004D0008这类IO控制码,通过控制码的组成方式能够得出设备类型为0x4D,但是在ntddk.h里并没有发现定义为0x4D的设备类型。其实这是存储设备加载管理器的设备类型,打开mountmgr.h查看,可以发现如下宏定义:
#define MOUNTDEVCONTROLTYPE ((ULONG) 'M')
这就是存储设备加载管理器的设备类型,字母'M'的ASCII码就是0x4D,这个文件里还定义了存储设备加载管理器的IO控制码。
* 获得一个内核对象的名称
很多内核对象在创建时都指定了名称,例如驱动程序对象、设备对象和文件对象等,可以通过调用函数ObQueryNameString获得指定对象的完整名称。
* 打开文件系统对象的特殊方式
文件系统驱动接收到IRP请求IRP_MJ_CREATE时,如果IrpSp->Flags指定了SL_OPEN_TARGET_DIRECTORY,则表示并不是真的要打开指定的文件系统对象,而是要检查对象是否可以删除已经它所在的目录是否可以进行创建操作。 通常这样的请求会发生在重命名文件系统对象之前。
* 文件系统驱动处理相对路径
处理IRP_MJ_CREATE时,如果IRP的FileObject使用的相对路径,可以通过FileObject的RelatedFileObject获得相对路径所在的目录信息。
* 提供正确的存储空间信息
处理IRP_MJ_QUERY_VOLUME_INFORMATION时,如果要查询的信息类型为FileFsSizeInformation或者FileFsFullSizeInformation,必须没有提供正确的存储空间参数,可能会造成资源管理器查看文件属性时崩溃。尤其是信息结构的BytesPerSector成员,千万不要设置为0。
* 支持文件的内存映射
Windows的文件内存映射机制能够缓解文件的IO次数,比如CreateFileMapping函数。要实现最基本的文件映射支持,文件系统驱动必须在处理IRP_MJ_CREATE时给FileObject的SectionObjectPointer成员赋值,并设置SectionObjectPointer的成员都为NULL值。FileObject的FsContext成员必须指向一块已分配的并全部填充为0的内存区,这块内存区的大小不能小于FSRTL_COMMON_FCB_HEADER类型的字节数。在处理IRP_MJ_CLEANUP时,如果FileObject的SectionObjectPointer的DataSectionObject成员不为NULL,应该调用CcPurgeCacheSection函数清除缓存管理器产生的数据。
* 正确处理IRP_MJ_CREATE
文件系统驱动的IRP_MJ_CREATE不仅要处理打开文件或目录,还要处理打开卷。如果FileObject的FileName字节数为0,则表示要打开的是卷。