CPU实模式和保护模式、全局描述符表GDT、Linux内核中GDT和IDT的结构定义

一 计算机实模式和保护模式

 

实模式
在实模式下,内存被限制为仅有1M字节(220 字节)。有效的地址从00000到FFFFF (十六进制)。
这些地址需要用20位的数来表示。一个20位的数不适合任何一个8086的16位寄存器。
Intel通过利用两个16位数值来决定一个地址的方法来解决这个问题。开始的16位值称为段地址(selector)。
段地址的值必须存储在段寄存器中。第二个16位值称为偏移地址(offset)。

16位保护模式
在实模式下,一个段地址的值是物理内存里的一节的首地址。在保护模式下,一个段地址的值是一个指向描述符表的指针
。两种模式下,程序都是被分成段。在实模式下,这些段在物理内存的固定位置而且段地址的值表示段开始处所在节的首
地址。在保护模式下,这些段不是在物理内存的固定的地址。事实上,它们不一定需要在内存中。

保护模式使用了一种叫做虚拟内存的技术。虚拟内存的基本思想是仅仅保存程序现在正在使用的代码和数据到内存中。其
它数据和代码暂时储存在硬盘中直到它们再次需要时。

在保护模式下,每一段都分配了一条描述符表里的条目。这个条目拥有系统想知道的关于这段的所有信息。这些信息包括
:现在是否在内存中;如果在内存中,在哪;访问权限(例如: 只读)。段的条目的指针是储存在段寄存器里的段地址值

32位保护模式
80386引入了32位保护模式。
386 32位保护模式和286 16位保护模式之间最主要的区别是:
    1 偏移地址扩展成了32位。这就允许偏移地址范围升至4G。因此,段的大小也升至4G。
    2 段可以分成较小的4K大小的单元,称为内存页。虚拟内存系统工作在页的方式下,代替了段方式。这就意味着一段
在任何一个时刻只有部分可能在内存中。在28616位保护模式下,要么整个段在内存中,要么整个不在。 

    在Windows 3.x系统中,标准模式为286 16位保护模式而增强模式为32位保护模式。Windows 9X,Windows
NT/2000/XP,OS/2和Linux都运行在分页管理的32位保护模式下。

 

二 全局描述符表GDT

 

    GDT,Global Descriptor Table。
    IA32允许将一个段的基地址设置为32bit所能表示的任何值,limit(段大小)则可以设置成以2^12为倍数的任何值。
    在保护模式下,对一个段的描述包括以下三个方面:[Base Address,Limit,Access],他们加在一起被放在一个64bit
长的数据结构中,被成为段描述符。
    在这种情况下,如果我们直接通过一个64bit段描述符来引用一个段的时候,就必须使用一个64bit长的段寄存器装入
这个段描述符。
    把这些长度为64bit的段描述符放入一个数组中,而将段寄存器中的值作为下标索引来间接引用(事实上就是将段寄
存器中的高13bit的内容作为索引)。这个全局数据就是GDT。

 

三 Linux中GDT和IDT的结构定义

 

GDT,IDT都是全局的。LDT是局部的(在GDT中有它的描述符);

GDT用来存储描述符(门或非门);系统中几个CPU,就有几个GDT

struct gdt_page {

    struct desc_struct gdt[GDT_ENTRIES];

} __attribute__((aligned(PAGE_SIZE)));

DECLARE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page);


IDT整个系统只有一个;
系统启动时候需要初始化GDT和IDT;


描述符结构定义,在<arch/x86/include/asm/desc_defs.h>

struct desc_struct {

    union {

        struct {

            unsigned int a;

            unsigned int b;

        };

        struct {

            u16 limit0;

            u16 base0;

            unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;

            unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;

        };

    }; 
} __attribute__((packed));

上面第一个匿名结构体用来作为成员访问取值的出口,下面第二个结构体对真实的成员设置值的入口。

字段:

limit:段长度

base:段的首字节的线性地址,有base0,base1,base2三部分构成

type:段的类型和存取权限

s:系统标志。1-系统段;0-普通段

dpl:描述符特权级

p:segment-Present。linux下总是1

avl:linux不用

d:区分代码段还是数据段

g:段大小粒度。以4K倍数计算

 

在32位机器上,这就是所有描述符的数据结构;

