【自制操作系统】(四)加载GDT

GDT简介

在intel 8086体系结构中,有6个段寄存器,CPU取址采用段:偏移模式。从80286开始,为描述不同的段结构,x86架构引入了GDT(Global Descriptor Table,全局描述符表)。GDT可以描述程序各段的组成结构,其中主要包含了基地址(base address)、大小(limit)、权限(privilege)等信息。值得一提的是,GDT中还可以包含LDT的描述符。LDT,即Local Descriptor Table(局部描述表),可以用来描述每个程序独立的局部段描述信息。

这种架构的出现,其实和当时操作系统的发展紧密相关。因为操作系统的实现,除了要依靠软件设计,还需要底层硬件对某些特性的支持。我们可以简单地看看GDT的结构,没必要进行深入的理解。因为GDT中某些丑陋的设计是出于对老平台的兼容以及对当时的操作系统的考量,所以我们没有必要将精力放在研究这些遗留问题之上。

万恶的“Backward Campability”,因此我们在实现操作系统时没有办法绕过GDT,因此我们必须对其有个简单的认识。

简要的理论知识(可跳过)

GDT描述符

GDT长度是可变的
因此我们需要告诉底层硬件,我们操作系统中的GDT位置及大小。为此x86体系定义了如下数据结构用以描述GDT信息(包括GDT的大小,以及存放GDT的线性地址)。

对上图需要进行简单的补充:Size为GDT所占的字节大小-1。因为size为两字节,所以GDT最大的大小为65535字节(供8192表项)。

x86体系架构,提供lgdt指令来加载gdt描述符。即lgdt [上图所示的表的地址]

GDT表项

GDT的每个表项,抽象地可以看成包含四个字段的数据结构:基地址(Base),大小(Limit),标志(Flag),访问信息(Access Byte)。

GDT中每个表项在内存中的实际布局如下图所示:

为什么描述段基地址的Base以及段大小的Limit字段会被拆成这种丑陋的结构?
因为需要兼容286架构,啊哈哈哈哈~。

Base和Limit字段很好理解,Flag和Access Byte字段如下图所示:

其中每个字段代表的意义在此不做展开,这里只指出一点:Privl字段描述了对应段的权限等级(Ring 0~3)。

LDT

LDT,Local Descriptor Table,局部描述符表。在分页机制出现以前的操作系统中并没有虚拟内存(Virtual Memory)这个概念。为了让不同程序的数据彼此互不干扰,x86架构引入了LDT概念,期望操作系统可以通过为不同的应用程序设置不同的LDT而隔离程序间的数据。程序在使用分段机制取址的时候,可是通过设置选择子(selector)的特定位而告诉CPU是从GDT还是从LDT中选择对应的段信息,如下图所示。更多的细节在此不做展开。

<a id="gdtend" name="gdtend"></a>

随着分页机制的提出,GDT所代表的分段机制逐渐废弃。对于现代操作系统而言,GDT的作用几乎只是用来改变当前CPU执行的特权级,并且改变CPU的特权级也只有这种方式。

用代码操作GDT

加载GDT描述符的任务,必须由汇编指令完成,因此我们实现了一个汇编方法_flush_gdt, 并将其暴露给C文件。注释虽然用英文写的,但是我相信大家应该能阅读。汇编代码使用AT&T语法,使用gas作为编译器。

使用汇编代码加载GDT

(完成这段代码花了我一个小时,泪奔~~。因为一个小小的typo导致debug了好久,各位可直接拿去使用)

#function for loading gdt
.global _flush_gdt
.type _flush_gdt, @function
_flush_gdt:
    movl 4(%esp), %eax #Get the first argument, which is the pointer to gdt descriptor

    lgdt (%eax) #load gdt descriptor

    movw $0x10, %ax #0x10 is the offset in the GDT to our data segment
    mov %ax, %ds #copy %ax to ds,es,fs,gs,ss
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs
    mov %ax, %ss

    ljmp $0x08,$flush_lable #Using a long jump to set %cs to 0x8

flush_lable:
    ret

使用C语言填充GDT表项

由于代码里都有很好的注释,我就不再做过多介绍了。

gdt.h

主要是定义了RING 0和RING 3下数据段和代码段的Flag与Access Byte字段。

#ifndef _KERNEL_GDT_H
#define _KERNEL_GDT_H

#include <stdint.h>

//Each define here is for a specific bit(bits) flag for a gdt entry
#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)

struct gdt_descriptor_struct{
    uint16_t limit;
    uint32_t base;
}
__attribute__((packed));

typedef struct gdt_descriptor_struct gdt_descriptor;
typedef uint64_t gdt_entry;

void init_gdt();

#endif

gdt.c

init_gdt()函数完成了对GDT的设置和加载。主要是设置了ring 0的代码段和数据段(各一个),ring 3的代码段和数据段(各一个)。最开始的空表项是硬件必须的(如果没有这项,bochs会报错)。

#include <kernel/gdt.h>

gdt_entry entrys[5];
gdt_descriptor gdtd;

extern void _flush_gdt(uint32_t gdtp);

static gdt_entry create_entry(uint32_t base, uint32_t limit, uint16_t flag){
  uint64_t entry ;
  // Create the high 32 bit segment
  entry =  limit       & 0x000F0000;         // set limit bits 19:16
  entry |= (flag <<  8) & 0x00F0FF00;         // set type, p, dpl, s, g, d/b, l and avl fields
  entry |= (base >> 16) & 0x000000FF;         // set base bits 23:16
  entry |=  base        & 0xFF000000;        // set base bits 31:24 

  // Shift by 32 to allow for low part of segment
  entry <<= 32;

  // Create the low 32 bit segment
  entry |= base  << 16;                       // set base bits 15:0
  entry |= limit  & 0x0000FFFF;               // set limit bits 15:0

  return entry;
}

void init_gdt(){
    gdtd.limit = (sizeof(gdt_entry) * 5) - 1;
    gdtd.base = (uint32_t)entrys;

    //Fill data to gdt entries
    entrys[0] = create_entry(0, 0, 0); //Needed
    entrys[1] = create_entry(0, 0x000FFFFF, (GDT_CODE_PL0));   // Ring 0 code section
    entrys[2] = create_entry(0, 0x000FFFFF, (GDT_DATA_PL0));   // Ring 0 data section
    entrys[3] = create_entry(0, 0x000FFFFF, (GDT_CODE_PL3));   // Ring 3 code section
    entrys[4] = create_entry(0, 0x000FFFFF, (GDT_DATA_PL3));   // Ring 3 data section

    _flush_gdt((uint32_t)&gdtd);

}

实际运行结果

如下图所示,使用bochs加载我的操作系统之后,通过自带的debug功能能得到下述的GDT信息,和我们在C代码中设置的一致。Succeed~

参考文献

  1. https://www.wikiwand.com/en/Global_Descriptor_Table
  2. http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
  3. http://wiki.osdev.org/GDT_Tutorial
  4. http://wiki.osdev.org/Global_Descriptor_Table
  5. http://www.osdever.net/bkerndev/Docs/gdt.htm
  6. http://bochs.sourceforge.net/doc/docbook/user/internal-debugger.html
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容