windows内核中的网络架构大致如下:
TDI驱动 -----把win32的网络请求转换成NDIS认识的方式
.
NDIS协议驱动 -----对应一个网络协议
.
NDIS小端口驱动-----对应一个网卡
本来协议驱动和小端口驱动结合在一起,当NDIS中间层驱动被安装到系统时,NDIS机制会断开协议驱动与小端口驱动的连结,把中间层驱动强势插入。中间层驱动对上(协议驱动)表现为一个小端口驱动,对下(小端口驱动)表现为一个协议驱动,它作为一个“中间人”,可以拦截或修改流经的网络数据。
因为要扮演中间人或者叫双面人,它必须具有协议驱动与小端口驱动两者的所有特征。从代码角度来看,就是既要有构成协议驱动的函数,也要有构成小端口驱动的函数。
PassThru是DDK中的NDIS中间层驱动示例,先看passthru.c:
开头定义了如下的全局变量:
NDIS_HANDLE ProtHandle = NULL; // 协议句柄
NDIS_HANDLE LayerHandle = NULL; // 中间层驱动句柄
//
NDIS_SPIN_LOCK GlobalLock; // 操作pListAdapter,注册控制设备(PtRegisterDevice)时使用
PADAPT pListAdapter = NULL; // 一个由ADAPT结构构成的list,ADAPT结构后述
//
LONG MiniportCount = 0; // 小端口实例计数,或者说本机网卡数
//
NDIS_HANDLE NdisWrapperHandle = NULL; // 包装句柄,注册小端口驱动时用
NDIS_HANDLE NdisDeviceHandle = NULL; // 不透明,NdisMRegisterDevice生成,NdisMDeregisterDevice使用
//
PDEVICE_OBJECT pControlDeviceObject = NULL; // 控制设备
NDIS_MEDIUM MediumArray[4] = // 代表支持的媒质类型,以太网,令牌环网等。。
{
NdisMedium802_3, // Ethernet
NdisMedium802_5, // Token-ring
NdisMediumFddi, // Fddi
NdisMediumWan // NDISWAN
};
enum _DEVICE_STATE // 由于要和用户层交互,需要生成一个控制设备,这个代表控制设备的状态,用于保障互斥操作
{
PS_DEVICE_STATE_READY = 0, // ready for create/delete
PS_DEVICE_STATE_CREATING, // create operation in progress
PS_DEVICE_STATE_DELETING // delete operation in progress
} ControlDeviceState = PS_DEVICE_STATE_READY;
下面是DriverEntry:
{
首先分配(初始化)GlobalLock,第一个中间层驱动实例初始化时就要用到,所以尽先初始化。
接着初始化包装句柄,NdisMInitializeWrapper,NDIS内部用这个结构来管理小端口驱动。
然后注册小端口驱动,其实是调用NdisIMRegisterLayeredMiniport,它内部除了注册小端口驱动,还有一些属于中间层驱动的动作。
然后注册协议驱动。
这里必须先注册小端口驱动,再注册协议驱动,因为协议驱动的绑定函数中就有初始化小端口实例的操作,如果此时小端口驱动还没注册,那应该是一个蓝屏。
如果有哪一步失败,释放所有的资源(GlobalLock,NdisWrapperHandle)。
最后调用NdisIMAssociateMiniport把我们的协议驱动和小端口驱动关联在一起,就成了一个中间层驱动。
}
PtUnload和PtDispatch从略。
passthru.c还有PtRegisterDevice和PtDeregisterDevice函数,前者负责注册控制设备,后者负责撤销控制设备。
常规驱动中,我们用IoCreateDevice来创建控制设备,而NDIS中间层驱动的设备对象的分发函数已经被NDIS征用,所以应该用NDIS推荐的方式来注册控制设备。
前文中提到全局变量中有个MiniportCount,用来计数小端口实例,或者说本机网卡数,当中间层驱动的初始化完成,第一个网卡被插入中间层驱动时,MiniportCount为1,这时候注册控制设备。当中间层驱动从所有网卡unload时,也就是MiniportCount减为0时,撤销控制设备。
为了保证注册控制设备与撤销控制设备互斥,注册控制设备与(撤销控制设备再)注册控制设备互斥,应该保证(加减MiniportCount并操作控制设备)的原子性。本来把这些操作放到一个函数中,然后前面加锁,完成解锁即可。但是NdisMRegisterDevice函数不能在DISPATCH_LEVEL运行。于是引入了前面全局变量中的ControlDeviceState,把(加减MiniportCount和设置ControlDeviceState)放到一个锁里,这样即使NdisMRegisterDevice时被打断,其他线程欲NdisMRegisterDevice时见到ControlDeviceState,也就只能等待或失败了。
这里笔者曾试想把PtRegisterDevice放到DriverEntry中,把PtDeregisterDevice放到PtUnload里,但是这样当停止中间层驱动的时候,中间某部分会停住,并不会执行到PtUnload,应该与NdisMRegisterDevice的实现机制有关。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课