一、驱动卸载的入口:DriverUnload
1. DriverUnload 例程定义
驱动卸载时,系统会调用你注册的卸载例程(DriverUnload),在 DriverEntry 中设置:
DriverObject->DriverUnload = MyDriverUnload;
2. 卸载函数原型
VOID MyDriverUnload(PDRIVER_OBJECT DriverObject) {
// 清理代码
}
二、设备对象与符号链接的清理
1. 删除符号链接
驱动创建符号链接时(如 \DosDevices\MyDeviceLink),卸载时必须删除:
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, L"\\DosDevices\\MyDeviceLink");
IoDeleteSymbolicLink(&symLink);
2. 删除设备对象
驱动创建的设备对象要逐一删除:
PDEVICE_OBJECT devObj = DriverObject->DeviceObject;
while (devObj) {
PDEVICE_OBJECT next = devObj->NextDevice;
IoDeleteDevice(devObj);
devObj = next;
}
如果只创建了一个设备对象,也可以直接
IoDeleteDevice(DriverObject->DeviceObject);
三、内存与资源的清理
1. 释放动态分配的内存
- 所有用
ExAllocatePoolWithTag、MmAllocateContiguousMemory等分配的内存都必须在卸载时释放。
if (pBuffer) ExFreePoolWithTag(pBuffer, 'tag1');
2. 释放同步对象、Lookaside List
- Lookaside List、互斥体等资源也要清理。
ExDeleteNPagedLookasideList(&myLookasideList);
// 互斥体、事件等无需显式释放,但可重置状态
3. 释放 MDL、DMA 资源
- 用
IoAllocateMdl分配的 MDL 要用IoFreeMdl释放。 - DMA缓冲区用
MmFreeContiguousMemory释放。
四、清理全局或设备扩展数据
- 如果设备扩展中保存了类对象指针或其他动态资源,记得在卸载时析构/释放。
MY_DEVICE_EXTENSION* ext = (MY_DEVICE_EXTENSION*)devObj->DeviceExtension;
if (ext->pMyClassObj) {
delete ext->pMyClassObj;
}
五、常见问题与注意事项
- 符号链接和设备对象必须都清理,否则下次加载驱动可能失败或系统残留垃圾对象。
- 所有动态分配资源都要释放,否则系统内存泄漏,影响稳定性。
- 多设备对象要循环清理,不能只清理第一个。
- IRP异步处理相关资源要妥善回收,避免悬挂IRP或未释放缓冲区。
- 清理顺序:先符号链接,再设备对象,再其他资源。
- 卸载例程不能访问已删除的设备对象或资源。
六、卸载与清理代码模板
VOID MyDriverUnload(PDRIVER_OBJECT DriverObject)
{
// 删除符号链接
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, L"\\DosDevices\\MyDeviceLink");
IoDeleteSymbolicLink(&symLink);
// 循环删除设备对象
PDEVICE_OBJECT devObj = DriverObject->DeviceObject;
while (devObj) {
// 清理扩展区资源
MY_DEVICE_EXTENSION* ext = (MY_DEVICE_EXTENSION*)devObj->DeviceExtension;
if (ext->pMyClassObj) {
delete ext->pMyClassObj;
}
// 可以清理其他资源,如缓冲区、Lookaside等
PDEVICE_OBJECT next = devObj->NextDevice;
IoDeleteDevice(devObj);
devObj = next;
}
// 清理全局资源
ExDeleteNPagedLookasideList(&myLookasideList);
// 日志
DbgPrint("MyDriver: Unloaded and cleaned up.\n");
}
七、调试与验证
- 用 WinDbg/DebugView 查看卸载日志。
- 用 PoolTag 工具和 WinDbg 的
!poolused命令检查内存泄漏。 - 多次加载/卸载驱动,验证资源是否全部释放。
- 检查设备管理器和符号链接是否残留。
八、复杂驱动卸载的资源清理流程
1. 多设备对象清理
驱动可能会动态创建多个设备对象(比如枚举硬件或支持多个虚拟设备)。卸载时必须遍历链表,逐一清理:
PDEVICE_OBJECT devObj = DriverObject->DeviceObject;
while (devObj) {
PDEVICE_OBJECT next = devObj->NextDevice;
// 清理设备扩展区资源
MY_DEVICE_EXTENSION* ext = (MY_DEVICE_EXTENSION*)devObj->DeviceExtension;
if (ext->pBuffer) ExFreePoolWithTag(ext->pBuffer, 'tag1');
if (ext->pMyClassObj) delete ext->pMyClassObj;
// 清理Lookaside List、MDL等
IoDeleteDevice(devObj);
devObj = next;
}
注意:不能在IoDeleteDevice后再访问设备扩展区内容!
2. IRP异步处理相关资源回收
- 驱动异步处理IRP时,可能有挂起的IRP、缓冲区、定时器等。
- 卸载时要主动取消所有挂起IRP,释放相关资源。
常用做法:
- 维护IRP链表或计数器,DriverUnload中遍历取消和释放。
- 用
IoCancelIrp取消IRP,用IoCompleteRequest完成IRP。
3. 全局资源与同步对象清理
- Lookaside List、环形缓冲区、全局内存、同步对象(自旋锁、事件)都要清理。
- Lookaside List用
ExDeleteNPagedLookasideList,缓冲区用ExFreePoolWithTag。
九、驱动卸载常见问题分析
1. 残留符号链接/设备对象
- 卸载没删除符号链接,导致下次加载失败或用户层句柄异常。
- 卸载没删除设备对象,系统残留垃圾对象,影响稳定性。
2. 内存泄漏
- 动态分配内存未释放,驱动多次加载/卸载后系统内存耗尽。
- 用WinDbg的
!poolused、!pooltag命令定位泄漏。
3. 悬挂IRP/异步资源未回收
- IRP异步处理未完成,导致驱动卸载后系统异常或蓝屏。
- 解决方法:DriverUnload中主动取消所有挂起IRP。
4. 清理顺序错误
- 先删除设备对象再释放扩展区资源,访问已删除对象导致崩溃。
- 正确顺序:先释放扩展区资源,再调用
IoDeleteDevice。
十、驱动卸载调试与验证技巧
1. 日志输出
- DriverUnload中用
DbgPrint或WPP记录清理进度和资源回收情况。 - 便于WinDbg/DebugView实时监控。
2. 自动化测试
- 用脚本反复加载/卸载驱动,监控系统资源变化。
- 检查符号链接、设备对象是否被彻底清理。
3. WinDbg资源泄漏分析
!poolused查看分配池使用情况。!object \Device查看残留设备对象。!symbollink查看残留符号链接。
十一、特殊场景与高级清理
1. 设备移除(Plug and Play驱动)
- PnP驱动设备移除时要清理设备扩展、硬件资源、挂起IRP等。
- 用
IRP_MN_REMOVE_DEVICE分发函数处理资源释放。
2. 驱动热重载/升级
- 某些驱动支持热重载,需确保旧驱动所有资源彻底释放,防止新驱动加载失败。
3. 多线程/同步对象清理
- 卸载时确保所有工作线程、定时器、安全对象都已终止和释放。
- 可用事件、信号量通知线程退出。
十二、代码清理模板(综合版)
VOID MyDriverUnload(PDRIVER_OBJECT DriverObject)
{
// 1. 删除符号链接
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, L"\\DosDevices\\MyDeviceLink");
IoDeleteSymbolicLink(&symLink);
// 2. 取消所有挂起IRP(如有)
CancelAllPendingIrps();
// 3. 循环清理设备对象和扩展区资源
PDEVICE_OBJECT devObj = DriverObject->DeviceObject;
while (devObj) {
MY_DEVICE_EXTENSION* ext = (MY_DEVICE_EXTENSION*)devObj->DeviceExtension;
if (ext->pBuffer) ExFreePoolWithTag(ext->pBuffer, 'tag1');
if (ext->pMyClassObj) delete ext->pMyClassObj;
// 清理Lookaside List、MDL等
PDEVICE_OBJECT next = devObj->NextDevice;
IoDeleteDevice(devObj);
devObj = next;
}
// 4. 清理全局资源
ExDeleteNPagedLookasideList(&myLookasideList);
if (gRingBuffer) ExFreePoolWithTag(gRingBuffer, 'ring');
// 5. 日志
DbgPrint("MyDriver: Unloaded and all resources cleaned up.\n");
}
十三、最佳实践总结
- 所有资源都要有分配与释放对称点,防止泄漏。
- DriverUnload中务必清理所有符号链接、设备对象、内存、同步对象和异步资源。
- 多设备对象要循环清理,不能遗漏。
- 异步IRP/线程/定时器等需提前终止和回收。
- 清理顺序:符号链接→异步资源→设备对象及扩展区→全局资源→日志。
- 多用日志和WinDbg工具验证清理效果。
十四、总结
- 驱动卸载时必须清理所有设备对象、符号链接和动态资源。
- 推荐统一用 DriverUnload 例程,清理顺序要合理。
- 动态分配的内存、同步对象、扩展区对象都要彻底释放。
- 多设备对象要循环清理,避免遗漏。
- 卸载日志有助于调试和验证清理效果。
2411





