第四章 探索 Windows 2000 的内存管理机制
翻译: Kendiv( [url=http://www.pccode.net].net"fcczj@263.net )
更新: Sunday, February 14, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
数据结构
本章随后的示例代码的某些部分将涉及底层的内存管理机制,在前面我们已快速浏览了该机制内部的大致轮廓。为了方便,我用 C 语言定义了几个数据结构。这是因为 i386 CPU 内部的很多数据项需要使用一个二进制位或一组二进制位,而 C 的位域( bit-fields )唾手可得。位域可以很有效的访问一个大的数据中的一个位或从中提取一组连续的位。微软的 Visual C/C++ 可以产生非常棒的代码来完成位域的操作。 列表 4-2 是一系列 CPU 数据类型定义的一部分,该列表包含如下的内容:
l X86_REGISTER 这是一个基本的无符号 32 位整数类型,该类型可描述多个 CPU 寄存器。这包括:通用的、索引、指针、控制、调试和测试寄存器。
l X86_SELECTOR 代表一个 16 位的段选择器,如 CS 、 DS 、 ES 、 FS 、 GS 和 SS 。在 图 4-1 和 图 4-2 中,选择器可描述 48 位逻辑地址的高 8 位,或作为描述符表的索引。为了计算的方便, 16 位选择器的值被扩展到 32 位,不过高 16 位被标识为“保留”。注意, X86_SELECTOR 结构实际是两个结构的联合( union )。第一个指定了选择器的值,该值占用一个 16 位的 WORD ,其名字为 wValue ,第二个采用了位域。 RPL 域指定了请求的特权级,在 Windows 2000 上其值或者为 0 (内核模式)或者为 3 (用户模式)。 TI 位用来选择 GDT 或 LDT 。
l X86_DESCRIPTOR 定义了由选择器指向的页表项的格式。这是一个 64 位的数值,由于历史演化,该结构比较让人费解。线性基地址定义了与其相关的段的起始位置,它们分散在三个位域中: Base1 、 Base2 和 Base3 , Base1 是作用最小的部分。段的界限指定了段的大小, The segment limit specifying the segment size minus one is divided into the pair Limitl and Limit2, with the former representing the least significant half. 剩余的位域存放不同的段属性( cf. Intel 1999c, pp.3-11 )。例如, G 位域定义了段的粒度。如果为零,段的限制按字节指定;否则,限制值为 4KB 的倍数。像 X86_SELECTOR 一样, X86_DESCRIPTOR 结构由一个 union 组成,以允许按不同的方式解释它的值。如果你必须复制描述符(在忽略其内部情况下)那么 dValueLow 和 dValueHigh 成员将会很有帮助。
l X86_GATE 该结构看起来有些像 X86_DESCRIPTOR 。事实上,这两个结构是相关的: X86_DESCRIPTRO 是一个 GDT 项,并描述了一个段的内存属性, X86_GATE 代表中断描述符表( IDT )中的一项,并描述了中断例程的内存属性。 IDT 可以包含任务、中断和陷阱门(不! Bill Gates 并没有存储在 IDT 中! 哈哈)。 X86_GATE 结构可匹配上述三种类型,并通过 Type 位域来进行区分。 Type 5 表示这是一个任务门; Type 6 和 14 为中断门; Type 7 和 15 为陷阱门。 Type 中最重要的位是用来描述门的位数的位:该位若为 0 则表示是 16 位门;其余情况表示 32 位门。
l X86_TABLE 是一个巧妙的结构,该结构用来读取 GDTR 或 IDTR 的当前值,分别通过汇编指令 SGDT (存储 GDT 寄存器)和 SIDT (存储 IDT 寄存器)来实现( cf. Intel 1999b, pp.3-636 )。这两个指令需要一个 48 位的内存操作数,在该操作数中存放限制值和基地址值。通过在结构体中增加一个 DWORD 来对齐 32 位的基地址, X86_TABLE 以一个 16 位的哑元成员 wReserved 开始。根据是否使用了 SGDT 或 SIDT 指令,其基地址将被解释为一个描述符指针或一个门指针,就像 PX86_DESCRIPTOR 和 PX86_GATE 中的 union 所暗示的那样。最后的 wLimit 成员在这两种类型的表中的意义均相同。
译注:
列表 4-2 中的这些结构定义可以在随书光盘的 \src\common\include\w2k_spy.h 中找到。
typedef DWORD X86_REGISTER, *PX86_REGISTER, **PPX86_REGISTER;
// -----------------------------------------------------------------
typedef struct _X86_SELECTOR
{
union
{
struct
{
WORD wValue; // packed value
WORD wReserved;
};
struct
{
unsigned RPL : 2; // requested privilege level
unsigned TI : 1; // table indicator: 0=gdt, 1=ldt
unsigned Index : 13; // index into descriptor table
unsigned Reserved : 16;
};
};
}
X86_SELECTOR, *PX86_SELECTOR, **PPX86_SELECTOR;
#define X86_SELECTOR_ sizeof (X86_SELECTOR)
// -----------------------------------------------------------------
typedef struct _X86_DESCRIPTOR
{
union
{
struct
{
DWORD dValueLow; // packed value
DWORD dValueHigh;
};
struct
{
unsigned Limit1 : 16; // bits 15..00
unsigned Base1 : 16; // bits 15..00
unsigned Base2 : 8; // bits 23..16
unsigned Type : 4; // segment type
unsigned S : 1; // type (0=system, 1=code/data)
unsigned DPL : 2; // descriptor privilege level
unsigned P : 1; // segment present
unsigned Limit2 : 4; // bits 19..16
unsigned AVL : 1; // available to programmer
unsigned Reserved : 1;
unsigned DB : 1; // 0=16-bit, 1=32-bit
unsigned G : 1; // granularity (1=4KB)
unsigned Base3 : 8; // bits 31..24
};
};
}
X86_DESCRIPTOR, *PX86_DESCRIPTOR, **PPX86_DESCRIPTOR;
#define X86_DESCRIPTOR_ sizeof (X86_DESCRIPTOR)
// -----------------------------------------------------------------
typedef struct _X86_GATE
{
union
{
struct
{
DWORD dValueLow; // packed value
DWORD dValueHigh;
};
struct
{
unsigned Offset1 : 16; // bits 15..00
unsigned Selector : 16; // segment selector
unsigned Parameters : 5; // parameters
unsigned Reserved : 3;
unsigned Type : 4; // gate type and size
unsigned S : 1; // always 0
unsigned DPL : 2; // descriptor privilege level
unsigned P : 1; // segment present
unsigned Offset2 : 16; // bits 31..16
};
};
}
X86_GATE, *PX86_GATE, **PPX86_GATE;
#define X86_GATE_ sizeof (X86_GATE)
// -----------------------------------------------------------------
typedef struct _X86_TABLE
{
WORD wReserved; // force 32-bit alignment
WORD wLimit; // table limit
union
{
PX86_DESCRIPTOR pDescriptors; // used by sgdt instruction
PX86_GATE pGates; // used by sidt instruction
};
}
X86_TABLE, *PX86_TABLE, **PPX86_TABLE;
#define X86_TABLE_ sizeof (X86_TABLE)
列表 4-2. i386 的寄存器、选择器、描述符、门和表
接下来的一组与 i386 内存管理相关的结构,它们收录在 列表 4-3 中,这些结构包括:与请求式分页相关的结构和 图 4-3 和 图 4-4 给出的几个成员。
l X86_PDBR 该结构对应 CPU 的 CR3 寄存器,即众所周知的页目录基地址寄存器( PDBR )。其高 20 位为 PFN ,即 4KB 物理页数组的索引。 PFN=0 对应物理地址 0x00000000 , PFN=1 为 0x00001000 ,依此类推。 20 个位足够转换整个 4GB 地址空间。 PDBR 中的 PFN 是物理页的索引,用来控制整个页目录。 PFN 中剩余的位大多数都被保留,但 3 号位例外,它用来控制页一级的 write-through ( page-level write-through, PWT ), 4 号位如果为 1 ,则禁止页一级的高速缓冲。
l X86_PDE_4M 和 X86_PDE_4K 是页目录项( PDE )的两个可选方案,用来选择 4MB 页或者 4KB 的页。一个页目录中最多包含 1024 个 PDE 。 PFN 是页帧号,它指向下一级的页。对于一个 4MB 的 PDE ,其 PFN 位域仅有 10 个位的宽度,可寻址一个 4MB 的数据页。 4KB 的 PDE 拥有 20 位的 PFN ,可指向一个页表,由页表最终选择一个数据页。剩余的位用来定义多种属性。这些属性中最有趣的是“页大小”位 PS ,用于控制页的大小( 0=4KB , 1=4MB )和“存在”位 P ,标识下属的数据页( 4MB 模式)或页表( 4KB 模式)是否存在于物理内存中。
X86_PTE_4K 定义了页表项(属于一个页表)的内部结构。和页目录类似,一个页表可拥有 1024 个项。 X86_PTE_4K 和 X86_PDE_4K 的不同之处为:前者没有 PS 位,这根本不需要,因为页的大小肯定是 4KB 。需要注意的是,没有所谓的 4MB 的 PTE ,因为采用 4MB 页的内存模式不需要页表这一中间层。
X86_PNPE 代表一个“不存在的页”项( page-not-present entry, PNPE ),也就是说,一个 PDE 或 PTE 中的 P 位为 0 。如 Intel 的手册所说的,保留的第 31 位是“对操作系统或执行体( executive )均可用”( Intel 1999c,pp. 3-28 )。如果一个线性地址映射到了一个 PNPE ,这意味着这个地址或者还未使用或者它所指向的页已经被置换到了页面文件中。 Windows 2000 使用 PNPE 保留的第 31 位来存储页的信息。有关页信息的结构没有文档记载,不过它类似于名为 PageFile 的第 10 位,如 列表 4-3 所示,如果设置了该位,则表示页已被置换出物理内存。在这种情况下, Reserved1 和 Reserved2 位域将包含系统在页面文件中定位该页的信息,因此,当需要访问该页时,可很快的将其换回物理内存。
X86_PE 该结构是为了方便使用而加入的。它仅包含一个 union ,该 union 包括页项所有可能的状态,此处的页项是指: PDBR 的内容、所有 4MB 和 4KB 的 PDE 、 PTE ,以及所有的 PNPE 。
typedef struct _X86_PDBR // page-directory base register (cr3)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned Reserved1 : 3;
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned Reserved2 : 7;
unsigned PFN : 20; // page-frame number
};
};
}
X86_PDBR, *PX86_PDBR, **PPX86_PDBR;
#define X86_PDBR_ sizeof (X86_PDBR)
// -----------------------------------------------------------------
typedef struct _X86_PDE_4M // page-directory entry (4-MB page)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (1 = present)
unsigned RW : 1; // read/write
unsigned US : 1; // user/supervisor
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned A : 1; // accessed
unsigned D : 1; // dirty
unsigned PS : 1; // page size (1 = 4-MB page)
unsigned G : 1; // global page
unsigned Available : 3; // available to programmer
unsigned Reserved : 10;
unsigned PFN : 10; // page-frame number
};
};
}
X86_PDE_4M, *PX86_PDE_4M, **PPX86_PDE_4M;
#define X86_PDE_4M_ sizeof (X86_PDE_4M)
// -----------------------------------------------------------------
typedef struct _X86_PDE_4K // page-directory entry (4-KB page)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (1 = present)
unsigned RW : 1; // read/write
unsigned US : 1; // user/supervisor
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned A : 1; // accessed
unsigned Reserved : 1; // dirty
unsigned PS : 1; // page size (0 = 4-KB page)
unsigned G : 1; // global page
unsigned Available : 3; // available to programmer
unsigned PFN : 20; // page-frame number
};
};
}
X86_PDE_4K, *PX86_PDE_4K, **PPX86_PDE_4K;