寒江独钓 第三章 串口的过滤

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;
}











































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值