typedef struct desc_struct gate_desc;

typedef struct desc_struct ldt_desc;

typedef struct desc_struct tss_desc;

 

由于三类描述符都是一个结构类型,从而一律使用下面宏初始化在GDT中表项

#define GDT_ENTRY_INIT(flags, base, limit) { { { \

        .a = ((limit) & 0xffff) | (((base) & 0xffff) << 16), \

        .b = (((base) & 0xff0000) >> 16) | (((flags) & 0xf0ff) << 8) | \

            ((limit) & 0xf0000) | ((base) & 0xff000000), \

    } } }

 

但是在64位机器上,Linux则进行了细致划分;
无论是32位还是64位机器上,都使用typedef重新定义,以提供给系统其他使用此描述符的部分一致的类型名;

 

区分描述符的枚举量

enum {

    GATE_INTERRUPT = 0xE,

    GATE_TRAP = 0xF,

    GATE_CALL = 0xC,

    GATE_TASK = 0x5,

};


enum {

    DESC_TSS = 0x9,

    DESC_LDT = 0x2,

    DESCTYPE_S = 0x10,  /* !system */

};

 

系统GDT,IDT指针描述结构

struct desc_ptr {

    unsigned short size;

    unsigned long address;

} __attribute__((packed)) ;


这个结构记录了系统的GDT或者IDT的大小以及在系统中的线性基址;

Reference:

    <arch/x86/include/asm/desc_defs.h>

 

四 如何通过段描述符访问内存

 

当我们要访问某个段中的一个地址时候:
    1 从GDTR中拿到GDT在内存中的基地址,得到段描述符表; 

    2 从段选择子中的前13位得到我们要访问的段的描述符在段描述符表中的索引; 

    3 从段描述符表中得到要访问的段的描述符,得到其基地址;
    4 基地址加上偏移地址就是我们要访问的内存地址(这里是虚拟地址,接下来是分页机制的功能将虚地址转换为物理
地址,)

 

看内核相关源码涉及到 AT&T汇编,和Intel汇编语法不同;先学部分AT&T汇编语法;

    操作数排列是从源(左)到目的(右),如"movl %eax(源), %ebx(目的)";

    符号常数直接引用 如:
value: .long 0x12a3f2de
movl value , %ebx

    会见到如下的指令:push,pushl,pushfl;

    操作数的长度用加在指令后的符号表示b(byte, 8-bit), w(word, 16-bits), l(long, 32-bits);
    如
        "movb %al, %bl",
        "movw %ax, %bx",
        "movl %eax, %ebx",
    目前还不知道pushfl是啥;

    如果没有指定操作数长度的话,编译器将按照目标操作数的长度来设置。

参阅
https://blog.csdn.net/cwcmcw/article/details/21640363
http://blog.chinaunix.net/uid-29113598-id-5210949.html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
GDT全局描述符)和IDT描述符)是操作系统用于管理内存和断的重要数据结构GDT是一个,用于存储所有内存描述符描述符包含了的基地址、的长度、访问权限等信息。在x86体系结构保护模式下,所有的内存访问都必须通过寄存器来现。当CPU执行一条访存指令时,它会把寄存器的值当做描述符的索引,在GDT找到对应的描述符,从而确定要访问的内存地址的范围和访问权限。因此,初始化GDT是操作系统启动时的必要步骤。 IDT是另一个,用于存储所有断和异常处理程序的描述符。当CPU收到一个断请求或异常时,它会从IDT找到对应的描述符,从而确定要执行的断或异常处理程序的地址。因此,初始化IDT也是操作系统启动时的必要步骤。 GDTIDT的初始化大致可以分为以下几个步骤: 1.创建并填充GDTIDT项,每个项对应一个内存断处理程序。 2.创建并填充GDTR(GDT寄存器)和IDTR(IDT寄存器),这两个寄存器分别存储GDTIDT的地址和大小信息。 3.使用LGDT和LIDT指令将GDTR和IDTR的值加载到CPU,从而告诉CPU如何寻找GDTIDT。 需要注意的是,为了保证安全性,GDTIDT通常被放置在内核态的固定位置,并且只有内核态的代码才能够修改它们。此外,为了简化现,现代操作系统通常会使用一些预定义GDTIDT项,而不是每次都手动填充项。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值