XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
阅读:1552回复:4

XiangXiangRen的DriverNetworks教程1-4课(重发)

楼主#
更多 发布于:2003-10-20 09:26
Hi,我干其他的事情去了(驱动开发的工作少,已经堕落干其他任务了……)所以好久没来过了。老是收到网友的邮件说链接打不开,今天试一

下,果然如此。很抱歉,所以在这里重新发一次。

我以前就只写过一到四课,后来投入搞其他工作了,兴趣转移,所以就没写了。有兴趣的网友可以自己看DriverNetworks的帮助。只要入了门

,还是很浅显易懂的。

我的qq是16191935,邮箱tanwen@myhexin.com,有问题可以和我联系。


DriverNetworks开发Ndis网络驱动教材

使用DriverNetworks开发网络驱动非常容易,有使用VB开发Windows应用的感觉(比MFC容易)。下面这些东西是我从DriverNetworks的帮助上

翻译过来,只加了很少dongxi,非常浅显,很适合入门者。

第一课 管理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类也就是我的整个驱动,注意成员函数实现写在类声明里了,别被这个给迷惑
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_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\xxxx\\Ndi\\params
(那串数字找起来很麻烦,但是我记记住了72两个数字,你会发现很有用!)

对于协议驱动,他的参数在:
HKEY_LOCAL_MACHINE\\SYSTEM|CurrentControlSet\\Services\\<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端口

中间层驱动和协议驱动可能对IO端口不感兴趣,但是真正开发驱动访问io端口可是真正的硬工夫。用DriverNetworks开发NDIS微端口驱动时访

问io端口或者内存映射io端口,基本上有三个步

骤:

1.向注册io端口或者内存地址范围。

2.访问这些端口。

3.注销你注册的东西。

首先是注册,你必须在你的Adapter类中包括KNdisIoRange类或者KNdisMemory的数据成员.例子如下:

class MyAdapter : public KNdisMiniAdapter {
...
KNdisIoRange m_Ports;
KNdisMemoryRange m_Memory;
...
}

然后你在adapter的Initialize函数中访问一个KNdisXxxResource类,从中得到你的微端口驱动所拥有的资源。(当然包括io口)。

最后你就可以初始化你的KNdisIoRange以及KNdisMemoryRange成员了。下面有例子:

