網(wǎng)絡(luò)驅(qū)動程序編程要點(diǎn)
編寫Windows 2000的任何網(wǎng)絡(luò)驅(qū)動程序時通常都需要考慮的幾點(diǎn)問題
1 可移植性
2 多處理器支持
3 IRQLs
4 同步和指示
5 包結(jié)構(gòu)
6 使用共享內(nèi)存
7 異步I/O和完成函數(shù)
1. 可移植性
NDIS驅(qū)動程序應(yīng)很容易在支持Windows 2000的平臺間移植。一般說來,從一個硬件平臺移植到另一個平臺只需要將它在兼容系統(tǒng)的編譯中重新編譯即何。
驅(qū)動程序開發(fā)者應(yīng)當(dāng)避免調(diào)用與操作系統(tǒng)相關(guān)的函數(shù),因?yàn)檫@將使他們的驅(qū)動程序不可移植。應(yīng)用NDIS函數(shù)替換這些調(diào)用,只調(diào)用NDIS函數(shù)將使代碼在支持NDIS的微軟操作系統(tǒng)中可移植,NDIS為編寫驅(qū)動程序提供了很多支持函數(shù),并且直接調(diào)用操作系統(tǒng)是不需要的。
驅(qū)動程序應(yīng)用C來編寫。更近一步,驅(qū)動程序代碼應(yīng)當(dāng)限制在ANSI C標(biāo)準(zhǔn)并且應(yīng)當(dāng)避免使用不被其他兼容系統(tǒng)編譯器支持的語言特性。驅(qū)動程序代碼編寫不應(yīng)當(dāng)使用任何ANSI C標(biāo)準(zhǔn)指明為“implementation defined”的特性來編寫。
驅(qū)動程序應(yīng)當(dāng)避免使用任何在不同平臺上大小和結(jié)構(gòu)變化的數(shù)據(jù)類型,驅(qū)動程序代碼不應(yīng)該調(diào)用任何C的運(yùn)行時庫函數(shù),而是限于調(diào)用NDIS提供的函數(shù)。
在內(nèi)核模式下漂移指針是不允許的。試圖運(yùn)行這樣的操作將是一個致命的錯誤。
如果驅(qū)動程序代碼要支持特殊平臺的特性,那么這些代碼應(yīng)當(dāng)包含在#ifdef和#endif聲明之間。
2. 多處理器支持
編寫的代碼應(yīng)當(dāng)可以在多處理器系統(tǒng)中安全運(yùn)行。這對于編寫可移植的Windows 2000驅(qū)動程序是很重要的。一個網(wǎng)絡(luò)驅(qū)動程序必須使用NDIS函數(shù)庫提供的多處理器安全保證。
在單處理器環(huán)境下,在一個時刻單處理器只運(yùn)行一條機(jī)器指令,既使這樣,當(dāng)包到達(dá)或時間中斷發(fā)生時,對于NIC或其他設(shè)備執(zhí)行的中斷也可能發(fā)生。典型的,當(dāng)正在操縱數(shù)據(jù)結(jié)構(gòu)時,例如它的隊列時,驅(qū)動程序?qū)?span lang=EN-US>NIC發(fā)出停用中斷來操縱數(shù)據(jù),隨后再發(fā)生可用中斷。許多線程在單處理器環(huán)境下表現(xiàn)的好像是在同一時刻運(yùn)行的,但是實(shí)際上它們卻是在獨(dú)立的時間片上運(yùn)行的。
在多處理器環(huán)境下,處理器同時可運(yùn)行多條機(jī)器指令,一個驅(qū)動程序必須異步化,這使得當(dāng)一個驅(qū)動程序函數(shù)正在操縱一個數(shù)據(jù)結(jié)構(gòu)時,同樣的在其他處理器運(yùn)行的驅(qū)動程序函數(shù)不能修改共享的數(shù)據(jù)。在一個SMP機(jī)器中,所有的驅(qū)動程序代碼都要重新裝入,為了消除這種資源保護(hù)問題,Windows 2000驅(qū)動程序使用自旋鎖。
3. IRQL
所有NDIS調(diào)用的驅(qū)動程序函數(shù)都運(yùn)行在系統(tǒng)決定的IRQL下, PASSIVE_LEVEL<DLSPATCH_LEVEL<DIRQL中的一個。例如,一個微端口初始化函數(shù)、掛起函數(shù)、重啟函數(shù)和有時的關(guān)閉函數(shù)通常都運(yùn)行在PASSIVE_LEVEL下。中斷代碼運(yùn)行在DIRQL下,所以NDIS中層或協(xié)議驅(qū)動程序從不運(yùn)行在DIRQL下。所有其他的NDIS驅(qū)動程序函數(shù)運(yùn)行在IRQL<=DISPATCH_LEVEL下。
驅(qū)動程序函數(shù)運(yùn)行于的IRQL將影響調(diào)用什么樣的NDIS函數(shù)。特定的函數(shù)只可在IRQL PASSIVE_LEVEL下調(diào)用,其他的函數(shù)可在DISPATCH_LEVEL或更低層調(diào)用。一個驅(qū)動程序的編寫者應(yīng)當(dāng)檢查每一個NDIS函數(shù)的IRQL限制。
任何與驅(qū)動程序的ISR共享資源的驅(qū)動程序函數(shù)必須能將它的IRQL升級到DTRQL來防止?fàn)幱们闆r的發(fā)生,NDIS提供了這種機(jī)質(zhì)。
4. 同步和指示
當(dāng)兩個線程共享可被同時訪問的資源時,無論是單處理機(jī)還是SMP,同步是必須的。例如,對于一個單處理機(jī),如果一個驅(qū)動程序正在訪問一個共享資源時,被一個運(yùn)行在更高IRQL(例如ISR)的函數(shù)中斷時,必須保護(hù)共享資源以阻止這種爭用的發(fā)生而使資源處于不確定狀態(tài)。在一個SMP中,兩個線程可以在同一時刻運(yùn)行,在不同處理器上并且試圖來修改同一數(shù)據(jù)的訪問必須同步。
NDIS提供了自旋鎖可以用來對在同一IRQL下運(yùn)行的線程間訪問共享資源實(shí)現(xiàn)同步。當(dāng)兩個線程在不同IRQL下訪問共享資源時,NDIS提供了一種機(jī)制來臨時提高低IRQL代碼的IRQL,以使得訪問共享資源串行化。
NDIS提供下面四種機(jī)制來保證同步:
自旋鎖
自旋鎖提供了一個用來保護(hù)共享資源的同步機(jī)制,這種資源是單處理器或一個多處理機(jī)下的、運(yùn)行在IRQL>PASSIVE_LEVEL下的、內(nèi)核模式中的線程所共享使用的。一個自旋鎖在同時運(yùn)行在一個SMP機(jī)上不同的執(zhí)行線程之間提供同步。一個線程在訪問保護(hù)資源前獲得一個自旋鎖。自旋鎖使得任務(wù)線程中只有持有自旋鎖的線程可使用資源。一個等待自旋鎖的線程將在試圖獲得鎖時間內(nèi)循環(huán),直到持有鎖的線程釋放為止。
自旋鎖還存在著一個不太明顯但很重要的事實(shí):你僅能在低于或等于DISPATCH_LEVEL級上請求自旋鎖,在你擁有自旋鎖期間,內(nèi)核將把你的代碼提升到DISPATCH_LEVEL級上運(yùn)行。在內(nèi)部,內(nèi)核能在高于DISPATCH_LEVEL的級上獲取自旋鎖,但你和我都做不到這一點(diǎn)。當(dāng)KeAcquireSpinLock獲取自旋鎖時,它會把IRQL提升到DISPATCH_LEVEL級上。當(dāng)KeReleaseSpinLock釋放自旋鎖時,它會把IRQL降低到原來的IRQL級上。如果你知道代碼已經(jīng)處在DISPATCH_LEVEL級上,你可以調(diào)用兩個專用函數(shù)來獲取自旋鎖。KeAcquireSpinLockAtDpcLevel及 KeReleaseSpinLockFromDpcLevel。一個編寫很好的網(wǎng)絡(luò)驅(qū)動程序應(yīng)該會減少自旋鎖持有的時間。
一個典型的使用自旋鎖的例子是保護(hù)一個隊列。例如,微端口發(fā)送函數(shù)MiniportSend將協(xié)議驅(qū)動程序傳來的包進(jìn)行排隊。因?yàn)槠渌?qū)動程序函數(shù)也使用這個隊列,MiniportSend必須用一個自旋鎖保護(hù)這個隊列使得在一個時刻只有一個線程可操縱這個隊列。Miniport Send獲得自旋鎖,添加包到隊列后釋放自旋鎖。使用自旋鎖保證持鎖線程是唯一修改隊列的線程,同時使得包被安全地添加到隊列中。當(dāng)NIC驅(qū)動程序從隊列中取走包時,通過同樣的自旋鎖保護(hù)這個訪問。當(dāng)執(zhí)行指令修改隊列頭或任何隊列組成域時,驅(qū)動程序必須用自旋鎖保護(hù)隊列。
避免死鎖問題
Windows 2000并不限制網(wǎng)絡(luò)驅(qū)動程序同時持有多于一個的自旋鎖。但是,驅(qū)動程序的某部分在持有自旋鎖B時,試圖獲得自旋鎖A,并且其他部分在持有鎖A時,試圖獲得自旋鎖B時,死鎖就會發(fā)生。如果要獲得多于一個的自旋鎖,驅(qū)動程序應(yīng)當(dāng)通過強(qiáng)制以某一順序獲得鎖來避免死鎖,這就是說,如果一個驅(qū)動程序強(qiáng)制在獲得自旋鎖A之后才可獲得鎖B,那么上述情況就不會發(fā)生。
總得來說,使用自旋鎖將對系統(tǒng)性能帶來負(fù)面效應(yīng),所以驅(qū)動程序不應(yīng)當(dāng)使用許多鎖。
時鐘
時鐘被用來輪詢或進(jìn)行超時操作的。一個驅(qū)動程序可以產(chǎn)生一個時鐘并與一個函數(shù)關(guān)聯(lián)上。當(dāng)一個特定周期時鐘期滿時,調(diào)用相關(guān)函數(shù)。時鐘可以是一次的或周期性的,一但設(shè)置了一個周期時鐘,當(dāng)每個周期結(jié)束時都會觸發(fā),直到它被完全清除掉為止。一次性時鐘在觸發(fā)后必須重新設(shè)置。
時鐘通過調(diào)用NdisMInitializeTimer來產(chǎn)生和初始化,并且通過調(diào)用NdisMsetTimer來設(shè)置,也可調(diào)用NdisMsetPeriodicTimer設(shè)置周期時鐘。如果使用了一個非周期時鐘,那么通過調(diào)用NdisMSetPeriodicTimer重新設(shè)置時鐘。通過調(diào)用NdisMCancelTimer可以清除時鐘。
事件
事件在兩個執(zhí)行線程之間實(shí)現(xiàn)同步操作。一個事件通過一個驅(qū)動程序裝入并且通過調(diào)用NdisInitializeEvent初始化。一個運(yùn)行在IRQL PASSIVE_LEVEL下的線程調(diào)用NdisWaitEvent來將自身轉(zhuǎn)入等侯狀態(tài)。當(dāng)一個驅(qū)動程序線程等待一個事件時,它指定了最大等待時間即等待事件的時間。當(dāng)調(diào)用NdisSetEvent使時間得到信號量,或最大等待時間段結(jié)束時,它們兩個無論是誰先發(fā)生時都將結(jié)束線程等待狀態(tài)。
典型的,事件是通過相互協(xié)調(diào)的線程調(diào)用NdisSetEvent來設(shè)置的。事件被創(chuàng)建時是沒有信號量的,但為了指示等待線程,它必須要設(shè)置信號量,事件將一直處于保持有信號狀態(tài),直到NdiResetEvent調(diào)用后為止。
5. 包結(jié)構(gòu)
通過一個協(xié)議驅(qū)動程序可以分配NDIS包、填充數(shù)據(jù),并且將它傳遞到下層的NDIS驅(qū)動程序,以便將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上。一些最底層的NIC驅(qū)動程序分配包用來保存接收到的數(shù)據(jù),并將包傳遞到對應(yīng)的高層驅(qū)動程序。有時,一個協(xié)議驅(qū)動程序分配一個包,并且通過一個請求將它傳給NIC驅(qū)動程序,以使NIC驅(qū)動程序?qū)⒔邮盏降臄?shù)據(jù)拷貝到提供的包中。NDIS提供函數(shù)用來分配和操縱構(gòu)成包的子結(jié)構(gòu)。
包描述符
Flags:physicallPageCount:
ToralLenth:FirstBuffer:P
…
|
緩存描述符:
StartVirtualAddress:P
ByteOffset ByteCount PhysicalPage[] Next P....
|
緩存描述符:
StartVirtualAddress:P
ByteOffset ByteCount PhysicalPage[] Next P....
|
圖1:NDIS包結(jié)構(gòu)
具體詳情可以參考:http://www.ndis.com/papers/ndispacket/ndispacket1.htm
6. 使用共享內(nèi)存
用作總線管理DMA設(shè)備的微端口驅(qū)動程序必須為NIC和NIC驅(qū)動程序分配共享內(nèi)存。當(dāng)在一個驅(qū)動程序和它的NIC之間共享cache時,特別的預(yù)防是必須的。在某種結(jié)構(gòu)下,必須采取特別步驟來保證內(nèi)存一致,因?yàn)?span lang=EN-US>NIC可以直接訪問共享的物理內(nèi)存,而NIC驅(qū)動程序卻要通過cache訪問內(nèi)存。這就引起驅(qū)動程序和NIC訪問內(nèi)存的不同,即使它們看起來在同一位置。
7. 異步I/O和完成函數(shù)
因?yàn)樵谝恍┚W(wǎng)絡(luò)操作中有繼承的因素,許多由NIC驅(qū)動程序提供的上層函數(shù)和協(xié)議驅(qū)動程序提供的下層函數(shù)被設(shè)計成支持異步操作,而不是用CPU消耗一定時間的循環(huán)來等待一個任務(wù)的完成或硬件事件的指示,網(wǎng)絡(luò)驅(qū)動程序依賴處理許多異步操作的能力。
通過使用完成函數(shù)來支持異步網(wǎng)絡(luò)I/O。以下的例子將說明網(wǎng)絡(luò)的send操作如何使用一個完成函數(shù),同樣的機(jī)制也存在一個協(xié)議或NIC驅(qū)動程序的其他操作中。
當(dāng)協(xié)議驅(qū)動程序調(diào)用NDIS發(fā)送一個包時,NDIS調(diào)用NIC驅(qū)動程序的MiniportSend函數(shù)發(fā)送請求,NIC驅(qū)動程序試圖立即完成這個請求并且返回一個恰當(dāng)?shù)臓顟B(tài)值。對于同步操作,可能返回NDIS_STATUS_SUCCESS作為發(fā)送成功的標(biāo)志,NDIS_STATUS_RESOURCES和NDIS_STATUS_FAILURE表明有某些失敗。
但是一個發(fā)送操作要花費(fèi)一些時間來完成,此時NIC驅(qū)動程序(或NDIS)可將包排隊并且等侯NIC指示發(fā)送操作的結(jié)果。NIC驅(qū)動程序的MiniportSend函數(shù)可以通過返回一個NDIS_STATUS_PENDING的狀態(tài)值來異步處理這個操作,當(dāng)NIC驅(qū)動程序完成了發(fā)送操作后,包調(diào)用完成函數(shù)NdisMSendComplete在調(diào)用中傳遞指向一個已被發(fā)送的包的描述符的指針。這個信息會傳給協(xié)議驅(qū)動程序,指示完成了操作。
許多需要一定時間來完成的驅(qū)動程序操作用完成數(shù)來完成支持異步的操作。這種函數(shù)有同一形式的名字NidisMXxxComplete。不僅可用于發(fā)送和接收函數(shù),完成函數(shù)也可用于查詢、配置、重新設(shè)置硬件、狀態(tài)指示、指示收到數(shù)據(jù)和傳送收到數(shù)據(jù)。
posted on 2009-02-22 22:43
水 閱讀(4024)
評論(0) 編輯 收藏 引用 所屬分類:
windows驅(qū)動