Windows 95/98下直接訪問物理內存
發表于:2007-07-14來源:作者:點擊數:
標簽:
在很多情況下,我們都有直接訪問物理內存的要求,如在實時高速數據采集系統中, 對I/O板上配置的存儲器的訪問。但是,為了保證系統的 安全 性和穩定性,操作系統 并不提倡應用程序直接訪問硬件資源, 因此,隨著操作系統的進步,導致了目前存在 的這樣一個不幸的事實
在很多情況下,我們都有直接訪問物理內存的要求,如在實時高速數據采集系統中,
對I/O板上配置的存儲器的訪問。但是,為了保證系統的
安全性和穩定性,操作系統
并不提倡應用程序直接訪問硬件資源, 因此,隨著操作系統的進步,導致了目前存在
的這樣一個不幸的事實: 以前在DOS下很容易實現的特定物理內存的讀寫操作,在Windows
下卻變得相當困難。
本文主要討論如何在Windows 95/98下實現物理內存的直接讀寫操作。為了論述清
楚這個問題,有必要敘述保護模式的尋址方式以及W indows 95/98的內存管理方式。
Windows 95/98內存管理方式
Windows 95/98工作在32位保護模式下,保護模式與實模式的根本區別在于CPU尋址方
式上的不同:盡管兩者對應的內存地址均為"段地址:偏移量"形式,但在保護模式下,
"段地址"代表的值已不再是實模式中段的起始基準地址了;對于CS、DS、ES、SS寄存
器,在實模式下,這些寄存器的值左移4位,再加上偏移量,即得到物理地址,而在保護
模式下,這些寄存器的值為"段選擇符",它實際上是一個查全局描述符表(G DT)或局
部描述符表(LDT)的索引,據此在GDT或LDT找到對應的段描述符,從而獲得段的基址及
類型等信息,再根據偏移量,才能得到線性地址。如果操作系統沒有采用分頁機制,
那么得到的線性地址即為物理地址,否則,線性地址需要進一步經過分頁機制才能得
到物理地址。這就是保護模式下的"段頁式尋址機制"。
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中,
Microsoft 卻不在任何文檔中提供這些段的預定義,在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()函數得到欲訪問物理內存對應的段選擇符,該API函數的原型定義為
BOOL GetThreadSelectorEntry (
HANDLE hThread,
// handle of thread that contains selector
DWORD 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 <= 0xffff; sel +=8 ) {
if (::GetThreadSelectorEntry ( theApp.m_hThread,
DWORD ( sel ), &ldtEntry ) ) {
baseMid = ldtEntry . HighWord . Bytes . BaseMid;
baseMid <<= 16;
baseHigh = ldtEntry . HighWord . Bytes . BaseHi;
baseHigh <<= 24;
base = ldtEntry . BaseLow + baseMid +
baseHigh;
limitHigh = m_ldtEntry . HighWord . Bits . LimitHi;
limitHigh <<= 24;
limit = limitHigh + m_ldtEntry . LimitLow;
if ( 0xFFFF == limit )
if ( BASE_DESIRED == base ) {
// BASE_DESIRED為內存段對應的基地址
wSelector = sel;
break; }}}
直接訪問物理內存的實現
得到了段選擇符之后,即可把該段選擇符置于相應的段寄存器中( 不能用CS,DS),
用該寄存器進行數據訪問。需注意的是,任何非法段選擇符寫入段寄存器將會導
致通用保護錯誤(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 [i];
_asm { push es mov ax, sel mov es, ax
mov ebx, dwOffset mov al, cWrite mov byte ptr es:[ebx], al
inc dwOffset pop es
}
}
}
void ReadMemory ( WORD sel, DWORD dwOffset,char * str, UINT length )
{
char cRead;
for ( UINT i = 0; i < length; i ++ ) {
_asm {
push es
mov ax, sel
mov es, ax
mov ebx, dwOffset
mov al, byte ptres:[ebx]
mov cRead, al
inc dwOffset
pop es
}
str [i] = cRead;
}
}
本文所用操作系統為中文Windows 95 OSR 2.0以及中文Windows
98,編程環境為Vis ual C++ 5.0。
原文轉自:http://www.anti-gravitydesign.com