?? 網絡驅動教程.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 + -