RayCommand操作系统的实现笔记3--GDT的介绍
来源:互联网 发布:网络手游 编辑:程序博客网 时间:2024/06/07 05:29
GDT是X86上操作系统的一个最基础的问题。这个文章只在介绍GDT的基本知识。并没有任何一个RayCommand版本对应这一段东西。因为实在是太基础了,我也不想单独拿这个作为一个Milestone。但是,下文中介绍的任何实现,均在RayCommand的最新版本中/kernel/driver/x86arch/GDT中,有对应的实现。本文主体翻译自这里。但是有一些自己的改变。如果想看原文,请参考英文版。
在Intel X86架构上,有很多保护内存访问的方法,使得用户程序禁止访问内核程序的内存,或者其他程序的内存。其中一个重要的方法是使用全局描述符表(Global Descriptor Table), 也就是GDT。GDT定义了某一段特定内存的权限。我们可以使用GDT中的一个字段,定义某一段内存不能被出了内核程序之外的程序访问。现代操作系统使用的是"分页"技术实现这一点。使用分页技术会具有更大的灵活性。GDT基本上可以说是段式的内存,但是X86平台中,必须设置GDT,也算是一个历史遗留的问题。在GDT中还可以设置"任务状态段"(Task State Segments, Tss)。这个TSS段可以进行硬件的任务切换,但是不再本篇文章的讨论范围中。并且,TSS也不是唯一的多任务的方法。
值得注意的是Grub在加载系统的时候,已经载入了默认的GDT.但是,如果你对GRUB GDT的内存区域进行复写,会导致GDT的失效,引发一个'triple fault'异常。如果要解决这个问题,我们需要自己建立GDT,并将GDT放到一个可控的不会复写的内存中。再载入自己的GDT。载入后,再将cs,ds,es等段寄存器,设置成GDT中对应字段的偏移。例如,cs中为代码段的偏移。如果GDT中,描述代码段的内存属性,位于第0x10处偏移的话,则将cs设置成0x10.(抑或,某些书上翻译的叫做段选择子,但我实际上觉得,这个东西只是简单的偏移而已,说的那么复杂会让他人产生困惑)
GDT本身是一个数组。它内部的每一个元素都是一个64位长的字段(原文为Entry,但是我觉得字段的意思更明确,当然实际上字段应该是Field,Entry应该是入口点)。每一个字段设置了一段内存的属性,权限等等。一个通常的规范是,GDT的第0个字段,应该是个NULL字段,也就是全为0的字段。没有任何一个CS,ES这样的段寄存器应该设置为0。由于GDT设置了权限,在越权访问的情况下,CPU会产生一个"General Protection"异常。
GDT中的每一个字段,同时说明了这段内存运行在什么状态下。究竟是运行在内核空间(Ring 0)还是用户空间(Ring 3)。当然,X86系统还有其他的Ring,但是那些大多数情况都不会用到。在Ring 3中,程序被限制只能执行一些基础的命令。例如在用户状态下,就不可以关中断。这实际上是对操作系统内核程序的一种保护。
在每一个GDT的字段中,均有一些基地址,偏移地址,和一些属性为,他们不是依次排列的,其内存中状态如下图所示。
在每一个GDT的字段中,有几位说明了他的权限和访问情况。如下图所示。
上图中,每一个位代表的意思如下。
- Pr: Present Bit,当前是否在内存中的标志位。对于任何一个有效的代码段,都必须是1.
- Privl: Privilege, 特权位,两位。标志着这段的Ring等级。最高级为Ring0(内核状态),最低级为Ring3(用户状态)。
- Ex: Executable,是否可执行位。如果为1,则该段内存为可执行的代码,即代码段。否则为数据段。
- DC: Direction bit/Conforming bit, 方向或一致性位。
- 如果该段为代码段,则位表示方向,如果是0,则该段是向上增长的,反之则是向下增长的。换句话说,向下增长意味着偏移地址要大于基地址。
- 如果该段为数据段,则该位表示一致性。即地位的代码段是否能够方位该数据段。
- RW: Readable/Writeable位。如果该段为1,则对应代码段的话为可读,对应的数据段可写。注意的是,代码段永远是不可写的,数据段永远是可读的。
- Ac:Accessed bit. 当CPU访问过的时候,设置这位为1,当然,初始化的时候,我们需要将这位设置为0.
- Gr:Granularity bit.粒度位。如果是0,则表示在这个GDT中,任何地址单位都为Byte。如果是1,则表示其单位为4KB
- Sz:Operand Size bit. 如果为0,该段为16位的段,也就是IP每次会取16位指令。为1,则为32位的段。
下面是一些示例代码,在操作系统中载入三个GDT字段。为什么是3个呢?和开始说的一样,第0个为NULL的字段,再加上一个数据段一个代码段正好三段。当我们准备好这三个字段组成的数组后,我们需要一个新的数据结构去加载它,这个数据结构叫做GDT的指针,是个48位的地址,里面包括GDT的内存地址和GDT的长度。
在GDT.c中,我们定义了GDT的一些数据结构和数据。
/* Defines a GDT entry. We say packed, because it prevents the* compiler from doing things that it thinks is best: Prevent* compiler "optimization" by packing */struct gdt_entry{ unsigned short limit_low; unsigned short base_low; unsigned char base_middle; unsigned char access; unsigned char granularity; unsigned char base_high;} __attribute__((packed));/* Special pointer which includes the limit: The max bytes* taken up by the GDT, minus 1. Again, this NEEDS to be packed */struct gdt_ptr{ unsigned short limit; unsigned int base;} __attribute__((packed));/* Our GDT, with 3 entries, and finally our special GDT pointer */struct gdt_entry gdt[3];struct gdt_ptr gp;/* This will be a function in start.asm. We use this to properly* reload the new segment registers */extern void gdt_flush();/* Setup a descriptor in the Global Descriptor Table */void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran){ /* Setup the descriptor base address */ gdt[num].base_low = (base & 0xFFFF); gdt[num].base_middle = (base >> 16) & 0xFF; gdt[num].base_high = (base >> 24) & 0xFF; /* Setup the descriptor limits */ gdt[num].limit_low = (limit & 0xFFFF); gdt[num].granularity = ((limit >> 16) & 0x0F); /* Finally, set up the granularity and access flags */ gdt[num].granularity |= (gran & 0xF0); gdt[num].access = access;}/* Should be called by main. This will setup the special GDT* pointer, set up the first 3 entries in our GDT, and then* finally call gdt_flush() in our assembler file in order* to tell the processor where the new GDT is and update the* new segment registers */void gdt_install(){ /* Setup the GDT pointer and limit */ gp.limit = (sizeof(struct gdt_entry) * 3) - 1; gp.base = &gdt; /* Our NULL descriptor */ gdt_set_gate(0, 0, 0, 0, 0); /* The second entry is our Code Segment. The base address * is 0, the limit is 4GBytes, it uses 4KByte granularity, * uses 32-bit opcodes, and is a Code Segment descriptor. * Please check the table above in the tutorial in order * to see exactly what each value means */ gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); /* The third entry is our Data Segment. It's EXACTLY the * same as our code segment, but the descriptor type in * this entry's access byte says it's a Data Segment */ gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); /* Flush out the old GDT and install the new changes! */ gdt_flush();}
在上述代码中,GDTInstall为安装GDT的过程,每个gdt_set_gate是设置GDT中每个字段的函数。具体的设置方法是根据GDT
; This will set up our new segment registers. We need to do; something special in order to set CS. We do what is called a; far jump. A jump that includes a segment as well as an offset.; This is declared in C as 'extern void gdt_flush();'global _gdt_flush ; Allows the C code to link to thisextern _gp ; Says that '_gp' is in another file_gdt_flush: lgdt [_gp] ; Load the GDT with our '_gp' which is a special pointer mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax jmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump!flush2: ret ; Returns back to the C code!
的数据结构设置。最开始设置了一个NULL字段,之后设置的是代码段,最后设置的是数据段。细心的朋友会发现,GDTFlush并没有定义,这个函数的目的,是将新设置好的GDT加载给CPU。这段函数使用汇编进行书写。单独文件GDT_ASM.S,汇编器为NASM。代码如下:
由此,我们加载完了新的GDT,在汇编中,将数据段(0x10,因为是GDT中第三个字段,每个字段长64bit,也就是长0x08。第三个为0x08*(3-2) = 0x10 )设置给了ds,es等等。又使用jmp,将代码段0x08设置给了cs。
现在,只要在Main函数中调用GDTInstall,即可完成GDT的设置。
- RayCommand操作系统的实现笔记3--GDT的介绍
- RayCommand操作系统的实现笔记1--BareBone!把架子搭起来
- RayCommand操作系统的实现笔记0--设计想法与编写目的
- RayCommand操作系统的实现笔记2--完成在保护模式Text Mode下,输出字符
- 《一个操作系统的实现》笔记一,GDT(全局描述符)
- 一个操作系统的实现 分段机制 GDT LDT
- 【从头开始写操作系统系列】实现一个 GDT(3)
- 1个人开发操作系统之GDT和IDT的初始化
- 《一个操作系统的实现》学习笔记3
- 一个操作系统的实现笔记
- 一步一步实现一个简单的OS(初始化GDT)
- 操作系统——保护模式下的GDT表,地址的映射
- GDT和IDT的初始化
- GDT是如何切换的
- GDT和IDT的初始化
- GDT和LDT的关系
- 【从头开始写操作系统系列】实现一个-GDT(1)
- 【从头开始写操作系统系列】实现一个-GDT(2)
- VC编译选项里面如何增加 win32 unicode release项
- 简单的多任务操作系统
- 再谈三范式
- jdk7正则表达式-命名捕获组(named capture)
- 如何在Linux内核里增加一个系统调用
- RayCommand操作系统的实现笔记3--GDT的介绍
- MySQL的mysqldump工具的基本用法
- TCP/IP编程示例
- windows socket api 函数
- C 回调函数
- 第二周测试赛
- C++ 一条代码打印vector内容以及random_shuffle函数
- asp.net 中 mdf 数据库文件的使用
- Fedora 构建Samba