驱动程序的初始化主要由函数DriverEntry完成,卸载主要由函数DriverUnload完成。下面主要分析驱动程序的初始化与清除过程,以及相关的基础知识。
图5-1 函数调用关系图
1.1 结构体_NDIS_PROTOCOL_CHARACTERISTICS
结构体_NDIS_PROTOCOL_CHARACTERISTICS的定义如下:
typedef struct _NDIS_PROTOCOL_CHARACTERISTICS {
UCHAR MajorNdisVersion;
UCHAR MinorNdisVersion;
UINT Reserved;
OPEN_ADAPTER_COMPLETE_HANDLER OpenAdapterCompleteHandler;
CLOSE_ADAPTER_COMPLETE_HANDLER CloseAdapterCompleteHandler;
SEND_COMPLETE_HANDLER SendCompleteHandler;
TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler;
RESET_COMPLETE_HANDLER ResetCompleteHandler;
REQUEST_COMPLETE_HANDLER RequestCompleteHandler;
RECEIVE_HANDLER ReceiveHandler;
RECEIVE_COMPLETE_HANDLER ReceiveCompleteHandler;
STATUS_HANDLER StatusHandler;
STATUS_COMPLETE_HANDLER StatusCompleteHandler;
NDIS_STRING Name;
//
//使用下列任何成员,需要把MajorNdisVersion设置为0x04或0x05
//
RECEIVE_PACKET_HANDLER ReceivePacketHandler;
BIND_HANDLER BindAdapterHandler;
UNBIND_HANDLER UnbindAdapterHandler;
PNP_EVENT_HANDLER PnPEventHandler;
UNLOAD_PROTOCOL_HANDLER UnloadHandler;
//
//使用下列任何成员,需要把MajorNdisVersion设置为0x05
//面向连接的协议驱动程序使用,不分析。
//
CO_SEND_COMPLETE_HANDLER CoSendCompleteHandler;
CO_STATUS_HANDLER CoStatusHandler;
CO_RECEIVE_PACKET_HANDLER CoReceivePacketHandler;
CO_AF_REGISTER_NOTIFY_HANDLER CoAfRegisterNotifyHandler;
} NDIS_PROTOCOL_CHARACTERISTICS, *PNDIS_PROTOCOL_CHARACTERISTICS;
下面分别说明结构体_NDIS_PROTOCOL_CHARACTERISTICS各成员的作用,其中,只在面向连接的协议驱动程序中使用的成员不作分析说明。
MajorNdisVersion
描述驱动程序所使用NDIS库的主要版本。当前值为0x05(WinPcap当前使用的为版本5,NDIS目前已到版本6),虽然NDIS库支持早期采用NDIS V4.0开发的驱动程序,但不再对V3.0版本的协议驱动程序进行支持。
MinorNdisVersion
描述NDIS的次版本,当前为0x00。
Reserved
该成员保留给系统使用。
OpenAdapterCompleteHandler
这是一个必须提供的函数。如果协议驱动程序对NdisOpenAdapter的调用返回NDIS_STATUS_PENDING,则接着调用ProtocolOpenAdapterComplete来完成绑定操作。
CloseAdapterCompleteHandler
这是一个必须提供的函数。如果协议驱动程序对NdisCloseAdapter的调用返回NDIS_STATUS_PENDING,则接着调用ProtocolCloseAdapterComplete来完成解除绑定操作。
SendCompleteHandler
这是一个必须提供的函数。如果协议驱动程序对
NdisSendPackets或
NdisSend的调用返回NDIS_STATUS_PENDING,则接着调用
SendCompleteHandler来完成发送处理。
TransferDataCompleteHandler
如果协议驱动程序可以把它自己绑定到一个低层非面向连接的NIC驱动程序(以NdisMIndicateReceivePacket指定并不是接收所有的数据包)上,这就是一个必须提供的函数。当协议驱动程序发起传输数据请求,对NdisTransferData的调用返回NDIS_STATUS_PENDING时,ProtocolTransferDataComplete函数被调用。专门面向连接的协议驱动程序不需要
ProtocolTransferDataComplete 函数。
ResetCompleteHandler
这是一个必须提供的函数。当协议驱动程序调用NdisReset函数执行复位操作,返回值为NDIS_STATUS_PENDING,则接着调用ProtocolResetComplete完成复位操作。
RequestCompleteHandler
这是一个必须提供的函数。当协议驱动程序调用NdisRequest函数查询和设置操作,返回值为NDIS_STATUS_PENDING,则调用ProtocolRequestComplete完成操作。
ReceiveHandler
对绑定到一个非面向连接的NIC驱动程序上的NDIS协议驱动程序,这是一个必须提供的函数。ProtocolReceive决定协议驱动程序的使用者对一个被接收的网络数据包是否感兴趣,如果感兴趣,就复制所需要的数据,可能地,调用NdisTransferData重新获取剩余的所需数据包。
ReceiveCompleteHandler
这是一个必须提供的函数。驱动程序完成前面所述的一个或多个从一个NIC驱动程序接收所需数据的操作后,ProtocolReceiveComplete完成诸如通知协议驱动程序使用者之类的后处理。
StatusHandler
这是一个必须提供的函数。ProtocolStatus函数用来处理低层NIC驱动程序所指示状态的改变。
StatusCompleteHandler
这是一个必须提供的函数。ProtocolStatusComplete完成一个状态改变的操作,该操作由底层驱动程序调用NdisMIndicateStatus发起。
Name
一个NDIS_STRING类型,包含调用者初始化的一个字符串,用来命名该驱动程序,采用系统默认的字符集。
对Windows 2000与后面的驱动,该字符串为Unicode字符。也就是说,对Windows 2000与以后的版本,NDIS定义NDIS_STRING类型为一个UNICODE_STRING类型。当协议被安装时,该字符串必须与注册表所指定的(在Services条目之下)匹配。
NdisRegisterProtocol把所提供的字符串转换为大写字符,因此一个协议驱动程序的编写者不能假设通过改变一个已经注册协议名称的大小写来为驱动创建一个唯一的名称。
ReceivePacketHandler
这是一个可选函数。如果协议驱动程序绑定到支持多数据包(multipacket )的NIC驱动程序(通过调用NdisMIndicateReceivePacket指定),那么ProtocolReceivePacket函数应该被提供。
BindAdapterHandler
这是一个必须提供的函数。NDIS调用该函数请求协议驱动程序绑定到低层网卡或虚拟网卡上,网卡名作为该处理程序的参数传递。
UnbindAdapterHandler
这是一个必须提供的函数。NDIS调用ProtocolUnbindAdapter释放对低层网卡或虚拟网卡的绑定,网卡名作为参数传递。当绑定成功解除时,ProtocolUnbindAdapter函数调用NdisCloseAdapter,并释放资源。
PnPEventHandler
这是一个必须提供的函数。NDIS调用ProtocolPnPEvent来指示即插即用事件或电源管理事件。
UnloadHandler
这是一个可选函数。NDIS调用ProtocolUnload函数来响应用户卸载中间层驱动程序的请求。对于每一个绑定的适配器,在调用NDIS的 ProtocolUnbindAdapter之后,调用ProtocolUnload函数卸载驱动程序。ProtocolUnload执行驱动程序决定的清除操作。
1.2 NPF的DriverEntry函数的主要流程
和编写普通应用程序一样,驱动程序也有个入口函数,也就是首先被执行的函数。该函数通常被命名为DriverEntry,可以指定另外的名称,但最好遵循这个名称。该函数的原型如下:
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
DriverEntry主要是对驱动程序进行初始化工作,它由系统进程所调用。在Windows中有个特殊的进程叫做系统进程。打开进程管理器,可见里面有个名为System的进程就是系统进程。系统进程在系统启动的时候,就已经被创建了。
图5-2 System进程
驱动加载的时候,系统进程启动新的线程,调用执行体组件中的对象管理器,创建一个驱动对象。这个驱动对象是一个DRIVER_OBJECT的结构体。另外,系统进程调用执行体组件中的配置管理程序,查询此驱动程序在注册表中对应的项。
系统线程调用驱动程序的DriverEntry例程时,同时传进两个参数,分别是
pDrivelobject和pRegistryPath。其中,第一个是指向刚才被创建驱动对象的指针,第二个是指向设备服务键的键名称符串的指针。
为了和NDIS库建立通信,驱动程序的DriverEntry必须调用NdisRegisterProtocol作为协议驱动程序注册,稍候将作详细描述。
DriverEntry返回值是NTSTATUS的数据,NTSTATUS被定义为32位的无符号长整型。在驱动程序开发中,人们习惯用NTSTATUS返回状态。DriverEntry的返回值如果表示成功,则意味着加载驱动成功,否则意味着加载驱动失败,调用对象管理程序销毁驱动对象。如果该程序成功,它必须返回STATUS_SUCCESS,否则,它必须返回一个在ntstatus.h定义的错误状态码。下面列出STATUS_SUCCESS与STATUS_UNSUCCESSFUL的定义。
st1\:*{behavior:url(#ieooui) }
#define STATUS_SUCCESS ((NTSTATUS)0x
00000000L
)
#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC
0000001L
)
1.2.1 协议驱动程序注册
驱动程序在
DriverEntry
环境中通过调用
NdisRegisterProtocol
向
NDIS
注册
ProtocolXxx
函数。
NdisRegisterProtocol
定义如下:
VOID NdisRegisterProtocol(
OUT PNDIS_STATUS Status,
OUT PNDIS_HANDLE NdisProtocolHandler,
IN NDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
IN UINT CharacteristicsLength
参数Status是指向调用者提供的一个变量的指针,该函数可以返回下列的值:
NDIS_STATUS_SUCCESS
NDIS库把调用者注册为一个协议驱动程序
NDIS_STATUS_BAD_CHARACTERISTICS
对参数ProtocolCharacteristics 中MajorNdisVersion所描述的版本而言CharacteristicsLength太短
NDIS_STATUS_BAD_VERSION
参数ProtocolCharacteristics 中MajorNdisVersion描述的版本不可用
NDIS_STATUS_RESOURCES
资源不够,可能是内存,阻止NDIS库注册调用者
参数NdisProtocolHandle指向调用者提供的一个变量,函数将通过它返回一个描述已注册驱动的一个句柄。参数ProtocolCharacteristics指向一个NDIS_PROTOCOL_CHARACTERISTICS结构体,由调用者设置。参数CharacteristicsLength描述ProtocolCharacteristics所指结构体的大小。
该调用的返回句柄
NdisProtocolHandler
对协议驱动程序是透明的,协议驱动程序必须保存该句柄,并在将来对
NDIS
的调用中作为输入参数传递,例如,打开低层适配
器(调用
NdisOpenAdapter函数时
)。
在调用
NdisRegisterProtocol
之前,
DriverEntry
必须完成以下操作:
<!--[if !supportLists]-->
Ø <!--[endif]-->
对
NDIS_PROTOCOL_CHARACTERISTICS
结构体进行零初始化,例如调用
NdisZeroMemory
函数。这将确保可选入口点的尚未使用的成员设置为
NULL
。如果该结构没被置零,那么在调用
NdisRegisterProtocol
之前,任何不使用的成员必须置为
NULL;
<!--[if !supportLists]-->
Ø <!--[endif]-->
在
NDIS_PROTOCOL_CHARACTERISTICS
结构中指定协议兼容的
NDIS
版本
;
<!--[if !supportLists]-->
Ø <!--[endif]-->
在
NDIS_PROTOCOL_CHARACTERISTICS
结构中设置驱动程序导出的必需的和可选的
ProtocolXxx
函数的地址。
在
WinPcap
的
NPF
中的具体实现为:首先分配一个
NDIS_PROTOCOL_CHARACTERISTICS
的结构体,然后执行零初始化,接着用协议数据(版本、名称等)与驱动程序所需回调函数的地址设置该结构体。最后调用
NdisRegisterProtocol
函数把
NPF注册为一个NDIS协议驱动程序
。下面为
NPF
中对应的代码:
//声明一个NDIS_PROTOCOL_CHARACTERISTICS结构实例ProtocolChar;
NDIS_PROTOCOL_CHARACTERISTICS ProtocolChar;
//协议名称PacketDriver
NDIS_STRING ProtoName = NDIS_STRING_CONST("PacketDriver");
//对ProtocolChar的内存空间清零
RtlZeroMemory(&ProtocolChar,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
//用协议数据(版本、名称等)与回调函数地址设置ProtocolChar
#ifdef #ifdef NDIS50
ProtocolChar.MajorNdisVersion = 5;
#else
ProtocolChar.MajorNdisVersion = 3;
#endif
ProtocolChar.MinorNdisVersion = 0;
ProtocolChar.Reserved = 0;
ProtocolChar.OpenAdapterCompleteHandler = NPF_OpenAdapterComplete;
ProtocolChar.CloseAdapterCompleteHandler = NPF_CloseAdapterComplete;
ProtocolChar.SendCompleteHandler = NPF_SendComplete;
ProtocolChar.TransferDataCompleteHandler = NPF_TransferDataComplete;
ProtocolChar.ResetCompleteHandler = NPF_ResetComplete;
ProtocolChar.RequestCompleteHandler = NPF_RequestComplete;
ProtocolChar.ReceiveHandler = NPF_tap;
ProtocolChar.ReceiveCompleteHandler = NPF_ReceiveComplete;
ProtocolChar.StatusHandler = NPF_Status;
ProtocolChar.StatusCompleteHandler = NPF_StatusComplete;
#ifdef NDIS50
ProtocolChar.BindAdapterHandler = NPF_BindAdapter;
ProtocolChar.UnbindAdapterHandler = NPF_UnbindAdapter;
ProtocolChar.PnPEventHandler = NPF_PowerChange;
ProtocolChar.ReceivePacketHandler = NULL;
#endif
ProtocolChar.Name = ProtoName;
//把NPF注册为一个NDIS协议驱动程序
NdisRegisterProtocol(
&Status,
&g_NdisProtocolHandle,
&ProtocolChar,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if (Status != NDIS_STATUS_SUCCESS) {
//注册协议驱动程序到NDIF失败,程序结束
return Status;
1.2.2 设置卸载例程和IRP的派遣函数
在
DriverEntry
函数中,一般设置卸载例程和
IRP
的派遣函数。设置卸载例程和设置派遣函数都是对驱动对象的设置。设备对象中的
MajorFunction
是一个函数指针数组,
IRP_MJ_CREATE
、
IRP_MJ_ CLOSE
、
IRP_MJ_WR'lTE
等代表数组的第几个元素。下列为
NPF
的
DriverEntry
函数中的设置:
DriverObject->MajorFunction[IRP_MJ_CREATE] = NPF_Open;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = NPF_Close;
DriverObject->MajorFunction[IRP_MJ_CLEANUP]= NPF_Cleanup;
DriverObject->MajorFunction[IRP_MJ_READ] = NPF_Read;
DriverObject->MajorFunction[IRP_MJ_WRITE] = NPF_Write;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = NPF_IoControl;
DriverObject->DriverUnload = NPF_Unload;
// 卸载例程
1.2.3 获取系统中可用网络适配器的信息
函数getAdaptersList获取系统中可用的网络适配器信息,返回一个包含系统中可用网络适配器列表的字符串。如果getAdaptersList失败了,NPF试图通过调用getTcpBindings函数获得绑定了TCP/IP的网络适配器信息。函数getTcpBindings返回指向包含绑定了TCP/IP适配器的注册表键值的指针。
下面为
DriverEntry
函数中相关的代码。
bindP = getAdaptersList();
if (bindP == NULL)
{
//在注册表中没找到适配器,试图复制TCP-IP绑定的适配器
tcpBindingsP = getTcpBindings();
if (tcpBindingsP == NULL)
{
//没有找到绑定了TCP/IP的适配器,程序执行错误处理
goto RegistryError;
}
bindP = (WCHAR*)tcpBindingsP;
bindT = (WCHAR*)(tcpBindingsP->Data);
}
else {
bindT = bindP;
}
for (; *bindT != UNICODE_NULL;
bindT += (macName.Length + sizeof(UNICODE_NULL)) / sizeof(WCHAR))
{
RtlInitUnicodeString(&macName, bindT);
NPF_CreateDevice(DriverObject, &macName);
//给网络适配器创建一个设备
1.2.4 对可用的网络适配器创建一个设备
函数NPF_CreateDevice对一个给定的网络适配器创建一个设备。NPF驱动程序也调用
NPF_CreateDevice
函数,通过IoCreateDevice系统接口把Open/close,read/write与IOCTL请求的句柄地址传递给操作系统。