设备绑定的内核API之一
驱动 --> 生成多个 --> 设备对象 --> 对应 --> 真实的一个设备
windows --> 发送消息 --> 设备对象
消息 --> 驱动会拦截 --> 转发给 --> 设备对象
驱动程序和驱动对象是一对一的关系,即一个.sys文件对应一个DRIVER_OBJECT
驱动对象和设备对象是一对多的关系,即一个DRIVER_OBJECT中会有多个DEVICE_OBJECT
驱动对象中保存第一个设备对象的指针,即DRIVER_OBJECT中的PDEVICE_OBJECT DeviceObject;指向第一个设备对象
设备对象中保存下一个设备对象的指针,形成设备链,即DEVICE_OBJECT中的struct _DEVICE_OBJECT *NextDevice;构成一个设备链
设备对象中保存驱动对象的指针,即DEVICE_OBJECT中的struct _DRIVER_OBJECT *DriverObject;指向驱动对象
一对多的数据关系,是多保存一的关键字段,即每个DEVICE_OBJECT中都有一个*DriverObject会找到自己所属的驱动对象
NTSTATUS
IoAttachDevice(
_In_ _When_(return==0, __drv_aliasesMem)
PDEVICE_OBJECT SourceDevice,//调用者生成的 用来过滤的 虚拟设备
_In_ PUNICODE_STRING TargetDevice,//要绑定的设备,是设备的名字,所以没有名字的设备不能用这个API
_Out_ PDEVICE_OBJECT *AttachedDevice//返回的指针 的指针,绑定成功后,被绑定的设备指针返回到这个地址
);
如果设备被多个其他设备绑定,其他设备会组成设备栈,后者居顶
绑定串口很容易,因为串口的名字是固定的
但是如果省内没有名字,则不是用上面这个API,可以用下面这个
IoAttachDeviceToDeviceStack
IoAttachDeviceToDeviceStackSafe <--这个更安全,但是要Windows xp 以上版本才能使用
上面这两个函数都是根据设备对象的指针而不是名字进行绑定的,所以可以绑定没有名字的串口
NTSTATUS
IoAttachDeviceToDeviceStackSafe(
_In_ PDEVICE_OBJECT SourceDevice,//过滤设备
_In_ PDEVICE_OBJECT TargetDevice,//要绑定的设备(设备栈中的设备)是指针
_Outptr_ PDEVICE_OBJECT *AttachedToDeviceObject//返回最终被绑定的设备,实际上就是绑定之前设备栈最顶层的设备
);
上面这个函数在 ntddk.h 定义
绑定设备的最终过程:生成一个过滤设备(就是设备栈中的设备,之后它会变成随便栈的最顶层)
生成设备
NTSTATUS
IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,//本驱动的驱动对象,就是 DriverEntry传入的
_In_ ULONG DeviceExtensionSize,//设备扩展,填0
_In_opt_ PUNICODE_STRING DeviceName,//设备名字(过滤设备一般不要名字,所以填 NULL)
_In_ DEVICE_TYPE DeviceType,//设备类型(保持和被绑定的设备类型一致)
_In_ ULONG DeviceCharacteristics,//设备特征,填0
_In_ BOOLEAN Exclusive,
_Outptr_result_nullonfailure_
_At_(*DeviceObject,
__drv_allocatesMem(Mem)
_When_((((_In_function_class_(DRIVER_INITIALIZE))
||(_In_function_class_(DRIVER_DISPATCH)))),
__drv_aliasesMem))
PDEVICE_OBJECT *DeviceObject
);
//串口名
UNICODE_STRING com_name = RTL_CONSTANT_STRING(L"\\Device\\Serial0");
NTSTATUS status = IoAttachDevice();
分发函数,处理所有串口的写请求 串口--------------应用程序
NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp){
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status;
ULONG i,j;
//判断发送给哪个设备
for(i=0;i
if(s_fltobi[i] == device){
//所有的电源操作直接放过,直接发送,然后返回说已经处理
if(irpsp->MajorFunction == IRP_MJ_POWER){
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp);
return PoCallDriver(s_nextobj[i],irp);
}
//只过滤写操作,获得写请求的缓冲区和长度
if(irpsp->MajorFunction == IRP_MJ_WRITE){
//如果是写,获得长度
ULONG len = irpsp->Parameters.Write.Length;
//然后获得缓冲区
PUCHAR buf = NULL;
if(irp->MdlAddress != NULL){
buf = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
}else{
buf = (PUCHAR)irp->UserBuffer;
}
if(buf == NULL){
buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
}
//打印内容
for(j=0;j
DbgPrint("comcap:send data: %2x\r\n",buf[j]);
}
}
//这些请求直接下发执行即可,不处理
IoSkipCurrentIrpStackLocation(irp);
return IpCallDriver(s_nextobj[i],irp);
}
}
//如果请求不在所绑定的设备中.则有问题,直接返回参数错误
irp->IpStatus.Information = 0;
irp->IpStatus.Status = STATUS_INVALID_PARAMETER;
IpCompleteRequest(irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
动态卸载
IoDetachDevice 解除设备绑定
IoDeleteDevice 删除生成的设备
KeDelayExecutionThread 延时
卸载过滤参数有一个问题,我们要终止这个过滤程序,但是一些IRP可能还在这个过滤程序的处理过程中
要取消这些请求很麻烦,而且不一定成功,所以这里的解决方案是等待5秒在卸载
代码:
#include
#include
//串口过滤驱动
//绑定过滤设备
NTSTATUS ccpAttachDevice(PDRIVER_OBJECT driver, PDEVICE_OBJECT oldobj, PDEVICE_OBJECT *fltobj, PDEVICE_OBJECT *next){
NTSTATUS status;
PDEVICE_OBJECT topdev = NULL;
//生成设备并绑定
status = IoCreateDevice(driver, 0, NULL, oldobj->DeviceType, 0, FALSE, fltobj);
if (status != STATUS_SUCCESS){ 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; }
(*fltobj)->Flags |= DO_POWER_PAGABLE;
//讲一个设备绑定到另一个设备上
topdev = IoAttachDeviceToDeviceStack(*fltobj, oldobj);
if (topdev == NULL){
IoDeleteDevice(*fltobj);
*fltobj = NULL;
status = STATUS_UNSUCCESSFUL;
return status;
}
*next = topdev;
//设置这个设备已经启动
(*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
//打开串口
PDEVICE_OBJECT ccpOpenCom(ULONG id, NTSTATUS *status){
UNICODE_STRING name_str;
static WCHAR name[32] = { 0 };
PFILE_OBJECT fileobj = NULL;
PDEVICE_OBJECT devobj = NULL;
//根据ID转换成串口的名字
memset(name, 0, sizeof(WCHAR)* 32);
RtlStringCchPrintfW(name, 32, L"\\Device\\Serial%d", id);
RtlInitUnicodeString(&name_str, name);
//打开设备对象
*status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);
//如果打开成功,把文件对象引用解除
if (*status == STATUS_SUCCESS){ ObDereferenceObject(fileobj); }
return devobj;
}
//绑定所有的串口
#define CCP_MAX_COM_ID 32
static PDEVICE_OBJECT s_fltobj[CCP_MAX_COM_ID] = { 0 };
static PDEVICE_OBJECT s_nextobj[CCP_MAX_COM_ID] = { 0 };
void ccpAttachAllComs(PDRIVER_OBJECT driver){
ULONG i;
PDEVICE_OBJECT com_ob;
NTSTATUS status;
for (i = 0; i < CCP_MAX_COM_ID; i++){
com_ob = ccpOpenCom(i, &status);
if (com_ob == NULL){ continue; }
ccpAttachDevice(driver, com_ob, &s_fltobj[i], &s_nextobj[i]);
}
}
//分发函数
NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp){
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status;
ULONG i, j;
//发送给哪些设备(设备最多有 CCP_MAX_COM_ID 个)
for (i = 0; i < CCP_MAX_COM_ID; i++){
if (s_fltobj[i] == device){
//如果是电源操作,放过.直接发送,然后返回
if (irpsp->MajorFunction == IRP_MJ_POWER){
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp);
return PoCallDriver(s_nextobj[i], irp);
}
//处理写请求操作.获得缓冲区及其长度,然后打印
if (irpsp->MajorFunction == IRP_MJ_WRITE){
ULONG len = irpsp->Parameters.Write.Length;
PUCHAR buf = NULL;
if (irp->MdlAddress != NULL){
buf = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
}else{
buf = (PUCHAR)irp->UserBuffer;
}
if (buf == NULL){ buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer; }
//打印
for (j = 0; j < len; ++j){ DbgPrint("misaka: send data %2x\r\n",buf[j]); }
}
//其他请求不处理,直接下发
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i], irp);
}
}
//如果不在绑定的设备中,则可能有问题,直接返回参数错误
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//动态卸载
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
void ccpUnload(PDRIVER_OBJECT drv){
ULONG i;
LARGE_INTEGER interval;
//解除绑定
for (i = 0; i < CCP_MAX_COM_ID; i++){
if (s_nextobj[i] != NULL){ IoDetachDevice(s_nextobj[i]); }
}
//睡眠5秒,等待所有的 IRP 处理结束
interval.QuadPart = (5 * 1000 * DELAY_ONE_MILLISECOND);
KeDelayExecutionThread(KernelMode, FALSE, &interval);
//删除设备
for (i = 0; i < CCP_MAX_COM_ID; i++){
if (s_fltobj[i] != NULL){ IoDeleteDevice(s_fltobj[i]); }
}
DbgPrint("misaka: bye ,driver unload successfully.");
}
//驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path){
DbgPrint("misaka: hello ,wecome to use misaka driver service .");
size_t i;
//所有的分发函数都设置为一样的
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++){ driver->MajorFunction[i] = ccpDispatch; }
//动态卸载
driver->DriverUnload = ccpUnload;
//绑定所有串口
ccpAttachAllComs(driver);
return STATUS_SUCCESS;
}
说明:因为wein7系统没有找到教程的超级终端,随意没办法测试,不过驱动打开是正常的.关闭也是正常的.串口控制大概也应该正常!
misaka: hello ,wecome to use misaka driver service .
misaka: bye ,driver unload successfully.