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

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97