Windows驱动编程之串口过滤杂谈
字数 1523 2025-08-24 23:51:13
Windows驱动编程之串口过滤技术详解
一、串口基础概念
1.1 串口定义与特点
串口(COM接口)是一种采用串行通信方式的扩展接口,具有以下特点:
- 数据按bit位传输
- 通信线路简单,只需一根传输线即可实现双向传输
- 成本低廉
- 适用于远距离通信
- 传输速度相对较慢
重要区分:USB不是串口,两者是完全不同的接口技术。
1.2 主板架构与串口位置
- 北桥芯片:处理高速信号(CPU、RAM、AGP等)
- 南桥芯片:负责I/O总线直接通信(PCI、SATA、USB、LAN、音频、键盘控制等)
串口属于南桥芯片管理的I/O设备。
二、设备过滤原理
2.1 过滤概念
设备驱动过滤是指在原有设备驱动的基础上添加一层过滤设备,特点包括:
- 不改变原有接口封装
- 在数据传输过程中添加处理层
- 类似于"层层过滤"的概念
2.2 设备栈模型
- 设备栈采用分层结构,类似"蒸包子笼"一层盖一层
- 过滤设备被添加到设备栈的最顶层
- 一个设备可以被多个设备绑定
- ReactOS系统中由PnP管理器处理设备拔插通知
三、关键API与实现步骤
3.1 创建过滤设备
使用IoCreateDevice()函数:
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_Outptr_ PDEVICE_OBJECT *DeviceObject
);
参数说明:
DriverObject:调用者的驱动程序对象指针DeviceExtensionSize:扩展设备大小(无扩展传0)DeviceName:设备名(过滤设备通常为NULL)DeviceType:必须与绑定设备类型相同DeviceObject:返回新创建的设备对象指针
示例代码:
PDEVICE_OBJECT fltobj = NULL;
status = IoCreateDevice(pDriver, 0, NULL, oldobj->DeviceType, 0, FALSE, &fltobj);
if (!NT_SUCCESS(status))
return status;
3.2 绑定设备
两种绑定方式:
方式一:有设备名称绑定(IoAttachDevice)
NTSTATUS IoAttachDevice(
_In_ PDEVICE_OBJECT SourceDevice,
_In_ PUNICODE_STRING TargetDevice,
_Out_ PDEVICE_OBJECT *AttachedDevice
);
方式二:无设备名称绑定(推荐使用IoAttachDeviceToDeviceStackSafe)
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
PDEVICE_OBJECT SourceDevice,
PDEVICE_OBJECT TargetDevice
);
3.3 获取串口驱动对象
使用IoGetDeviceObjectPointer()函数:
NTSTATUS IoGetDeviceObjectPointer(
PUNICODE_STRING ObjectName,
ACCESS_MASK DesiredAccess,
PFILE_OBJECT *FileObject,
PDEVICE_OBJECT *DeviceObject
);
示例代码:
RtlStringCchPrintfW(&name_str, sizeof name_str, L"\\Device\\Serial%d", id);
IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);
注意:该函数会使内核引用计数+2,可能导致设备被占用。
3.4 停止和卸载过滤
使用以下两个函数:
void IoDetachDevice(PDEVICE_OBJECT TargetDevice);
void IoDeleteDevice(PDEVICE_OBJECT DeviceObject);
四、串口数据获取与处理
4.1 IRP与派遣函数
IRP(I/O Request Packet)是处理I/O请求的核心数据结构:
- 用户层通过系统I/O打开文件名
- I/O管理器调用对象管理解析符号链接
- 初始化分配内存IRP并创建IO_STACK_LOCATION数组
- 通过IRP的I/O堆栈获取数据执行操作
派遣函数原型:
DRIVER_DISPATCH DriverDispatch;
NTSTATUS DriverDispatch(_DEVICE_OBJECT *DeviceObject, _IRP *Irp)
4.2 IRP处理流程
- 获取当前设备的IO栈:
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
-
判断派遣函数处理的消息类型(如IRP_MJ_CREATE或IRP_MJ_WRITE)
-
获取数据(需考虑三种可能的通信方式):
// DO_DIRECT_IO方式
if(irp->MdlAddress != NULL) {
buf = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
}
// 其他两种方式
// irp->AssociatedIrp.SystemBuffer
// irp->UserBuffer
- 处理数据(示例打印串口数据):
ULONG Writesize = irpsp->Parameters.Write.Length;
for(ULONG i = 0; i < Writesize; ++i) {
KdPrint(("%2X ", buf[i]));
if(!(Writesize % 16))
KdPrint(("\r\n"))
}
- 继续处理或完成请求:
IoSkipCurrentIrpStackLocation(irp);
// 调用IoCallDriver或PoCallDriver处理
// 或进行敏感数据修改等操作
- 收尾工作:
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_ERROR;
4.3 IRQL(中断请求级别)
- 保证进程优先级的机制
- 主要级别:Dispatch、APC与Passive
- 影响IRP处理过程(本文未详细展开)
五、完整实现示例
5.1 封装打开串口设备对象
NTSTATUS prOpenSataobj(PDEVICE_OBJECT *devobj) {
NTSTATUS status;
UNICODE_STRING name;
PFILE_OBJECT fileobj = NULL;
RtlInitUnicodeString(&name, L"\\Device\\Serial0");
status = IoGetDeviceObjectPointer(&name, FILE_ALL_ACCESS, &fileobj, devobj);
if(NT_SUCCESS(status))
ObDereferenceObject(fileobj);
return status;
}
5.2 封装过滤设备创建与绑定
PDEVICE_OBJECT prAttachDevobj(PDRIVER_OBJECT pDriver, PDEVICE_OBJECT *oldobj) {
NTSTATUS status;
PDEVICE_OBJECT fltobj;
// 1. 创建过滤设备
status = IoCreateDevice(pDriver, 0, NULL, oldobj->DeviceType, 0, FALSE, &fltobj);
if(!NT_SUCCESS(status))
return status;
// 拷贝重要标志
if(oldobj->Flags & DO_BUFFERED_IO)
fltobj->Flags |= DO_BUFFERED_IO;
if(oldobj->Flags & DO_DIRECT_IO)
fltobj->Flags |= DO_DIRECT_IO;
if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
fltobj->Characteristics |= FILE_DEVICE_SECURE_OPEN;
// 2. 绑定设备驱动
PDEVICE_OBJECT nrtobj = NULL;
status = IoAttachDeviceToDeviceStackSafe(fltobj, oldobj, &nrtobj);
if(!NT_SUCCESS(status)) {
IoDeleteDevice(fltobj);
fltobj = NULL;
status = STATUS_UNSUCCESSFUL;
return status;
}
// 设置启动状态
fltobj->Flags = fltobj->Flags & ~DO_DEVICE_INITIALIZING;
return nrtobj;
}
六、注意事项与进阶知识
-
通信协议兼容性:必须处理所有可能的通信方式(DO_DIRECT_IO、DO_BUFFERED_IO和UserBuffer)
-
引用计数管理:IoGetDeviceObjectPointer会使引用计数+2,需要特别注意
-
IRP处理安全:索引不能设置为0,否则会导致系统崩溃
-
进阶知识(本文未详细展开):
- IRP的详细调用过程
- MDL映射机制
- 内核通信机制
- IRQL级别处理机制
七、参考资料
- 《Windows驱动开发技术详解》
- 《寒江独钓-Windows内核安全编程》
- ReactOS系统源码
- Windows WDK文档