Windows 95/98下直接訪問實體記憶體 (轉)

worldblog發表於2007-12-10
Windows 95/98下直接訪問實體記憶體 (轉)[@more@] 95/98下直接訪問物理   在很多情況下,我們都有直接訪問實體記憶體的要求,如在實時高速資料採集中, 對I/O板上的器的訪問。但是,為了保證系統的性和穩定性, 並不提倡應用直接訪問資源, 因此,隨著作業系統的進步,導致了目前存在 的這樣一個不幸的事實: 以前在DOS下很容易實現的特定實體記憶體的讀寫操作,在Windows 下卻變得相當困難。 本文主要討論如何在Windows 95/98下實現實體記憶體的直接讀寫操作。為了論述清 楚這個問題,有必要敘述保護的定址方式以及W indows 95/98的記憶體管理方式。 Windows 95/98記憶體管理方式 Windows 95/98工作在32位保護模式下,保護模式與真實模式的根本區別在於定址方 式上的不同:儘管兩者對應的記憶體地址均為&quot;段地址:偏移量&quot;形式,但在保護模式下, &quot;段地址&quot;代表的值已不再是真實模式中段的起始基準地址了;對於CS、DS、ES、SS寄存 器,在真實模式下,這些暫存器的值左移4位,再加上偏移量,即得到實體地址,而在保護 模式下,這些暫存器的值為&quot;段選擇符&quot;,它實際上是一個查全域性描述符表(G DT)或局 部描述符表(LDT)的,據此在GDT或LDT找到對應的段描述符,從而獲得段的基址及 型別等資訊,再根據偏移量,才能得到線性地址。如果作業系統沒有采用分頁機制, 那麼得到的線性地址即為實體地址,否則,線性地址需要進一步經過分頁機制才能得 到實體地址。這就是保護模式下的&quot;段頁式定址機制&quot;。 Windows 95/98使用4GB的虛擬記憶體地址空間,應用程式訪問記憶體使用虛擬地址,從虛 擬地址到實體地址的轉換過程如圖1所示: 圖1 虛擬地址到實體地址的轉化過程 對於圖1中的分頁機制,Windows 95/98採用兩級頁表結構,如圖2 所示。圖2 採用的 分頁機制的兩級頁表結構 從圖2可知,線性地址被分割成頁目錄條目(PDE)、頁表條目(PTE) 、頁偏移地址(Off set) 三個部分。當建立一個新的WIN 32程式時,Wi ndows 95/98會為它分配一塊記憶體,並 建立它自己的頁目錄、頁表,頁目錄的地址也同時放入程式的現場資訊中。當計算一 個地址時,系統首先從控制暫存器CR3中讀出頁目錄所在的地址(該地址為實體地址, 並且是頁對齊的),然後根據PDE得到頁表所在的地址,再根據PTE得到包含了實際Code 或Data的頁幀, 最後根據Offset訪問頁幀中的特定單元。 常用記憶體段的段選擇符 從上述所介紹的Windows 95/98採用的分段、分頁機制可看出,要想在Windows 95/98 下直接訪問實體記憶體,關鍵是得到欲訪問實體記憶體所在的記憶體區域對應的段選擇符。 一般說來,要求直接訪問的實體記憶體都與真實模式下能夠定址的記憶體有關(即DOS能直 接訪問的1M實體記憶體)。在Windows 3.X中,Microso ft給出了DOS常用段的段選擇符, 如_000 0H(未公開),_B800H,_F000H( 已公開),等等,均可以在KERNEL中找到,應用 程式可以直接使用這些段選擇符,實現實體記憶體的直接訪問。而在Windows 95/98中, 卻不在任何文件中提供這些段的預定義,在KERNEL中也不提供相應的段選 擇符。但是,Windows 95 /98確實給DOS下的這些常用記憶體段定義了相應的段描述符。 透過SoftIce 3.02 for Win dows 95/98,我們得到了關於LDT的如下資訊: ...... :ldt LDTbase=80003000 Limit=3FFF …… 1007 Data16 00000C90 0000FFFF 3 P RW 100F Data16 00000000 0000FFFF 3 P RW 1017 Data16 00000400 0000FFFF 3 P RW 101F Data16 000F0000 0000FFFF 3 P RW 1027 Data16 000A0000 0000FFFF 3 P RW 102F Data16 000B0000 0000FFFF 3 P RW 1037 Data16 000B8000 0000FFFF 3 P RW 103F Data16 000C0000 0000FFFF 3 P RW 1047 Data16 000D0000 0000FFFF 3 P RW 104F Data16 000E0000 0000FFFF 3 P RW …… 其中,每一行對應一個段描述符,第一欄為其段選擇符,第二欄為段描述符的型別, 第三欄為段的基地址(線性地址),第四欄為段的限長 ,第五欄為段描述符的特權級, 第六欄標誌對應段是否存在於記憶體中, 第七欄表示段的訪問。 可以看出,這些段的基地址與DOS下的常用記憶體段完全吻合,並且均為16位的資料段, 限長為64K(0XFFFF),供應用程式訪問,都存在於記憶體中,可讀寫。實踐證明,這些段 就是D OS的常用記憶體段,也就是說, 這裡的線性地址即為實體地址。因此,可以用這 些段選擇符對相應的實體記憶體進行訪問。 從程式執行的健壯性考慮,不應該直接應用上述段選擇符,而應該用GetThreadSelec torEntry()得到欲訪問實體記憶體對應的段選擇符,該函式的原型定義為 BOOL GetThreadorEntry ( HANDLE hThread, // handle of thread that contains selector D dwSelector, // number of selector value to look up LPLDT_ENTRY lpSelectorEntry // address of selector entry structure ); 其中,LDT_ENTRY的結構定義如下 typedef struct _LDT_ENTRY { // ldte WORD LimitLow; WORD BaseLow; union { struct { BYTE BaseMid; BYTE Flags1; BYTE Flags2; BYTE BaseHi; } Bytes; struct { DWORD BaseMid : 8; DWORD Type : 5; DWORD Dpl : 2; DWORD Pres : 1; DWORD LimitHi : 4; DWORD Sys : 1; DWORD Reserved_0 : 1; DWORD Default_Big : 1; DWORD Granularity : 1; DWORD BaseHi : 8; } Bits; } HighWord; } LDT_ENTRY, *PLDT_ENTRY; 用下面的程式碼可以得到基地址為BASE_DESIRED,限長為0XFFFF的 記憶體段對應的段選擇符: ...... extern CLDTApp theApp; WORD wSelector; // 記憶體段對應的段選擇符 LDT_ENTRY ldtEntry; DWORD base, baseMid, baseHigh; DWORD limit, limitHigh; for ( WORD sel = 7; sel 非法段選擇符寫入段暫存器將會導 致通用保護錯誤(General Protection Faul t)。 下面的程式碼實現實體記憶體的讀/寫操作(段選擇符用上述方法得到): void WriteMemory(WORD sel, DWORD dwOffset, const char * str, UINT length) { char cWrite; for ( UINT i = 0; i <length i="" cwrite="str" es="" sel="" ax="" dwoffset="" byte="" ptr="" al="" void="" readmemory="" word="" dword="" uint="" length="" char="" for="" osr="" href="http://blog.itpub.net/10752043/viewspace-990722/tag-255-1.html">程式設計環境為Vis ual C++ 5.0。 </length>

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990722/,如需轉載,請註明出處,否則將追究法律責任。

相關文章