查看“GDT Tutorial”的源代码
←
GDT Tutorial
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
{{Rating|1}} 在 [[:Category:x86 | Intel Architecture]] 中,更确切地说,在 [[protected mode]] 中,大多数 [[memory management]] 和 [[Interrupt Service Routines]] 都是通过描述符表来控制的。 每个描述符存储关于CPU在某个时间可能需要的单个对象 (例如,服务例程、任务、代码或数据块,无论什么) 的信息。 例如,如果您尝试将新值加载到 [[Segment | 段寄存器]] 中,则CPU需要执行安全和访问控制检查,以查看您是否实际上有权访问该特定内存区域。 一旦执行了检查,有用的值 (例如最低和最高地址) 就会缓存在CPU的不可见寄存器中。 英特尔定义了3种类型的表: [[Interrupt Descriptor Table]] (取代 [[IVT]]) 、全局描述符表 ([[GDT]]) 和本地描述符表。 每个表分别通过 <tt>LIDT</tt>,<tt>LGDT</tt>,<tt>LLDT</tt> 指令定义为 (大小,[[线性地址]]) 到CPU。 在大多数情况下,操作系统只是在启动时告诉这些表的位置,然后简单地通过指针写入/读取表。 == 词汇表 == ; Segment : 具有一致属性的逻辑上连续的内存块 (CPU的说法) ; Segment Register : 您的CPU的寄存器,它引用了用于特殊用途的段 (例如 <tt>SS</tt>,<tt>CS</tt>,<tt>DS</tt> ...) ; Selector : 对可以加载到段寄存器中的描述符的引用; 选择器是描述符表项的偏移量。 这些条目有8个字节长。 因此,位3及以上仅声明描述符表条目偏移,而位2指定此选择器是GDT还是LDT选择器 (LDT位设置,GDT位清除),和位0-1声明需要对应描述符表项的DPL字段的Ring级。 如果没有,则发生一般保护故障; 如果确实对应,则相应地更改所用选择器的CPL级别。 ; Descriptor : 告诉CPU给定段的属性的内存结构 (表的一部分) = = 在GDT里放什么 = = === 基础 === 出于理智的目的,您应该始终将这些项目存储在GDT中: * 处理器从未引用的空描述符。 某些仿真器 (例如Bochs) 会抱怨如果您没有出现限制异常。 有些使用此描述符来存储指向GDT本身的指针 (与LGDT指令一起使用)。 空描述符是8字节宽,指针是6字节宽,所以它可能只是这个完美的地方。 * 一个代码段描述符 (对于您的内核,它应该具有type = 0x9A) * 数据段描述符 (您不能写入代码段,因此请使用type = 0x92添加它) * [[TSS]] 段描述符 (相信我,至少保留一个位置) * 如果需要,可以容纳更多细分段 (例如用户级别,[[LDT]],更多ts,等等) === Sysenter/Sysexit === 如果您使用的是Intel <tt>SYSENTER</tt>/<tt>SYSEXIT</tt> 例程,则GDT的结构必须如下: * 前面的任何描述符 (空描述符、特殊内核的东西等) * 一个 [[DPL]] 0代码段描述符 (<tt>SYSENTER</tt> 将使用的那个) * 一个DPL 0数据段描述符 (用于 <tt>SYSENTER</tt> 堆栈) * DPL 3代码段 (用于 <tt>SYSEXIT</tt> 之后的代码) * DPL 3数据段 (用于 <tt>SYSEXIT</tt> 之后的用户模式堆栈) * 任何其他描述符 DPL 0代码段的段被加载到 [[MSR]] 中。 其他的是根据该值计算的。 有关更多信息,请参阅 <tt>SYSENTER</tt> 和 <tt>SYSEXIT</tt> 的英特尔说明参考。 您存储在那里的实际值将取决于您的系统设计。 === Flat设置 === 您想要完整的4个GiB地址未翻译: 只需使用 <source lang="c"> GDT[0] = {.base=0, .limit=0, .type=0}; // Selector 0x00 cannot be used GDT[1] = {.base=0, .limit=0xffffffff, .type=0x9A}; // Selector 0x08 will be our code GDT[2] = {.base=0, .limit=0xffffffff, .type=0x92}; // Selector 0x10 will be our data GDT[3] = {.base=&myTss, .limit=sizeof(myTss), .type=0x89}; // You can use LTR(0x18) </source> 请注意,在此模型中,由于代码和数据段重叠,因此实际上并未保护代码免受覆盖。 = = 小内核设置 = = 如果您希望 (出于特定原因) 将代码和数据清楚地分开 (假设两者都有4 MiB,也从4 MiB开始),只需使用: <source lang="c"> GDT[0] = {.base=0, .limit=0, .type=0}; // Selector 0x00 cannot be used GDT[1] = {.base=0x400000, .limit=0x3fffff, .type=0x9A}; // Selector 0x08 will be our code GDT[2] = {.base=0x800000, .limit=0x3fffff, .type=0x92}; // Selector 0x10 will be our data GDT[3] = {.base=&myTss, .limit=sizeof(myTss), .type=0x89}; // You can use LTR(0x18) </source> 这意味着您在物理地址4 MiB加载的任何内容都将在 <tt>CS:0</tt> 处显示为代码,而您在物理地址8 MiB加载的内容将在 <tt>DS:0</tt> 处显示为数据。 然而,这可能不是最好的设计。 = = 怎么做 = = === 禁用中断 === 如果启用了它们,请将其关闭,否则您将遇到麻烦。 === 填表 === <tt>GDT[]</tt> 的上述结构并不完整。 对于向后兼容286的GDT,描述符的实际结构有点混乱。 基址在3个不同的字段上分割,您不能编码任何您想要的限制。 另外,在这里和那里,如果你想让事情正常工作,你需要正确设置这些标志。 <source lang="c"> /** * \param target A pointer to the 8-byte GDT entry * \param source An arbitrary structure describing the GDT entry */ void encodeGdtEntry(uint8_t *target, struct GDT source) { // Check the limit to make sure that it can be encoded if ((source.limit > 65536) && ((source.limit & 0xFFF) != 0xFFF)) { kerror("You can't do that!"); } if (source.limit > 65536) { // Adjust granularity if required source.limit = source.limit >> 12; target[6] = 0xC0; } else { target[6] = 0x40; } // Encode the limit target[0] = source.limit & 0xFF; target[1] = (source.limit >> 8) & 0xFF; target[6] |= (source.limit >> 16) & 0xF; // Encode the base target[2] = source.base & 0xFF; target[3] = (source.base >> 8) & 0xFF; target[4] = (source.base >> 16) & 0xFF; target[7] = (source.base >> 24) & 0xFF; // And... Type target[5] = source.type; } </source> 当然,您可以对其进行硬编码,而不是在运行时将其转换。 此代码假定您只需要32位段。 === 告诉CPU表的位置 === 这里需要一些汇编示例。 虽然您可以使用 [[内联程序集]],但 <tt>LGDT</tt> 和 <tt>LIDT</tt> 所期望的内存打包使编写小型程序集例程变得更加容易。 如上所述,您将使用 <tt>LGDT</tt> 指令加载基地址和GDT的限制。 由于基址应该是一个线性地址,因此您需要根据当前的 [[MMU]] 设置进行一些调整。 ==== 从实模 ==== 此处的线性地址应计算为 <tt> 段 * 16偏移 </tt>。 <tt>GDT</tt> 和 <tt>GDT_end</tt> 被假定为当前数据段中的符号。 <source lang="asm"> gdtr DW 0 ; For limit storage DD 0 ; For base storage setGdt: XOR EAX, EAX MOV AX, DS SHL EAX, 4 ADD EAX, ''GDT'' MOV [gdtr + 2], eax MOV EAX, ''GDT_end'' SUB EAX, ''GDT'' MOV [gdtr], AX LGDT [gdtr] RET </source> ==== 从平面,保护模式 ==== “Flat” 表示数据段的基数为0 (无论分页是打开还是关闭)。 例如,如果您只是被 [[GRUB]] 引导,情况就是这样。 您应该将其称为 <tt>setGdt(GDT,sizeof(GDT))</tt>。 <source lang="asm"> gdtr DW 0 ; For limit storage DD 0 ; For base storage setGdt: MOV EAX, [esp + 4] MOV [gdtr + 2], EAX MOV AX, [ESP + 8] DEC AX MOV [gdtr], AX LGDT [gdtr] RET </source> ==== 来自非flat保护模式 ==== 如果您的数据段具有非零基数 (例如,您在分段技巧中使用的是 [[较高的半内核]]), 您必须在上面序列的 “<tt>MOV EAX,...</tt>” 和 “<tt>MOV...,EAX</tt>” 指令之间 “<tt> 添加EAX,base_of_your_data_segment_which_you_should_know</tt>”。 === 重新加载段寄存器 === 无论您对GDT做什么,都不会对CPU产生影响,直到您将选择器加载到段寄存器中。 您可以使用以下方法执行此操作: <source lang="asm"> reloadSegments: ; Reload CS register containing code selector: JMP 0x08:.reload_CS ; 0x08 points at the new code selector .reload_CS: ; Reload data segment registers: MOV AX, 0x10 ; 0x10 points at the new data selector MOV DS, AX MOV ES, AX MOV FS, AX MOV GS, AX MOV SS, AX RET </source> 可以找到上述代码的解释 [http://stackoverflow.com/questions/23978486/far-jump-in-gdt-in-bootloader here]。 == LDT == 与GDT (全局描述符表) 非常相似,LDT (“本地” 描述符表) 包含用于内存段描述,调用门等的描述符。 LDT的好处是,每个任务都可以有自己的LDT,并且当您使用硬件任务切换时,处理器会自动切换到正确的LDT。 由于其内容在每个任务中可能不同,因此LDT不是放置系统内容 (例如TSS或其他LDT描述符) 的合适位置: 这些是GDT的唯一特别之处。 由于要经常更改,因此用于加载LDT的命令与GDT和IDT加载有点不同。 这些参数不是直接给出LDT的基地址和大小,而是存储在GDT的描述符中 (具有适当的 “LDT” 类型),并给出该条目的选择器。 GDTR (base + limit) +-- GDT ------------+ | | SELECTOR ---> [LDT descriptor ]----> LDTR (base + limit) | | +-- LDT ------------+ | | | | ... ... ... ... +-------------------+ +-------------------+ 请注意,使用386处理器,分页使LDT几乎过时,并且不再需要多个LDT描述符,因此您几乎可以安全地忽略LDT进行操作系统开发,除非您设计了许多不同的段来存储。 == The IDT and why it's needed == 如上所述,IDT (中断描述符表) 的加载方式与GDT大致相同,其结构大致相同,只是它只包含门而不包含段。 每个门给出了对一段代码 (代码段、权限级别和该段中的代码的偏移量) 的完整引用,该代码现在绑定到0和255之间的数字 (IDT中的时隙)。 IDT将是在内核序列中启用的第一件事,以便您可以捕获硬件异常,侦听外部事件等。 有关X86系列中断的更多信息,请参见 [[中断]]。 == 一些让你的生活变得轻松的东西 == 轻松创建GDT条目的工具。 <source lang="c"> // Used for creating GDT segment descriptors in 64-bit integer form. #include <stdio.h> #include <stdint.h> // Each define here is for a specific flag in the descriptor. // Refer to the intel documentation for a description of what each one does. #define SEG_DESCTYPE(x) ((x) << 0x04) // Descriptor type (0 for system, 1 for code/data) #define SEG_PRES(x) ((x) << 0x07) // Present #define SEG_SAVL(x) ((x) << 0x0C) // Available for system use #define SEG_LONG(x) ((x) << 0x0D) // Long mode #define SEG_SIZE(x) ((x) << 0x0E) // Size (0 for 16-bit, 1 for 32) #define SEG_GRAN(x) ((x) << 0x0F) // Granularity (0 for 1B - 1MB, 1 for 4KB - 4GB) #define SEG_PRIV(x) (((x) & 0x03) << 0x05) // Set privilege level (0 - 3) #define SEG_DATA_RD 0x00 // Read-Only #define SEG_DATA_RDA 0x01 // Read-Only, accessed #define SEG_DATA_RDWR 0x02 // Read/Write #define SEG_DATA_RDWRA 0x03 // Read/Write, accessed #define SEG_DATA_RDEXPD 0x04 // Read-Only, expand-down #define SEG_DATA_RDEXPDA 0x05 // Read-Only, expand-down, accessed #define SEG_DATA_RDWREXPD 0x06 // Read/Write, expand-down #define SEG_DATA_RDWREXPDA 0x07 // Read/Write, expand-down, accessed #define SEG_CODE_EX 0x08 // Execute-Only #define SEG_CODE_EXA 0x09 // Execute-Only, accessed #define SEG_CODE_EXRD 0x0A // Execute/Read #define SEG_CODE_EXRDA 0x0B // Execute/Read, accessed #define SEG_CODE_EXC 0x0C // Execute-Only, conforming #define SEG_CODE_EXCA 0x0D // Execute-Only, conforming, accessed #define SEG_CODE_EXRDC 0x0E // Execute/Read, conforming #define SEG_CODE_EXRDCA 0x0F // Execute/Read, conforming, accessed #define GDT_CODE_PL0 SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | \ SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | \ SEG_PRIV(0) | SEG_CODE_EXRD #define GDT_DATA_PL0 SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | \ SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | \ SEG_PRIV(0) | SEG_DATA_RDWR #define GDT_CODE_PL3 SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | \ SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | \ SEG_PRIV(3) | SEG_CODE_EXRD #define GDT_DATA_PL3 SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | \ SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | \ SEG_PRIV(3) | SEG_DATA_RDWR void create_descriptor(uint32_t base, uint32_t limit, uint16_t flag) { uint64_t descriptor; // Create the high 32 bit segment descriptor = limit & 0x000F0000; // set limit bits 19:16 descriptor |= (flag << 8) & 0x00F0FF00; // set type, p, dpl, s, g, d/b, l and avl fields descriptor |= (base >> 16) & 0x000000FF; // set base bits 23:16 descriptor |= base & 0xFF000000; // set base bits 31:24 // Shift by 32 to allow for low part of segment descriptor <<= 32; // Create the low 32 bit segment descriptor |= base << 16; // set base bits 15:0 descriptor |= limit & 0x0000FFFF; // set limit bits 15:0 printf("0x%.16llX\n", descriptor); } int main(void) { create_descriptor(0, 0, 0); create_descriptor(0, 0x000FFFFF, (GDT_CODE_PL0)); create_descriptor(0, 0x000FFFFF, (GDT_DATA_PL0)); create_descriptor(0, 0x000FFFFF, (GDT_CODE_PL3)); create_descriptor(0, 0x000FFFFF, (GDT_DATA_PL3)); return 0; } </source> == 另见 == === 文章 === * [[Global Descriptor Table]] * http://web.archive.org/web/20190424213806/http://www.osdever.net/tutorials/view/the-world-of-protected-mode - 如何在汇编程序中设置GDT === Threads === === External Links === [[Category:Tutorials]] [[Category:X86 CPU]]
本页使用的模板:
模板:Eq
(
查看源代码
)
模板:Eq1
(
查看源代码
)
模板:If
(
查看源代码
)
模板:Rating
(
查看源代码
)
模板:Show1
(
查看源代码
)
返回至“
GDT Tutorial
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息