3.1.1设备绑定API
根据设备的名字进行绑定:
NTSTATUS IoAttachDevice
{
IN PDEVICE_OBJECT SourceDevice, //用来过滤的虚拟设备
IN PUNICODE_STRING TargetDevice,//要被绑定的设备的名字
OUT PDEVICE_OBJECT *AttachedDevice //
返回绑定设备栈上最顶端的那个设备};
如果一个设备被其它设备绑定,他们在一起的一组设备,被称为设备栈.IoAttachDevice总是会绑定设备栈上最顶层的那个设备.
IoAttachDevice无法绑定没有名字的设备.
根据设备对象的指针进行绑定:
NTSTATUS IoAttachDeviceToDeviceStackSafe
{
IN PDEVICE_OBJECT SorceDevice,
IN PDEVCIE_OBJECT TargetDevice,
IN OUT PDEVICE_OBJECT *AttachedTlDeviceObject //返回绑定设备栈上最顶端的那个设备
}
3.1.3生成过滤设备并绑定
函数IoCreateDevice被用于生成设备:
NTSTATUS IoCreateDevice
{
IN PDRIVER_OBJECT DriverObject, //本驱动的驱动对象
IN ULONG DeviceExtensionSize, //设备扩展,如果没有就填0
IN PUNICODE_STRING DeviceName OPTIONAL, //生成的设备名字,虚拟设备直接填NULL
IN DEVICE_TYPE DeviceType, //设备类型,保持和被绑定的设备类型一致
IN ULONG DeviceCharacteristics, //设备特征,直接填0
IN BOOLEAN Exclusive, //是否排斥,选择FALSE
OUT PDEVICE_OBJECT *DeviceObject
}
整合的生成并绑定设备函数:
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;
}
3.1.4从设备名字获得设备对象
使用函数IoGetDeviceObjectPointer可以根据设备名字获得设备对象的指针:
NTSTATUS IoGetDeviceObjectPointer
(
IN PUNICODE_STING ObjectName, //设备名字
IN ACCESS_OBJECT *FileObject, //期望访问权限,直接填FILE_ALL_ACCESS即可
OUT PFILE_OBJECT *FileObject, //得到一个文件对象,必须在使用后把这个文件对象解除引用
OUT PDEVICE_OBJECT *DeviceObject //要得到的设备对象
);
打开一个串口设备的整合函数:
//RtlStringCcpPrintfW必须使用头文件ntstrsafe.h
#include<ntstrsafe.h>
PDEVICE_OBJECT ccpOpenCom(ULONG id ,NTSTATUS *status)
{
//外面输入的是串口的id,这里会改写成字符串的形式
UNICODE_STRING name_str;
static WCHAR name[32] = {0};
PFILE_OBJECT fileobj = NULL;
PDEVICE_OBJECT devobj = NULL;
//根据转换成串口的名字
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;
}
3.1.5绑定所有串口
下面函数实现绑定本机所有串口的功能.这个函数用到了前面的ccpOpenCom和ccpAttachDevice这两个函数.
为了后面的过滤,这里必须把过滤设备和被绑定的设备(真实设备)的设备对象指针用两个数组保存起来.
//计算机上最多只有32个口,这是笔者的假定
#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++)
{
//获得object引用
com_ob = ccpOpenCom(i,&status);
if(com_ob == NULL)
continue;
//在这里绑定,并不管绑定是否成功,s_fltobj中不为NULL的成员表示已经绑定了
ccpAttachDevice(driver,com_ob,&s_fltobj[i],&s_nextobj[i]);
}
}
3.2.1请求的区分
串口设备接收到的请求都是IRP,串口的读请求指接收数据,写请求指发出数据.
下面的方法从一个IRP指针得到主功能号
//这里的irpsp称为IRP的栈空间,IoGetCurrentIrpStackLocation获得当前栈空间
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
if(irpsp->MajorFunction == IRP_MJ_WRITE)
{
//如果是写...
}
else if(irpsp->MajorFunction == IRP_MJ_READ)
{
//如果是读...
}
3.2.2请求的结局
首先调用IoSkipCurrentIrpStackLocation跳过当前栈空间,然后调用IoCallDriver把这个设备发送到真实设备.因为真实设备已经被过滤设备绑定.所以最先接收到IRP的是过滤设备的对象.
//跳过当前栈空间
IoSkipCurrentIrpStackLocation(irp);
//将请求发送到对应的真实设备
status = IoCallDriver(s_nextobj[i],irp);
3.2.3写请求的数据
一共有三个地方可以描述缓冲区:
irp->UserBuffer;
追求效率的解决方案.应用层的缓冲区地址直接放在UserBuffer,在内核空间中访问.要求发送请求进程和当前进程一致.
irp->AssociatedIrp.SystemBuffer;
一般用于比较简单不追求效率情况,比如把应用层中内存空间中的缓冲数据拷贝到内核空间.
irp->MDLAddress;
发送请求进程和前进程可以不一致,同时比SystemBuffer更轻量.
不同的Io类别,IRP的缓冲区不同.
下面的方法,无论串口写请求采用哪种缓冲方式,都可以把数据读出来:
PBYTE buffer = NULL;
if(irp->MdlAddress != NULL)
bufer = (PBYTE)MmGetSystemAddressForMdlSafe(irp->MdlAddress);
else
buffer = (PBYTE)irp->UserBuffer;
if(buffer == NULL)
buffer = (PBYTE)irp->AssociatedIrp.SystemBuffer;
获得缓冲区长度:
ULONG length = irpsp->Parameters.Write.Lengthg;
3.3.1完整的分发函数
这个函数处理所有的串口写请求,把有从串口输出的数据都用DbgPrint打印出来.
NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
{
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status;
ULONG i,j;
//首先要知道发送给了哪个设备
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;i<len;++j)
DbgPrint("comcap: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;
}
3.3.2如何动态卸载
这里涉及到3个内核API
IoDetachDevice,负责将绑定的设备解除绑定;
IoDeleteDevice,负责IoCreateDevice生成的设备删除掉以回收内存
KeDelayExecutionThread,负责延时,等待5秒,保证设备已将所有请求完成后再删除设备
#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]);
}
}
3.3.3驱动入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{
size_t i;
//所有的分发函数都设置成一样的
for(i = 0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
{
driver->MajorFunction[i] = ccpDispatch;
}
driver->DriverUnload = ccpUnload;
//绑定所有串口
ccpAttachAllComs(driver);
return STATUS_SUCCESS;
}