Windows設(shè)備驅(qū)動(dòng)程序編寫時(shí)的主要問題及解決方案
在PCI設(shè)備驅(qū)動(dòng)程序的初始化過程中,利用指定器件識(shí)別號(hào)(device_id)商識(shí)別號(hào)(vendor_id)、檢索號(hào)(index)搜索PCI器件,通過調(diào)用PC IBIOS確認(rèn)其存在,并確定其物理位置:總線號(hào)、器件號(hào)和功能號(hào),這是該器件/功能在系統(tǒng)中的唯一尋址標(biāo)志。利用總線號(hào)、器件號(hào)和功能號(hào)可以尋址該器件/功能的PCI配置空間(configuration space)。 接下來,設(shè)備驅(qū)動(dòng)就需要從配置空間獲得硬件的參數(shù)。PCI設(shè)備的許多參數(shù),包括所用的中斷號(hào),端口地址的范圍I/O方式、存儲(chǔ)器的地址?存儲(chǔ)器映射方式?等,都可以從PCI配置空間的各基址所對(duì)應(yīng)的尋址空間中得到。讀寫配置空間可以調(diào)用BIOS中斷1AH,也可以先向配置空間地址寄存器?(0CF8H)寫入總線和設(shè)備號(hào)?在前面搜索PCI器件時(shí)得到的和寄存器號(hào),再對(duì)配置空間數(shù)據(jù)寄存器?(0CFCH)進(jìn)行讀寫。對(duì)設(shè)備驅(qū)動(dòng)來說,最重要的是獲得基址寄存器(BADR),不能認(rèn)為PCI器件資源總是設(shè)計(jì)設(shè)備時(shí)設(shè)置的初值,系統(tǒng)可能會(huì)根據(jù)硬件情況為PCI設(shè)備分配新的資源。我們所設(shè)計(jì)的PCI設(shè)備使用的基址1-3都是按I/O空間映射的,而基址4是按內(nèi)存方式映射的。確定一個(gè)端口是按什么方式映射的,可以讀對(duì)應(yīng)端口的配置寄存器(Configuration Register)。讀出后,判斷其0位,如果0位為數(shù)值0,表示其是按內(nèi)存方式設(shè)置的,否則為I/O方式設(shè)置的。內(nèi)存方式和I/O方式的配置寄存器的含義參見文獻(xiàn)[3]。如果要獲得基址的大小,可以向基址寄存器寫入FFFFH,然后讀基址寄存器,如果是內(nèi)存方式,從第4位開始的0的數(shù)目表示基址的大小,如果是I/O方式,則從第2位開始的0的數(shù)目表示基址的大小。
? (2)?端口操作 在PC機(jī)上,I/O端口尋址空間和內(nèi)存尋址空間是不同的,所以處理方法也不同。I/O空間是一個(gè)64K字節(jié)的尋址空間,它不象內(nèi)存有實(shí)模式和保護(hù)模式之分,在各種模式下尋址方式相同。在Windows 9X下,用戶程序可以直接使用I/O指令,而不一定非通過專門的驅(qū)動(dòng)程序來完成,所以如果軟件對(duì)硬件的操作完全是通過I/O端口操作來完成的,甚至可以不用專門設(shè)計(jì)驅(qū)動(dòng)程序,直接由應(yīng)用程序來完成對(duì)硬件的控制。由于PCI總線是32位的總線標(biāo)準(zhǔn),在進(jìn)行I/O操作時(shí)通常要進(jìn)行雙字(DWORD)操作,而且以前大多數(shù)C/C++編譯軟件都沒有提供雙字的函數(shù),所以需要構(gòu)造雙字操作讀寫函數(shù)inpd/outpd。
在Windows NT下,系統(tǒng)不允許處于優(yōu)先級(jí)3級(jí)的用戶程序和用戶模式驅(qū)動(dòng)程序直接使用I/O指令,如果使用了I/O指令將會(huì)導(dǎo)致特權(quán)指令意外(privileged instruction exception)。所以任何對(duì)I/O的操作都需要借助內(nèi)核模式驅(qū)動(dòng)來完成。具體的做法有兩種:一是在驅(qū)動(dòng)程序中使用IoReportResourceUsage報(bào)告資源占用,然后使用READ_PORT_XXX、WRITE_PORT_XXX函數(shù)讀寫,最后使用IoReportResourceUsage取消資源占用;另一種是驅(qū)動(dòng)程序修改NT的I/OPermissions Map(IOPM),以使系統(tǒng)允許用戶程序?qū)χ付ǖ腎/O端口進(jìn)行操作,這時(shí)用戶程序采用通常的I/O指令進(jìn)行操作。后者的優(yōu)點(diǎn)是速度快、用戶程序設(shè)計(jì)簡(jiǎn)單,但犧牲了移植性,程序不能移植到非Intel的系統(tǒng)中,而且如果多個(gè)程序同時(shí)讀寫同一端口容易導(dǎo)致沖突。 ?
3?內(nèi)存的讀寫 Winsows工作在32位保護(hù)模式下,保護(hù)模式與實(shí)模式的根本區(qū)別在于CPU尋址方式上的不同,這也是Windows驅(qū)動(dòng)程序設(shè)計(jì)中需要著重解決的問題。Windows采用了分段、分頁機(jī)制如圖1?,這樣使應(yīng)用程序產(chǎn)生一種錯(cuò)覺,好象程序中可以使用非常大的物理存儲(chǔ)空間。這樣做最大的好處就是一個(gè)程序可以很容易地在物理內(nèi)存容量不一樣的、配置范圍差別很大的計(jì)算機(jī)上運(yùn)行,編程人員使用虛擬存儲(chǔ)器可以寫出比任何實(shí)際配置的物理存儲(chǔ)器都大得多的程序。每個(gè)虛擬地址由16位的段選擇子和32位段偏移量組成。通過分段機(jī)制,系統(tǒng)由虛擬地址產(chǎn)生線性地址。再通過分頁機(jī)制,由線性地址產(chǎn)生物理地址。線性地址被分割成頁目錄(Page Directory)、頁表(Page Table)和頁偏移(Offset)三個(gè)部分。當(dāng)建立一個(gè)新的Win32進(jìn)程時(shí),操作系統(tǒng)會(huì)為它分配一塊內(nèi)存,并建立它自己的頁目錄、頁表,頁目錄的地址也同時(shí)放入進(jìn)程的現(xiàn)場(chǎng)信息中。當(dāng)計(jì)算一個(gè)地址時(shí),系統(tǒng)首先從CPU控制器CR3中讀出頁目錄所在的地址,然后根據(jù)頁目錄得到頁表所在的地址,再根據(jù)頁表得到實(shí)際代碼/數(shù)據(jù)頁的頁幀,最后再根據(jù)頁偏移訪問特定的單元。硬件設(shè)備讀寫的是物理內(nèi)存,但應(yīng)用程序讀寫的是虛擬地址,所以存在著將物理內(nèi)存地址映射到用戶程序線性地址的問題。
從物理地址到線性地址的轉(zhuǎn)換工作也是由驅(qū)動(dòng)程序來完成的。在Windows 95下,使用DDK的VMMCall_M(jìn)apPhysToLinear進(jìn)行地址映射。驅(qū)動(dòng)程序的內(nèi)存映射部分主要是調(diào)用VxD的系統(tǒng)服務(wù)MapPhysToLinear。在VtoolsD中這個(gè)函數(shù)的定義如下:
PVOID MapPhysToLineag(CONST VOID * PhysAddr,DWORD nBytes,DWORD Flags); 其中第一個(gè)參數(shù)PhysAddr就是要映射的內(nèi)存的物理地址的起始位置,而nBytes是內(nèi)存區(qū)域的長度,F(xiàn)lags必須設(shè)置為0。這個(gè)函數(shù)返回的就是這段物理地址映射的線性內(nèi)存地址。如果指定的內(nèi)存不能存取,函數(shù)將返回FFFFFFFFH。 比如要映射物理內(nèi)存ED000000H開始的4096個(gè)字節(jié),可以這樣做: PCHAR *PointerToPage=(PCHAR)MapPhysToLinear((PVOID)OxED000000,4096,0); 而將PointerToPage傳遞給調(diào)用驅(qū)動(dòng)的用戶程序,在用戶程序中使用 DWORD *pFIFOBodyBase=(DWORD*)PointerToPage; 而這個(gè)PFIFOBodyBase指針就可以象普通的指針一樣進(jìn)行讀寫操作,而通過對(duì)這個(gè)指針的操作就可以實(shí)現(xiàn)對(duì)物理內(nèi)存ED000000H進(jìn)行讀寫。
在Windows NT下,首先調(diào)用IoReportResourceUsage請(qǐng)求使用設(shè)備的內(nèi)存。然后調(diào)用HalTranslateBusAddress轉(zhuǎn)換與總線相關(guān)的內(nèi)存為系統(tǒng)的物理內(nèi)存地址。再使用MmMapIoSpace把設(shè)備的內(nèi)存映射到虛擬空間。在設(shè)備驅(qū)動(dòng)卸出時(shí),調(diào)用MmUnmapIoSpace斷開設(shè)備的內(nèi)存和虛擬空間的連接。 ?
(4)?中斷的設(shè)置、響應(yīng)與調(diào)用 對(duì)中斷的設(shè)置、響應(yīng)與調(diào)用應(yīng)該在驅(qū)動(dòng)程序中完成。
對(duì)中斷的調(diào)用?象前面調(diào)用BIOS的1AH中斷讀取配置寄存空間?可以由DDK的Exec_Int完成。
PCI設(shè)備驅(qū)動(dòng)程序應(yīng)當(dāng)從PCI配置寄存器的中斷寄存器(INTLN)和中斷引腳寄存器(INTPIN)?中獲得有關(guān)中斷的信息。DDK還提供了響應(yīng)中斷事件的服務(wù)。如在Windows 95中,VPICD服務(wù)用來管理所有硬件中斷事件。PC機(jī)的硬件中斷需要確定硬件中斷的IRQ,對(duì)一個(gè)特定的IRQ中斷源,VPICD或者提供缺省的中斷處理函數(shù),或者允許其它VxD重載中斷處理函數(shù)。在VtoolsD中,要處理硬件中斷應(yīng)該從VHardwareInt繼承一個(gè)類。在這個(gè)類中,VtoolsD提供了編寫中斷響應(yīng)程序所需的功能。
在Windows NT中,同VPICD對(duì)應(yīng)的中斷服務(wù)為中斷請(qǐng)求層(IRQL)。設(shè)備驅(qū)動(dòng)首先使用HalGetInterrupuVector將與總線有關(guān)的中斷向量轉(zhuǎn)換為系統(tǒng)的中斷向量,然后利用IoConnectInterrupu指定中斷服務(wù)。
3 設(shè)備驅(qū)動(dòng)的調(diào)用
編寫設(shè)備驅(qū)動(dòng)并不是最終的目的,總是需要由用戶程序來調(diào)用驅(qū)動(dòng)并實(shí)現(xiàn)一定的功能。一般調(diào)用設(shè)備驅(qū)動(dòng)是使用CreateFile函數(shù)打開設(shè)備文件,得到一個(gè)文件句柄。具體到我們的設(shè)備
c++相關(guān)文章:c++教程
評(píng)論