📄 网络驱动教程.txt
字号:
楚狂人的 DriverNetworks开发网络驱动教材(0-7课)
简要说明:
这是一本您可以免费得到和自由传播的Ndis网络驱动开发的教材。以循序渐进的方式,通过大量的简单的示例代码介绍如何使用DriverrNetworks开发网络驱动。这本书由楚狂人搜集撰写。可以自由传阅修改。仅仅用于交流与学习。一部分来源于DriverNetworks帮助的翻译。一部分是楚狂人本人本人的工作经验介绍。若将用本书的内容用于赢利,您必须准备自己应付各种版权导致的问题。
这本教材曾经在这里发表过。这里是经过整理,并且更完整的新版本。
自我介绍:
1.从事驱动开发工作,曾经完成ipsec vpn客户端,网络文件系统,防火墙内核和一些协议驱动,usb客户驱动。
2.主要擅长vxd,Ndis,WDM驱动,文件系统驱动,USB客户驱动等。
3.喜爱开发调试工具:VC,DriverStudio,DriverNetworks,Softice.
4.此方面的问题欢迎与我联系.同时接受兼职或者全职的驱动开发工作.联系方式QQ16191935.
第O课 预备知识与概述
虽然为入门教材,本教材不讲述c++语法、VC的使用、网络基础知识等知识。为此,您至少应该预备以上的三种基础知识。
NDIS网络驱动的名词解释与用户您可以很方便的在网上尤其是本论坛找到。
本书主要介绍用DriverNetworks开发Ndis网络驱动的知识。
在实际建立一个驱动之前,您应该安装开发环境。VC和DDK是必须的,理论上98DDK也可以进行WDM设备的开发。我建议学习本教材的时候您使用windows2000作为开发环境,并安装windows 2000DDK.
2000DDK可以在网络上免费下载。此外建议您安装DriverStudio2_7或者更高的版本。并建议您按VC->DDK->DriverStudio2-7的顺序来安装。如果您先安装了DriverStudio,然后再安装VC,有一些配置需要手工设置。我曾为此重新安装DriverStudio.
DriverStudio的安装过程极其简单。但Softice需要相对专门的配置。注意在安装的时候要求您的windows2000用户拥有足够的权限(您最好使用Administrator),否则可能导致的问题包括从安装失败到windows无法启动等不能一一详述。如果您不需要调试工具softice,仅仅安装开发包倒是非常的安全。
只使用DDK配置开发环境需要很恼火的工作。但是使用DriverStudio几乎避免了所有的麻烦。安装后vc中出现一个新的工具栏,选择DriverNetworks向导即可快速的生成一个工程。
在生成框架后按普通的方法编译,DriverStudio常常会弹出对话框,提示说有某些库还没有编译。此时切勿取消,点对话框上的“启动VC”按纽,出来的新工程来一个批构件全部编译。以后就不会再有此问题。否则编译的时候出现无数的连接错误。
点了向导后,输入工程名。然后选择微端口驱动、中间层驱动或者协议驱动。微端口驱动是实际网卡驱动。协议驱动的特点是只能得到包和发送包,而不能阻止其他协议得到包。中间层驱动是一种过滤驱动,在小端口驱动和协议驱动中间,可以得到所有的数据包并决定它们的命运。
现在我们选择中间层驱动,下去选择Filter而不是Mux(先挑简单的下手),我只对以太网包有兴趣,因此我选择Medium Type 802_3.
网络设置控制面板需要我们实现一个叫Notify Object的东西。但是我已经决定由我自己的程序来控制,不关控制面板的事情,所以我不选择这个。
继续往下,可以决定在注册表中保存的参数。我对此无兴趣,直接下一步,生成了工程。
工程可以直接便宜,如果出现了连接错误或者其他任何错误请严格按上边的步骤来操作。编译结束了应该生成一个sys文件。
工程目录下有两个inf文件。这两个文件必须与sys在一起才能正常安装驱动。点开控制面板网络添加服务。选择没有带MP的那个inf文件。安装。如果一切顺利,打开DriverMonitor,您能看到输出信息,您的中间层驱动已经开始工作了。
现在回到我们前面所叙述的,您应该看到一些类:其中最重要的是MyProjectAdapter,"MyProject"是您的工程名字。
下面会从基础知识开始介绍。您可以使用刚刚建立的框架来测试下边的简单代码。TRACE()宏在DriverNetworks环境下可以非常方便的输出信息。请使用DriverMoniter或者DbgView来观看。
第一课 管理NDIS Packets
NDIS Packet(包描述符)是最基本的NDIS数据类型(NDIS_PACKET结构),被多种网络驱动用于描述临近的两个网络接口之间传输的数据。NDIS_PACKET是比NDIS_BUFFER更高层的抽象。NDIS_BUFFER描述NDIS_PACKET所使用的内存空间。在Windows NT中,就是是MDL(内存描述符号链)。NDIS_PACKET描述了在层之间收到或者发出的数据包的内容。这些内容保存在一个NDIS_PACKET所拥有的NDIS_BUFFER链中。
DriverNetworks通过KNdisPackets类来使用NDIS packets。KNdisPacket是PNDIS_PACKET的c++外包类,而且有与PNDIS_PACKET同样的运行效率。对于类型转换的支持使KNdisPacket可以被直接用于所有以PNDIS_PACKET为参数的函数中。
NDIS Packets总是从NDIS packet pool(包描述符号池,下面简称包池)中分配的。在DriverNetworks中,packet pool由KNdisPacketPool类描述。如果你的驱动管理自己的包池,它总是包括一个KNdisPacketPool对象作为adapter类(这个类以后再说)的数据成员。并在Adapter类的Initialize中初始化它,然后可以在其他地方分配或者释放你的包描述符。
当一个Ndis pakcet通过NDIS在一个驱动到另一个驱动之间传递的时候,该描述符的所有权可以临时的转移到后一个驱动。为了区分这些Ndis pakcet的所在环境,NDIS_PACKET结构提供一些特别的区域,名保留域,来在这些包描述符中保存上下文信息。DriverNetworks提供KNdisPacketWithContext与KNdisPacketListWithContext来管理这些区域。
为解决NDIS中间层驱动执行包管理计划的困难,DriverNetworks中间层驱动往往使用了包描述符中的一些保留域。为了在框架代码和用户代码之间正确的共享这些区域,DriverNetworks提供了KNdisFilterPacketPool类,这个类提供了一个安全的机制来在中间层驱动使用自己的包池。
下面是分配包池的例子。
//MyAdapter类也就是我的驱动的主要部分,由DriverNetWorks的向导生成,注意成员函数实现写在类声明里了,别被这个给迷惑
class MyAdapter : public KNdisMiniAdapter {
...
KNdisPacketPool m_Pool;
public:
NDIS_STATUS Initialize(KNdisMedium& Medium, IN KNdisConfig& Config) {
...
m_Pool.Initialize(8); //初始化包池(在其中初始化8个包描述符)
ASSERT(m_Pool.IsValid());
...
}
void SomeMethod() { //这里在某个成员函数中分配一个包描述符
KNdisPacket pkt = m_Pool.Allocate();
if(pkt.IsValid())
{
... //如果想使用就使用
}
void AnotherMethod(KNdisPacket& pkt) { //在这里将包描述符号释放还给包池
m_Pool.Free(pkt);
}
}
使用非常简单,应该注意的是包描述符只是一个描述符,并不包含真实的数据包数据。只是使你可以找到并管理数据包。
DriverNetworks有另一个类KNdisPacketList,可以用于管理Ndis Packet链表。KNdisPacketList是NDIS_PACKET的双向链表。注意KNdisPacketList并不是线程安全的,这对与标准的NDIS4微端口驱动来说足够了。不连续的NDIS5微端口驱动可能必须使用KNdisInterLockedPacketList代替之,这个类使用一个自旋锁保证几个线程对链表的操作不会互相干扰。
KNdisPacketList类一般用于执行先进先出式的包处理过程。下面是例子。
class MyAdapter : public KNdisMiniAdapter {
...
KNdisPacketList m_Queue;
public:
void Process(PNDIS_PACKET pkt) {
if(/*如果想立刻处理*/) {
//处理过程
}
else {//如果不想处理,暂时加入队列中
m_Queue.InsertTail(pkt);
}
}
void ProcessLater() { //在这里处理
KNdisPacket pkt = m_Pool.Remove();
if(pkt.IsValid()) {
//处理
}
else {} //说明队列是空的?
}
}
NDIS_PACKET提供MiniportReserved[]和ProtocolReserved[]这样特殊的保留区域,主要用于保存可能这些包的不同的上下文(或者说执行环境)信息。举个例子,一个协议驱动收到应用程序请求并生成了一个包,协议可能要储存有一个IRP的指针。NDIS要求微端口使用MiniportReserved[]而协议驱动使用ProtocolReserved[]。中间层驱动则更要严格的注意,当前是Miniport呢还是Protocol在使用哪一个域。
DriverNetworks提供了类模板KNdisPacketWithContext,这使你可以随意处理保留区域而不必担心类型。KNdisPacketWithContext当然来自KNdisPacket,而驱动开发者必须可以自己定义保留区域中的数据结构,然后使用GetContext()方法来返回一个指针访问保留区域。
KNdisPacketWithContext一般用于KNdisPacketListWithContext中。后者是KPacketList的容器,并且提供了根据用户定义数据结构很方便的访问保留区域的方法。
KNdisPacketWithContext和KNdisPacketListWithContext这两个模板都通过两个参数生成类,一个上下文类型,也就是用户定义的保留区域数据结构。另一个是一个bool变量,表示描述符号是用于微端口的还是协议的。
下面是例子
class MyPacketDevice : public KDevice
{
//上下文 (保存在包描述符的保留区域中)
struct PacketContext {
PIRP Irp;
PMDL pMdl;
};
typedef KNdisPacketListWithContext<PacketContext> PacketList;
protoected:
KNdisPacketPool m_PacketPool;
PacketList m_List;
}
//现在看如何使用了
void Submit(KIrp I) {
KNdisPacketWithContext packet = m_PacketPool.Allocate();
packet->GetContext()->Irp = I; //看见了吧,直接用用户定义的类型访问保留区域
m_List.InsertTail(packet);
...
}
void SubmitDone() {
KNdisPacketWithContext packet = m_RcvList.RemoveHead();
KIrp I = packet.GetContext()->Irp;
// ...
}
同时,类KNdisFilterPacketPool提供了安全的方法来在中间层驱动中使用私有的包池。值得注意,强烈推荐使用KNdisFilterPacketPool代替KNdisPool,如果你要在中间层驱动中使用私有的包池的话。
使用步骤如下:
1.定义你的保留区域数据类型T
struct MyContext {PVOID data;}
2.定义你的包池
typedef kNdisFilterPacketPool<MyContext,true> CTxPool;
typedef KNdisFilterPacketPool<MyContext,false> CRxPool;
3.分配包并使用保留区域
KNdisPacket p = m_CTxPool.Allocate();
CTxPool::GetContext(p)->data = ...
第二课 管理Ndis Buffers,访问注册表
NDIS_BUFFER是另一个基本的数据结构,几乎被所有的网络驱动用于描述在系统内存中分配的内存快。在Windows NT中,NDIS_BUFFER就在NT内核中常用的MDL(内存描述符链)。
DriverNetworks将NDIS_BUFFER包装成KNdisBuffer类。这个类可以直接用于任何以PNDIS_BUFFER为参数的函数中。
NDIS_BUFFER总是从一个NDIS buffer pool(缓冲描述符池,下面简称缓冲池,别和真的缓冲池混淆)中分配的。在DriverNetWorks中,缓冲池相关的类是KNdisBufferPool,如果你的驱动使用自己的缓冲池,一般得在你的Adapter类中包含一个KNdisBufferPool成员,并且在adapter的Initialize中初始化。
下面是使用 KNdisBuffer类的例子。
class MyAdapter : public KNdisMiniAdapter {
...
KNdisBufferPool m_Pool;
public:
NDIS_STATUS Initialize((KNdisMedium &Medium, IN KNdisconfig& Config)) {
m_Pool.Initialize(8); //初始化8个缓冲描述符的缓冲池
ASSERT(m_Pool.IsValid());
...
}
void SomeMethod(PVOID Data, UINT DataSize) {
KNdisBuffer buf = m_Pool.Allocate(Data,DataSize);
if(buf.IsValid()){/*在这里使用buf*/}
else{
//很糟糕,说明缓冲描述符用完了!
}
}
Ndis buffer就介绍到这里,这里只提一下在什么地方用,具体怎么操作,以后再说。
Ndis驱动通过类KNdisConfig类访问注册表。DriverNetworks架构总是在驱动初始化的时候生成一个KNdisConfig对象,并完adapter类的Initialize()成员函数中传入一个引用。包括微端口、中间层驱动和协议驱动都是这种模式的。
每个NDIS驱动在注册表上都有一个子树,记载了设置参数。
对于NDIS微端口驱动,他的参数保存在
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlClass{4D36E972-E325-11CE-BFC1-08002BE10318}xxxxNdiparams
(那串数字找起来很麻烦,但是我记记住了72两个数字,你会发现很有用!)
对于协议驱动,他的参数在:
HKEY_LOCAL_MACHINESYSTEM|CurrentControlSetServices<protocol>Parameters
这个子树会被安装脚本(你写的驱动会需要一个inf文件才能安装,就这个东西)和系统填写。
类KNdisConfig类提供了公有成员函数Read()和Write来从注册表读写一些数值,包括32位整数和Unicode字符串。
下面的例子读了一个32位的整数:
ULONG uCardMode;
Config.Read(KNDIS_STRING_CONST("CardMode"),&uCardMode);
这里的KNDIS_STRING_CONST宏是一个生成Unicode字符串的快速方式。
下面访问字符串
NDIS_STRING strCardName;
NDIS_STATUS err = Config.Read(KNDIS_STRING_CONST("CardName"),&strCardName);
if(err) {
//说明“CardName”没找到!
}
else {
//strCardName.Buffer是一个指向空字符为结束的Unicode字符缓冲区的指针。
}
前面的例子中Read()需要一个指向NDIS_STRING的指针作为第二个参数,但是参数转换使你可以直接使用一个类KNdisString的对象代替之。返回的字符串由NDIS管理,绝不能被调用者修改或者释放掉。当Config对象被释放的时候,这个字符串的空间会被挥手。一般这发生在MyAdapter::Initialize返回之后。
KNdisConfig还允许你查询一些随系统不同而变化的参数,比如:
ReadNetworkAddrss()——能读一个注册表中预定义的键值NetworkAddress,
IsNT()——能检查现在是运行在WindowsNT下还是Win9X下。
NdisVersion()——能检查NDIS的版本。
最后,KNdisConfig类允许你执行快速IO,有些微端口驱动利用这个功能。这个以后再说。
第三课 访问IO端口
中间层驱动和协议驱动可能对IO端口不感兴趣,但用DriverNetworks开发NDIS微端口驱动时访问io端口或者内存映射io端口,基本上有三个步骤:
1.向注册io端口或者内存地址范围。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -