如何能将一个数写入一个特定的地址中去?

解决方案 »

  1.   

    你这个问题一点都不简单:
       在很多情况下,我们都有直接访问物理内存的要求,如在实时高速
    数据采集系统中,对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/9 8下直接访问物理内存,关键是得到欲访问物理内
    存所在的内存区域对应的段选择符。
        一般说来,要求直接访问的物理内存都与实模式下能够寻址的内
    存有关(即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。
      

  2.   

    不错.
    现在硬件已经被一层一层的保护起来了.就连编写WDM也是实际上不能直接操作硬件,而只是调用HAL层定义的宏而已.