第四章 探索Windows 2000的内存管理机制
翻译:Kendiv( 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;
#define X86_PDE_4K_ sizeof (X86_PDE_4K)
// -----------------------------------------------------------------
typedef struct _X86_PTE_4K // page-table 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 D : 1; // dirty
unsigned Reserved : 1;
unsigned G : 1; // global page
unsigned Available : 3; // available to programmer
unsigned PFN : 20; // page-frame number
};
};
}
X86_PTE_4K, *PX86_PTE_4K, **PPX86_PTE_4K;
#define X86_PTE_4K_ sizeof (X86_PTE_4K)
// -----------------------------------------------------------------
typedef struct _X86_PNPE // page not present entry
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (0 = not present)
unsigned Reserved1 : 9;
unsigned PageFile : 1; // page swapped to pagefile
unsigned Reserved2 : 21;
};
};
}
X86_PNPE, *PX86_PNPE, **PPX86_PNPE;
#define X86_PNPE_ sizeof (X86_PNPE)
// -----------------------------------------------------------------
typedef struct _X86_PE // general page entry
{
union
{
DWORD dValue; // packed value
X86_PDBR pdbr; // page-directory Base Register
X86_PDE_4M pde4M; // page-directory entry (4-MB page)
X86_PDE_4K pde4K; // page-directory entry (4-KB page)
X86_PTE_4K pte4K; // page-table entry (4-KB page)
X86_PNPE pnpe; // page not present entry
};
}
X86_PE, *PX86_PE, **PPX86_PE;
#define X86_PE_ sizeof (X86_PE)
列表4-3. i386 的PDBR、PDE、PTE和PNPE
在列表4-4中,我增加了线性地址的结构化表示。这些结构是图4-3和4-4中的“线性地址”的正式形式。
l X86_LINEAR_4M 该结构是指向4MB数据页的线性地址的正式形式,如图4-4所示。页目录索引(PDI)是一个页目录的索引,页目录地址由PDBR给出,使用PDI可选择页目录中的一个PDE。22位的Offset成员指向一个目标地址,此目标地址对应4MB的物理页。
l X86_LINEAR_4K 是一个4KB线性地址类型的变量,如图4-3所示。该结构由三个位域组成:和4MB地址类似,高10位为PDI,用来选择一个PDE;页表索引PTI的任务与PDI相似,指向由PDE(该PDE由前面的PDI指定)确定的页表中的一个PTE;剩余的12个位是在4KB物理页中的偏移量。
l X86_LINEAR 是另一个为使用方便而加入的结构。该结构只是简单的将X86_LINEAR_4K和 X86_LINEAR_4M联合为一个数据类型。详见列表4-4。
typedef struct _X86_LINEAR_4M // linear address (4-MB page)
{
union
{
struct
{
PVOID pAddress; // packed address
};
struct
{
unsigned Offset : 22; // offset into page
unsigned PDI : 10; // page-directory index
};
};
}
X86_LINEAR_4M, *PX86_LINEAR_4M, **PPX86_LINEAR_4M;
#define X86_LINEAR_4M_ sizeof (X86_LINEAR_4M)
// -----------------------------------------------------------------
typedef struct _X86_LINEAR_4K // linear address (4-KB page)
{
union
{
struct
{
PVOID pAddress; // packed address
};
struct
{
unsigned Offset : 12; // offset into page
unsigned PTI : 10; // page-table index
unsigned PDI : 10; // page-directory index
};
};
}
X86_LINEAR_4K, *PX86_LINEAR_4K, **PPX86_LINEAR_4K;
#define X86_LINEAR_4K_ sizeof (X86_LINEAR_4K)
// -----------------------------------------------------------------
typedef struct _X86_LINEAR // general linear address
{
union
{
PVOID pAddress; // packed address
X86_LINEAR_4M linear4M; // linear address (4-MB page)
X86_LINEAR_4K linear4K; // linear address (4-KB page)
};
}
X86_LINEAR, *PX86_LINEAR, **PPX86_LINEAR;
#define X86_LINEAR_ sizeof (X86_LINEAR)
列表4-4. i386的线性地址
宏和常量
列表4-5给出的定义是对列表4-2到列表4-4所示结构的补充,让我们可以更容易的和i386内存管理一起工作。列表4-5的定义可以分为三大组。第一组用于控制线性地址:
1. X86_PAGE_MASK、X86_PDI_MASK和X86_PTI_MASK 都是位掩码(bit mask),用来选择线性地址中的某一部分。它们都基于常量:PAGE_SHIFT (12)、PDI-SHIFT (22)和PTI-SHIFT (12),这些常量定义于Windows 2000 DDK的头文件ntddk.h中。X86_PAGE_MASK等价于0xFFFFF000,可有效的屏蔽4KB线性地址(X86_LINEAR_4K)中的偏移量部分。X86_PDI_MASK等价于0xFFC00000,显然这可从线性地址中提取高10位的PDI。X86_PTI_MASK等价于0x003FF0000,用于屏蔽线性地址中除PTI外的所有位。
2. X86_PAGE()、X86_PDI()和X86_PTI() 使用上面的常量来计算给定线性地址的页索引、PDI和PTI。X86_PAGE()一般用来从Windows 2000的PTE数组(该数组首地址为:0xC0000000)中读取一个PTE。X86_PDI()和X86_PTI()只是针对给定的指针,简单的使用X86_PDI_MASK或X86_PTI_MASK,并将得到的索引移动到最右边。
3. X86_OFFSET_4M()和X86_OFFSET_4K() 分别从4MB或4KB线性地址中提取偏移量部分。
4. X86_PAGE_4M和X86_PAGE_4K 根据DDK中的常量PDI_SHIFT和PTI_SHIFT来计算4MB和4KB页的大小。X86_PAGE_4M=4,194,304,X86_PAGE_4K=4,096。注意,X86_PAGE_4K等价于DDK常量PAGE_SIZE,该常量也定义于ntddk.h中。
5. X86_PAGES_4M和X86_PAGES_4K 分别表示4GB地址空间中可容纳的4MB或4KB页的总数。X86_PAGES_4M等价于1,024,X86_PAGES_4K等价于1,048,576。
#define X86_PAGE_MASK (0 - (1 << PAGE_SHIFT))
#define X86_PAGE(_p) (((DWORD) (_p) & X86_PAGE_MASK) >> PAGE_SHIFT)
#define X86_PDI_MASK (0 - (1 << PDI_SHIFT))
#define X86_PDI(_p) (((DWORD) (_p) & X86_PDI_MASK) >> PDI_SHIFT)
#define X86_PTI_MASK ((0 - (1 << PTI_SHIFT)) & ~X86_PDI_MASK)
#define X86_PTI(_p) (((DWORD) (_p) & X86_PTI_MASK) >> PTI_SHIFT)
#define X86_OFFSET(_p,_m) ((DWORD_PTR) (_p) & ~(_m))
#define X86_OFFSET_4M(_p) X86_OFFSET (_p, X86_PDI_MASK)
#define X86_OFFSET_4K(_p) X86_OFFSET (_p, X86_PDI_MASK|X86_PTI_MASK)
#define X86_PAGE_4M (1 << PDI_SHIFT)
#define X86_PAGE_4K (1 << PTI_SHIFT)
#define X86_PAGES_4M (1 << (32 - PDI_SHIFT))
#define X86_PAGES_4K (1 << (32 - PTI_SHIFT))
// -----------------------------------------------------------------
#define X86_PAGES 0xC0000000
#define X86_PTE_ARRAY ((PX86_PE) X86_PAGES)
#define X86_PDE_ARRAY (X86_PTE_ARRAY + (X86_PAGES >> PTI_SHIFT))
// -----------------------------------------------------------------
#define X86_SEGMENT_OTHER 0
#define X86_SEGMENT_CS 1
#define X86_SEGMENT_DS 2
#define X86_SEGMENT_ES 3
#define X86_SEGMENT_FS 4
#define X86_SEGMENT_GS 5
#define X86_SEGMENT_SS 6
#define X86_SEGMENT_TSS 7
// -----------------------------------------------------------------
#define X86_SELECTOR_RPL 0x0003
#define X86_SELECTOR_TI 0x0004
#define X86_SELECTOR_INDEX 0xFFF8
#define X86_SELECTOR_SHIFT 3
#define X86_SELECTOR_LIMIT (X86_SELECTOR_INDEX >>
X86_SELECTOR_SHIFT)
// -----------------------------------------------------------------
#define X86_DESCRIPTOR_SYS_TSS16A 0x1
#define X86_DESCRIPTOR_SYS_LDT 0x2
#define X86_DESCRIPTOR_SYS_TSS16B 0x3
#define X86_DESCRIPTOR_SYS_CALL16 0x4
#define X86_DESCRIPTOR_SYS_TASK 0x5
#define X86_DESCRIPTOR_SYS_INT16 0x6
#define X86_DESCRIPTOR_SYS_TRAP16 0x7
#define X86_DESCRIPTOR_SYS_TSS32A 0x9
#define X86_DESCRIPTOR_SYS_TSS32B 0xB
#define X86_DESCRIPTOR_SYS_CALL32 0xC
#define X86_DESCRIPTOR_SYS_INT32 0xE
#define X86_DESCRIPTOR_SYS_TRAP32 0xF
// -----------------------------------------------------------------
#define X86_DESCRIPTOR_APP_ACCESSED 0x1
#define X86_DESCRIPTOR_APP_READ_WRITE 0x2
#define X86_DESCRIPTOR_APP_EXECUTE_READ 0x2
#define X86_DESCRIPTOR_APP_EXPAND_DOWN 0x4
#define X86_DESCRIPTOR_APP_CONFORMING 0x4
#define X86_DESCRIPTOR_APP_CODE 0x8
列表4-5. 附加的i386内存管理相关定义
第二组宏和常量与Windows 2000的PDE、PTE数组有关。和其他几个系统地址不同,这些数组的基地址并没有在系统启动时作为一个全局变量出现,而是被定义成了一个常量。可以通过反编译内存管理API函数:MmGetPhysicalAddress()和MmIsAddressValid()来证明,在这些函数里,这些地址都以“魔术数字”的形式出现。这些常量并没有包括在DDK头文件中,不过列表4-5展示了如何定义它们。
l X86_PAGES 是一个硬编码的地址和指针(指向0xC0000000),0xC0000000是Windows 2000的PTE数组开始的地方。
X86_PTE_ARRAY 等价于X86_PAGES,但是被转型为PX86_PE,也就是说,指向一个X86_PE类型的数组,X86_PE定义于列表4-2。
X86_PDE_ARRAY 是一个巧妙的定义,它通过PTE数组的位置来计算PDE数组的基地址,这需要用到PTI_SHIFT常量。将线性地址映射为PTE地址的通用格式为:((LinearAdress >> 12)*4)+0xC0000000,线性地址0xC0000000转换后的地址为页目录的基地址。
列表4-5的最后两部分包括选择器和特殊类型的描述符,以及对列表4-2的补充。
l X86_SELECTOR_RPL、X86_SELECTOR_TI和X86_SELECTOR_INDEX 都是位掩码,分别对应X86_SELECTOR结构中的RPL、TI和Index成员。
l X86_SELECTOR_SHIFT 是一个右移因子,用来使选择器的Index的数值向右对齐。
l X86_SELECTOR_LIMIT 定义了选择器可使用的最大索引值,该限制为8,191。这个值确定了描述符表的最大尺寸。每个选择器索引均指向一个描述符,每个描述符包含64个位(即8个字节)。所以,描述符表的最大尺寸为:8,192*8=64KB。
l X86_DESCRIPTOR_SYS_* 是一组常量,用于定义系统描述符类型。如果描述符的S位被设为0,那么描述的Type成员将采用这一组类型中的某一个。请参考列表4-2中的X86_DESCRIPTOR的定义。系统描述符类型在Intel手册中有详细介绍(Intel 1999c, pp. 3-15f),表4-1给出了所有可用的系统描述符类型。
列表4-5中的X86_DESCRIPTOR_APP_*常量也可用于定义描述符的Type成员,前提是描述符的S位不为0。此时,该应用程序描述符可能需要引用一个代码或数据段。因为应用程序描述符类型的属性受Type域的第四个位影响,所以X86_DESCRIPTOR_APP_*常量被定义为单位掩码(single-bit mask),这样一些位就可针对数据和代码段有不同的解释。
l X86_DESCRIPTOR_APP_ACCESSED 如果一个段可以被访问,则采用
l X86_DESCRIPTOR_APP_READ_WRITE 决定一个数据段是否允许只读或读/写访问。
l X86_DESCRIPTOR_APP_CONFORMATING 说明一个代码段是否相匹配。也就是说,它是否可以被以被弱特权代码(less privileged code)调用(参考 Intel 1999c,pp. 4-13ff)。
l X86_DESCRIPTOR_APP_CODE 用来区别代码段和数据段。注意,堆栈属于数据段的范畴,而且必须总是可写的。
稍后,当下一章中的Memory Spy程序开始运行时,我们将重温系统描述符。表4-1算是i386内存管理的一个简短总结。有关本话题的更多内容,请参考Intel Pentium手册(Intel 1999a,1999b,1999c)。
表4-1. 系统描述符类型
名 称
值
描 述
X86_DESCRIPTOR_SYS_TSS16A
0x1
16位任务状态段(可用)
X86_DESCRIPTOR_SYS_LDT
0x2
本地描述符表(LDT)
X86_DESCRIPTOR_SYS_TSS16B
0x3
16位任务状态段(繁忙)
X86_DESCRIPTOR_SYS_CALL16
0x4
16位调用门
X86_DESCRIPTOR_SYS_TASK
0x5
任务门
X86_DESCRIPTOR_SYS_INT16
0x6
16位中断门
X86_DESCRIPTOR_SYS_TRAP16
0x7
16位陷阱门
X86_DESCRIPTOR_SYS_TSS32A
0x9
32位任务状态段(可用)
X86_DESCRIPTOR_SYS_TSS32B
0xB
32位任务状态段(繁忙)
X86_DESCRIPTOR_SYS_CALL32
0xC
32位调用门
X86_DESCRIPTOR_SYS_INT32
0xE
32位中断门
X86_DESCRIPTOR_SYS_TRAP32
0XF
32位陷阱门
………………..待续…………………..