Bran的内核开发教程(bkerndev)-06 全局描述符表(GDT)
全局描述符表(GDT)
在386平台各种保护措施中最重要的就是全局描述符表(GDT)。GDT为内存的某些部分定义了基本的访问权限。我们可以使用GDT中的一个索引来生成段冲突异常, 让内核终止执行异常的进程。现代操作系统大多使用"分页"的内存模式来实现该功能, 它更具通用性和灵活性。GDT还定义了内存中的的某个部分是可执行程序还是实际的数据。GDT还可定义任务状态段(TSS)。TSS一般在基于硬件的多任务处理中使用, 所以我们在此并不做讨论。需要注意的是TSS并不是启用多任务的唯一方法。
注意GRUB已经为你安装了一个GDT, 如果我们重写了加载GRUB的内存区域, 将会丢弃它的GDT, 这会导致"三重错误(Triple fault)"。简单的说, 它将重置机器。为了防止该问题的发生, 我们应该在已知可以访问的内存中构建自己的GDT, 并告诉处理器它在哪里, 最后使用我们的新索引加载处理器的CS、DS、ES、FS和GS寄存器。CS寄存器就是代码段, 它告诉处理器执行当前代码的访问权限在GDT中的偏移量。DS寄存器的作用类似, 但是数据段, 定义了当前数据的访问权限的偏移量。ES、FS和GS是备用的DS寄存器, 对我们并不重要。
GDT本身是64位的长索引列表。这些索引定义了内存中可访问区域的起始位置和大小界限, 以及与该索引关联的访问权限。通常第一个索引, 0号索引被称为NULL描述符。所以我们不应该将任何的段寄存器设置为0, 否则将导致常见的保护错误, 这也是处理器的保护功能。通用的保护错误和几种异常将在中断服务程序(ISR)那节详细说明。
每个GDT索引还定义了处理器正在运行的当前段是供系统使用的(Ring 0)还是供应用程序使用的(Ring 3)。也有其他Ring级别, 但并不重要。当今主要的操作系统仅使用Ring 0和Ring 3。任何应用程序在尝试访问系统或Ring 0的数据时都会导致异常, 这种保护是为了防止应用程序导致内核崩溃。GDT的Ring级别用于告诉处理器是否允许其执行特殊的特权指令。具有特权的指令只能在更高的Ring级别上运行。例如"cli"和"sti"禁用和启用中断, 如果应用程序被允许使用这两个指令, 它就可以阻止内核的运行。你将在本教程的后续章节中了解更多有关中断的知识。
GDT的描述符组成如下:

- G: 段界限粒度(Granularity)
- G = 0: 长度单位为1字节
- G = 1: 长度单位为4KB
- D: 操作数大小
- 0 = 16bit
- 1 = 32bit
- L: 未使用为0
- AVL: 保留位, 系统软件使用
- P: 存在位, 段是否存在
- 1 = Yes
- 0 = No
- DPL: Ring级别(0到3)
- S: 描述符类型位
- S = 1: 存储段描述符, 数据段/代码段
- S = 0: 系统段描述符/门描述符)
- TYPE: 段类型
在我们的内核教程中, 我们将创建一个包含3个索引的GDT。一个用于''虚拟''描述符充当处理器内存保护功能的NULL段, 一个用于代码段, 一个用于数据段寄存器。使用汇编操作码lgdt告诉处理器我们新的GDT表在哪里。为lgdt提供一个指向48位的专用的全局描述符表寄存器(GDTR)的指针。该寄存器用来保存全局描述符信息, 0-15位表示GDT的边界位置(数值为表的长度-1), 16-47位存放GDT基地址。并且在我们访问GDT中不存在偏移的段时, 希望处理器可以立即创建一般保护错误)。
我们可以使用3个索引的简单数组来定义GDT。对于我们的特殊GDTR指针, 我们只需要声明一个即可。我们称其为gp。创建一个新文件gdt.c。在build.bat中添加一行gcc命令来编译gdt.c, 并将gdt.o添加到LD链接文件列表中。下面这些代码组成了gdt.c的前半部分:
gdt.c
#include <system.h>
/* 定义一个GDT索引. __attribute__((packed))用于防止编译器优化对齐 */
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));
/* GDTR指针 */
struct gdt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
/* 声明包含3个索引的GDT和GDTR指针gp */
struct gdt_entry gdt[3];
struct gdt_ptr gp;
/* 这是start.asm中的函数, 用来加载新的段寄存器 */
extern void gdt_flush();
gdt_flush()我们还没有定义, 该函数使用上面的GDTR指针来告诉处理器新的GDT所在位置, 并重新加载段寄存器, 最后跳转到我们的新代码段。现在我们在start.asm的stublet下的死循环后面添加下面的代码来定义gdt_flush:
start.asm
; 这将建立我们新的段寄存器
; 通过长跳转来设置CS
global _gdt_flush ; 允许C源程序链接该函数
extern _gp ; 声明_gp为外部变量
_gdt_flush:
lgdt [_gp] ; 用_gp来加载GDT
mov ax, 0x10 ; 0x10是我们数据段在GDT中的偏移地址
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:flush2 ; 0x08是代码段的偏移地址, 长跳转
flush2:
ret ; 返回到C程序中
仅为GDT保留内存空间是不够的, 还需要将值写入每个GDT中, 设置gp指针, 再调用gdt_flush进行更新。定义gdt_set_entry()函数, 该函数使用函数参数的移位给GDT每个字段设置值。为了让main.c能够使用这些函数, 别忘了将它们添加到system.h中(至少需要把gdt_install添加进去)。下面为gdt.c的剩下部分:
gdt.c
/* 在全局描述符表中设置描述符 */
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
/* 设置描述符基地址 */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
/* 设置描述符边界 */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
/* 最后,设置粒度和访问标志 */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
/* 由main函数调用
* 设置GDTR指针, 设置GDT的3个索引条码
* 最后调用汇编中的gdt_flush告诉处理器新GDT的位置
* 并跟新新的段寄存器 */
void gdt_install()
{
/* 设置GDT指针和边界 */
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = &gdt;
/* NULL描述符 */
gdt_set_gate(0, 0, 0, 0, 0);
/* 第2个索引是我们的代码段
* 基地址是0, 边界为4GByte, 粒度为4KByte
* 使用32位操作数, 是一个代码段描述符
* 对照本教程中GDT的描述符的表格
* 弄清每个值的含义 */
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/* 第3个索引是数据段
* 与代码段几乎相同
* 但access设置为数据段 */
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
/* 清除旧的GDT安装新的GDT */
gdt_flush();
}
现在我们的GDT加载程序的基本结构已经到位, 在将其编译链接到内核中后, 我们需要在main.c中调用gdt_install()才能真正完成工作。在main()函数的第一行添加gdt_install();GDT加载必须最先初始化。现在, 编译你的内核, 并在软盘中对其进行测试, 你不会在屏幕上看到任何变化, 这是一个内部的更改。
下面我们将进入中断描述符表(IDT)!
PS
如果编译的时候报错:
undefined reference to `_gp'
undefined reference to `gdt_flush'
则把start.asm中_gp和_gdt_flush前面的下划线去掉再重新编译。
此文原创禁止转载,转载文章请联系博主并注明来源和出处,谢谢!
作者: Raina_RLN https://www.cnblogs.com/raina/
Bran的内核开发教程(bkerndev)-06 全局描述符表(GDT)的更多相关文章
- Bran的内核开发教程(bkerndev)-07 中断描述符表(IDT)
中断描述符表(IDT) 中断描述符表(IDT)用于告诉处理器调用哪个中断服务程序(ISR)来处理异常或汇编中的"int"指令.每当设备完成请求并需要服务事, 中断请求也会调用I ...
- Bran的内核开发教程(bkerndev)-02 准备工作
准备工作 内核开发是编写代码以及调试各种系统组件的漫长过程.一开始这似乎是一个让人畏惧的任务,但是并不需要大量的工具集来编写自己的内核.这个内核开发教程主要涉及使用GRUB将内核加载到内存中.GR ...
- Bran的内核开发教程(bkerndev)-01 介绍
介绍 内核开发不是件容易的事,这是对一个程序员编程能力的考验.开发内核其实就是开发一个能够与硬件交互和管理硬件的软件.内核也是一个操作系统的核心,是管理硬件资源的逻辑. 处理器或是CPU是内核 ...
- Bran的内核开发教程(bkerndev)-04 创建main函数和链接C文件
目录 创建main函数和链接C文件 PS: 下面是我自己写的 Win10安装gcc编译器 本节教程对应的Linux下的编译脚本 _main的问题 创建main函数和链接C文件 一般C语言使用mai ...
- Bran的内核开发教程(bkerndev)-03 内核初步
目录 内核初步 内核入口 链接脚本 汇编和链接 PS: 下面是我自己写的 64位Linux下的编译脚本 内核初步 在这节教程, 我们将深入研究一些汇编程序, 学习创建链接脚本的基础知识以及使用它的 ...
- Bran的内核开发教程(bkerndev)-08 中断服务程序(ISR)
中断服务程序(ISR) 中断服务程序(ISR)用于保存当前处理器的状态, 并在调用内核的C级中断处理程序之前正确设置内核模式所需的段寄存器.而工作只需要15到20行汇编代码来处理, 包括调用C中的 ...
- Bran的内核开发教程(bkerndev)-05 打印到屏幕
打印到屏幕 现在, 我们需要尝试打印到屏幕上.为此, 我们需要管理屏幕滚动, 如果能允许使用不同的颜色就更好了.好在VGA视频卡为我们提供了一片内存空间, 允许同时写入属性字节和字符字节对, 可以 ...
- Taurus.MVC WebAPI 入门开发教程6:全局控制器DefaultController与全局事件。
系列目录 1.Taurus.MVC WebAPI 入门开发教程1:框架下载环境配置与运行. 2.Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World. 3.Tau ...
- linux内核学习之全局描述符表(GDT)(二)
来源:https://www.cnblogs.com/longintchar/p/5224406.html 在进入保护模式之前,我们先要学习一些基础知识.今天我们看一下全局描述符表(Global De ...
随机推荐
- MySQL二进制日志分析-概述篇
MySQL从3.23版本开始引入了二进制日志,用于的数据复制, 二进制日志根据MySQL的版本不同,目前有4个版本: https://dev.mysql.com/doc/internals/en/bi ...
- 为git创建远程仓库
首先生成ssh公钥: 将公钥添加到git: 测试秘钥是否通过: 然后就可以到web界面看到标注的地方被绿了: 但是我的没有绿,不知道为啥,难道没有女朋友的原因吗? rm -rf .ssh 重来好几遍都 ...
- .Net基础篇_学习笔记_第八天_复杂数据类型(常量/枚举/结构)
1.常量: 声明的常量的语法:const 变量类型 变量名=值;最大的特点就是,声明了常量后,就不能再被重新赋值. 2.枚举 语法:[public] enum 枚举名{ 值1, 值2, 值3, ... ...
- Linux 笔记 - 特殊权限
博客地址:http://www.moonxy.com 一.前言 Linux 中使用权限的时候,一般都是使用 3 位数,比如,777.755.666.644 等,其实在最前面还有一位,那就是特殊权限,也 ...
- 重学js之JavaScript 面向对象的程序设计(创建对象)
注意: 本文章为 <重学js之JavaScript高级程序设计>系列第五章[JavaScript引用类型]. 关于<重学js之JavaScript高级程序设计>是重新回顾js基 ...
- go语言-最大32位数反转
package main import ( "fmt" "strconv" ) func fanzhuang32(number int) string { fu ...
- vue把链接转二维码
使用qrcodejs2插件 1. 安装qrcodejs2:npm install qrcodejs2 --save 2. 在组件里面引入: import QRCode from 'qrcodejs2' ...
- scrapy框架来爬取壁纸网站并将图片下载到本地文件中
首先需要确定要爬取的内容,所以第一步就应该是要确定要爬的字段: 首先去items中确定要爬的内容 class MeizhuoItem(scrapy.Item): # define the fields ...
- SpringBoot起飞系列-入门(一)
一.SpringBoot简介 1.1 什么是SpringBoot 说到spring系列,可能大家都很熟悉,spring.springmvc,美之名曰:spring全家桶,那么springboot其实也 ...
- jenkins自动化部署项目2 --插件的选择和安装
一.安装插件: 我选择的安装建议的插件,也可以自定义安装自己想要的插件,在不敢保证自己确定要用的插件是完全正确的情况下建议按推荐安装 我理解的jenkins+tomcat完成自动化部署maven项目需 ...