NDIS_STATUS MyAdapter::Initialize(KNdisMedium& Medium,IN KNdisConfig& Config) { //注意这行代码是向导生成的
...
//现在来设法获取资源
KNdisPnpResourceRequest request(Config);
KNdisResource<CmResourceTypePort> Port(request);

//判断获取结果是否有问题
if(!Port.IsValid())
KNDIS_RETURN_ERROR(NDIS_STATUS_NOT_ACCEPTED;

//注册io端口范围
if(!m_Port.IsValid())
KNDIS_RETURN_ERROR(NDIS_STATUS_RESOURCES);
...
}

就是这样,注册结束。下面看怎么访问。

DriverNetworks提供了四种方法来访问你的m_Ports

1.使用in()和out()成员函数, 它们使用一个ULONG型的偏移量作为参数访问io端口,这个偏移量从io范围的开始地址开始计算。看下面的例子



UCHAR reg = m_Ports.inb(4);
if(reg|1)
m_Ports.outb(4,reg|1);
else
m_Ports.outb(4,0);

2.使用KNdisIoRegister和KNdisMemoryRegister提供了一种访问这些io范围中一些特殊的寄存器的方法。KNdisIoRegister或者

KNdisMemoryRegister的对象可以在对KNdisIoRange或

者KNdisMemoryRange对象做[]操作的时候获得。而且KNdisIoRegister和KNdisMemoryRegister对象还可以当作一些基本类型使用,比如它们可

以当作ULONG,USHORT,UCHAR等等来访问。看例子



KNdisIoRegister reg = m_Ports[4];
if(UCHAR(reg)|1)
reg |= UCHAR(reg)|1;
else
reg = 0;


3.使用模板!(其实我个人对c++不是太感冒,怎么都感觉DriverNetworks有点太照顾c++人了)DriverNetworks提供两个模板

KNdisIoRegisterSafe和KNdisMemoryRegisterSafe作为刚刚2中提

到的两个类的“safe”版本。怎么说呢,非“safe”版本就是说在编译的时候不会检查读取数据的宽度和实际硬件寄存器数据宽度的不同。比

方说你试图往一个只有8位的寄存器中写一

个ULONG的数据,象这样 m_Ports.outd(4,1),这编译没问题,但是可能网卡不会象你希望的那么工作。而“safe”版本则可以指定其数据宽度

,你可以根据寄存器的实际宽度指定

成ULONG,USHORT,UCHAR这样的类型。如果尝试读写不同的数据宽度的类型,你会得到一个编译错误。例子如下:

ULONG newval = 0;
KNdisIoRegisterSafe<UCHAR> reg = m_Ports[4];
if(UCHAR(reg)|1)
reg|=UCHAR(reg)|1;
else
reg = val; //这里你将得到编译错误!

4.最后一种访问io端口的方式是所谓的直接io访问。这是基于NdisImmediateXxx系列函数的。这提供了一种方法让你的驱动可以在硬件资源已

经分配,io或者内存范围已经注册之间就访问实

际网卡硬件。这种情况下,系统会对每一次访问都要进行
硬件资源转换和映射,所以这种方法访问是很慢的。而且只能用在硬件初始化过程中。举个例子,一个驱动在开始必须读一个网卡上的EEPROM

,来获得某种信息(比如版卡类型?),然后才

能开始资源分配请求,这种情况下不能不使用这个技术。一般这种方法在即插即用环境中是不推荐的。

DriverNetworks通过KNdisConfig类来支持这种技术。(刚好上一课介绍了这个东西)。这个类有一系列的in()/out()成员函数来访问io口。例

子如下:

UCHAR reg = Config.inb(4);
if(reg | 1)
Config.outb(4,reg|1);
else
Config.outb(4.0);

最后是注册的io范围的注销,这很简单,一般在MyAdapter的Halt()函数中做。另一个选择是在Adapter的析构函数中做。只要这样:
m_Ports.Invalidate();
m_Memory.Invalidate();
就可以了。

我非常的抱歉!太匆忙了,第二个例子的某一行掉了,很关键的一行,在“//注册io端口范围”一行的下边应该还有一行“

m_Ports.Initialize();”装了ds的兄弟们可以帮我检查检查……


第四课 关于中断

这也是只用于微端口的。涉及以下几个主题。

1.注册一个中断。

(1).首先,应该在你的工程的Characteristics.h文件中(关于这个文件,我想你在使用向导生成了一个微端口驱动的框架之后,自然就会看见

。)声明你对中断相关的回调函数。这里你要使

用一个宏KNDIS_MINPORT_HANDLER.例子如下:

KNDIS_MINIPORT_HANDLER(MyAdapter,DisableInterrupt)
KNDIS_MINIPORT_HANDLER(MyAdapter,EnableInterrupt)
KNDIS_MINIPORT_HANDLER(MyAdapter,HandleInterrupt)
KNDIS_MINIPORT_HANDLER(MyAdapter,Isr)

(2).在你的adapter类中包括一个KNdisInterrupt的数据成员。

class MyAdapter:public KNdisMiniAdapter(
...
KNdisInterrupt m_Interrupt;
...
};

(3).你必须在adapter的Initiazlize()函数中访问一个KNdisXxxResource类来获得系统分配的中断资源。

(4).根据3中得到的信息初始化你的KNdisInterrupt成员。这个过程举例如下:

NDIS_STATUS MyAdapter::Initialize(KNdisMedium& Medium, IN KNdisConfig& Config)
{
. . .
//获得资源信息
KNdisPnpResourceRequest request(Config);
KNdisResource<CmResourceTypeInterrupt> Int(request);

//确保其可用
if (!Int.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_NOT_ACCEPTED);

//注册你的中断
m_Interrupt.Initialize(this, Int, NdisInterruptLatched);
if (!m_Interrupt.IsValid())
KNDIS_RETURN_ERROR (NDIS_STATUS_RESOURCES);
. . .
}

(5).在硬件上使能你的中断。这和网卡有关,你得阅读硬件的资料了。这步应该不会很难,我想无非是io操作。io操作怎么做,见上一课。

2.同步工作

任何微端口驱动函数若和其他同在DIRQL层上运行的函数共享任何资源,都必须处理同步问题。这个问题出现在中断相关的两个回调函数Isr()

和DisableInterrupt()上。为了同步这些函数,

驱动使用KNdisInterrupt::Synchronize()函数。这个函数将安排指定的函数运行的时候持有一个自旋锁,从来解决同步问题。例子如下:

m_Interrupt.Synchronize(KNDIS_MEMBER_CALLBACK(CardSetMulticast),this);

void MyAdapter::CardSetMulticast()
{
//我们假设这个函数运行于DIRQK,并其中不想被
//Isr()之类的调用打断工作
}

换句话说,使用了Synchronize函数之后,就不必担心CardSetMulicast这个函数会被Isr()或者是DisableInterrupt()这两个函数打断了。

但是在MyAdapter的声明中,还应该有下边的语句:

class MyAdapter:public KNdisMiniAdapter(
...
KNDIS_DECLARE_SYNCHROCALLBACK( MyAdapter,CardSetMulticast);
void CardSetMulticast();
...
);

SYNCHROCALLBACK宏会在adapter类中增加一个静态的成员函数。而系统将会通过这个函数来控制CardSetMulticast()这个函数的运行。

3.最后是注销中断,这样:
m_Interrupt.Invalidate();

关于中断就介绍到这里。具体的使用过程,以后我们看看通过具体的微端口驱动实例就知道了。

下一课介绍如何生成wdm设备对象,以及被动和主动的与应用程序通信。

最新喜欢:

kuangnuzhirenkuangn...
jusinmile
驱动牛犊
驱动牛犊
  • 注册日期2003-09-09
  • 最后登录2008-06-20
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2003-11-03 10:49

请问在用wizard生成框架后,build with ddk build.exe时,这个错误原因何在?谢谢!
NMAKE :  U1073: don\'t know how to make \'D:\\...\\DriverNetworks\\lib\\i386\\FREE\\kndis5mp.lib\'
XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
板凳#
发布于:2003-11-05 12:34
我没装这个了,没办法具体试一下。但是DriverNetworks很多lib都是装了之后要自己重新编译的。描述一下就是说第一次刚开始编译的时候会弹出一个警告,你点上边“开启 vc”后会再打开一个vc,里边有需要编译lib的代码。你选中所有的配置,把各个版本包括check和free的都编译出来。然后第一个 vc也就是是生成的框架就可以正确编译了。
上边是最经常碰到的问题,我只是猜测一下。如果这样做了还解决不了,就得具体检查你的编译环境啦。
jusinmile
驱动牛犊
驱动牛犊
  • 注册日期2003-09-09
  • 最后登录2008-06-20
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地板#
发布于:2003-11-06 14:18
非常感谢!
是没有编译库的问题,这个问题可以了,现在能生成.sys
但呼出softice调试又遇到问题了:生成.NMS文件然后load进去以后,file 文件名,总说此文件不存在,table一下,看不到想调试的 .cpp文件。难道softice还存在路径的问题?实在是没有搞懂,还望大虾指点一二

XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
地下室#
发布于:2003-11-06 14:49
以前我做过一件比较傻的事情就是把.NMS文件拷贝到和sys文件安装了之后的同一个目录下,结果出现这个问题:)试来试去我的结论好象是只要保持.nms文件在原来的位置(和代码在一起),.sys文件拷贝到任何地方,调试都不会出问题。
如果你没动过.nms文件,还是有这个问题,这就…… 我不知道了!不过你可以干脆把所有的代码拷贝到sys安装之后的目录下(比如system\\driver下边编译一把,使生成的sys文件刚好不用再拷贝,再试一次,如果还不行…… :)自己想办法了。
游客

返回顶部