操作系统篇-分段机制与GDT|LDT

 

|| 版权声明:本文为博主原创文章,未经博主允许不得转载。

  一、前言

在《操作系统篇-浅谈实模式与保护模式》中提到了两种模式,我们说在操作系统中,其实大部分时间是待在保护模式中的。因此若想理解操作系统程序中的启动相关的部分,必须要理解保护模式下的编程,而分段机制是保护模式编程下的基础。而且,由于实模式与保护模式的不同,对保护模式下的分段机制更需要注意。

  二、线性地址

  在保护模式下编程,访问内存时,需要在程序中给出段地址和偏移量,因为分段是保护模式的基本特征之一。传统上,段地址和偏移地址称为逻辑地址,偏移地址叫做有效地址,在指令中给出有效地址的方式叫做寻址方式。

  段的管理是由处理器的段部件负责进行的,段部件将段地址和偏移地址相加,得到访问内存的地址。一般来说,段部件产生的地址就是物理地址。

  在分段模型下,内存的分配是不定长的,时间长了,内存空间就会碎片化,就有可能出现一种情况:内存空间是有的,但都是小块,无法分配给某个任务。为了解决这个问题,在支持分页功能后,分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为 4KB,通过使用页,可以简化内存管理。

  如下图所示,当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址,线性地址还要经页部件转换后,才是物理地址。

  线性地址的概念用来描述任务的地址空间。如上图所示, 32位保护模式中,每个任务都拥有4GB 的虚拟内存空间,就像一段平直的线段,因此叫线性地址空间。相应地,由段部件产生的地址,就对应着线性地址空间上的每一个点,这就是线性地址。

  三、段

  段是实现虚拟地址到线性地址转换机制的基础。在保护方式下,段的特征有以下三个:段基址,段限长,段属性。这三个特征存储在段描述符(segmentdescriptor)之中,用以实现从逻辑地址到线性地址的转换。段描述符存储在段描述符表之中,通常,我们使用段选择符定位段描述符在这个表中的位置。每个逻辑地址由16位的段选择符+32位的偏移量组成。

  段基址规定线性地址空间中段的开始地址。在保护模式下,段基地址长32位。因为基地址长度与寻址地址的长度相同,所以段基地址可以是 0~4GB 范围内的任意地址,而不像实方式下规定的边界必须被16整除。不过,还是建议应当选取那些 16 字节对齐的地址。尽管对于 Intel 处理器来说,允许不对齐的地址,但是,对齐能够使程序在访问代码和数据时的性能最大化。

  段界限规定段的大小。在保护模式下,段界限用20位表示,而且段界限可以是以字节为单位或以4K字节为单位。偏移量是从 0 开始递增,段界限决定了偏移量的最大值。对于向下扩展的段,如堆栈段来说, 段界限决定了偏移量的最小值。

  段属性我们放到下面讲解描述符的时候来讲。

  四、段描述符表

   和一个段有关的信息需要 8 个字节来描述,这就是段描述符( Segment Descriptor),每个段都需要一个描述符。为了存放这些描述符,需要在内存中开辟出一段空间。在这段空间里,所有的描述符都是挨在一起,集中存放的,这就构成一个描述符表。描述符表的长度可变,最多可以包含8K个这样的描述符(为什么呢?因为段选择子是16位的,其中的13bit用来作index)。

  有两种描述符表,GDT和LDT。结构如下:

  

  其实LDT与我们在《操作系统篇-浅谈实模式与保护模式》中提到过的GDT是差不多的,区别在于(1)全局(Global)和局部(local);(2)LDT表存放在LDT类型的段之中,此时GDT必须含有LDT的段描述符;(3)LDT本身是一个段,而GDT不是。

  查找GDT在线性地址中的基地址,需要借助GDTR;而查找LDT相应基地址,需要的是GDT中的段描述符。访问LDT需要使用段选择符,为了减少访问LDT时候的段转换次数,LDT的段选择符,段基址,段限长都要放在LDTR寄存器之中。

  对于操作系统来说,每个系统必须定义一个GDT,用于系统中的所有任务和程序。可选择性定义若干个LDT。GDT本身不是一个段,而是线性地址空间的一个数据结构;GDT的线性基地址和长度必须加载进GDTR之中。因为每个描述符长度是8,所以GDT的基地址最好进行8字节对齐。

  五、段选择符

  实模式下的 6 个段寄存器 CS、 DS、 ES、 FS、 GS 和 SS,在保护模式下叫做段选择器。和实模式不同,保护模式的内存访问有它自己的方式。在保护模式下,尽管访问内存时也需要指定一个段,但传送到段选择器的内容不是逻辑段地址,而是段描述符在描述符表中的索引号。在保护模式下访问一个段时,传送到段选择器的是段选择符,也叫段选择子。其结构如下图所示:

  如图所示,段选择子由三部分组成,共16bit,第一部分是描述符的索引号,用来在描述符表中选择一个段描述符。 TI 是描述符表指示器, TI=0 时,表示描述符在 GDT 中; TI=1 时,描述符在 LDT 中。RPL 是请求特权级,表示给出当前选择子的那个程序的特权级别,正是该程序要求访问这个内存段。每个程序都有特权级别。

  GDT 的线性基地址在 GDTR 中,又因为每个描述符占 8 字节,因此,描述符在表内的偏移地址是索引号乘以 8。当处理器在执行任何改变段选择器的指令时(比如 pop、 mov、jmp far、 call far、 iret、 retf),就将指令中提供的索引号乘以 8 作为偏移地址,同 GDTR 中提供的线性基地址相加,以访问 GDT。如果没有发现什么问题(比如超出了 GDT 的界限),就自动将找到的描述符加载到不可见的描述符高速缓存部分。如下图所示:

加载的部分包括段的线性基地址、段界限和段的访问属性。此后,每当有访问内存的指令时,就不再访问 GDT 中的描述符,直接用当前段寄存器描述符高速缓存器提供线性基地址。

  六、段描述符

  a.描述符的结构

  首先,我们来看看描述符的结构,拿出上篇blog的图来look一look:

  GDT的作用是用来提供段式存储机制,这种机制是段寄存器和GDT中的描述符共同提供的。每个描述符在GDT中占8字节,也就是 2 个双字,或者说是 64 位。图中,下面是低32位,上面是高32位。

  其中:

  G 位是粒度位,用于解释段界限的含义。当 G 位是“ 0”时,段界限以字节为单位。此时,段的扩展范围是从 1 字节到 1 兆字节( 1B~1MB),因为描述符中的界限值是 20 位的。相反,如果该位是“ 1”,那么,段界限是以 4KB 为单位的。这样,段的扩展范围是从 4KB到 4GB

  S 位用于指定描述符的类型( Descriptor Type)。当该位是“ 0”时,表示是一个系统段;为“ 1”时,表示是一个代码段或者数据段(堆栈段也是特殊的数据段)。

  DPL 表示描述符的特权级( Descriptor Privilege Level, DPL)。这两位用于指定段的特权级。共有 4 种处理器支持的特权级别,分别是 0、 1、 2、 3,其中 0 是最高特权级别, 3 是最低特权级别。刚进入保护模式时执行的代码具有最高特权级 0(可以看成是从处理器那里继承来的),这些代码通常都是操作系统代码,因此它的特权级别最高。每当操作系统加载一个用户程序时,它通常都会指定一个稍低的特权级,比如 3 特权级。不同特权级别的程序是互相隔离的,其互访是严格限制的,而且有些处理器指令(特权指令)只能由 0 特权级的程序来执行,为的就是安全。这里再次点明了为何叫保护模式。

  P 是段存在位( Segment Present)。 P 位用于指示描述符所对应的段是否存在。一般来说,描述符所指示的段都位于内存中。但是,当内存空间紧张时,有可能只是建立了描述符,对应的内存空间并不存在,这时,就应当把描述符的 P 位清零,表示段并不存在。P 位是由处理器负责检查的。每当通过描述符访问内存中的段时,如果 P 位是“ 0”,处理器就会产生一个异常中断。

  D/B 位是“默认的操作数大小”( Default Operation Size)或者“默认的堆栈指针大小”,又或者“上部边界”标志。设立该标志位,主要是为了能够在 32 位处理器上兼容运行 16 位保护模式的程序。D=0 表示指令中的偏移地址或者操作数是 16 位的; D=1,指示 32 位的偏移地址或者操作数。

  举个例子来说, 如果代码段描述符的 D 位是 0,那么,当处理器在这个段上执行时,将使用 16位的指令指针寄存器 IP 来取指令,否则使用 32 位的 EIP。

  对于堆栈段来说,该位被叫做“ B”位,用于在进行隐式的堆栈操作时,是使用 SP 寄存器还是ESP 寄存器。

  L 位是 64 位代码段标志,保留此位给 64 位处理器使用。目前,我们将此位置“ 0”即可。

  TYPE 字段共 4 位,用于指示描述符的子类型,或者说是类别。

  b.描述符类型

  对于数据段来说, 这 4 位分别是 X、 E、 W、 A 位;而对于代码段来说,这 4 位则分别是 X、 C、 R、 A 位。如下表所示

 

  X 表示是否可以执行( eXecutable)。数据段总是不可执行的, X=0;代码段总是可以执行的,因此, X=1。

  对于数据段来说, E 位指示段的扩展方向。 E=0 是向上扩展的,也就是向高地址方向扩展的,是普通的数据段; E=1 是向下扩展的,也就是向低地址方向扩展的,通常是堆栈段。

  W 位指示段的读写属性,或者说段是否可写, W=0 的段是不允许写入的,否则会引发处理器异常中断; W=1的段是可以正常写入的。

  对于代码段来说, C 位指示段是否为特权级依从的( Conforming)。 C=0 表示非依从的代码段,这样的代码段可以从与它特权级相同的代码段调用,或者通过门调用; C=1 表示允许从低特权级的程序转移到该段执行。

   R 位指示代码段是否允许读出。代码段总是可以执行的,但是,为了防止程序被破坏,它是不能写入的。至于是否有读出的可能,由 R 位指定。 R=0 表示不能读出,如果企图去读一个 R=0 的代码段,会引发处理器异常中断;如果 R=1,则代码段是可以读出的,即可以把这个段的内容当成 ROM 一样使用。

  也许有人会问,既然代码段是不可读的,那处理器怎么从里面取指令执行呢?事实上,这里的R属性并非用来限制处理器, 而是用来限制程序和指令的行为。

   数据段和代码段的 A 位是已访问位,用于指示它所指向的段最近是否被访问过。在描述符创建的时候,应该清零。之后,每当该段被访问时,处理器自动将该位置“ 1”。

  七、总结

  GDT|LDT是进入保护模式必须了解的基础之一,其是保护模式中进行寻址的关键,也是以后理解代码跳转的基础,必须要熟练。

时间: 2024-11-03 23:24:11

操作系统篇-分段机制与GDT|LDT的相关文章

操作系统篇-浅析分页机制

|| 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言 在我们进行程序开发的时候,一般情况下,是不需要管理内存的,也不需要操心内存够不够用,其实,这就是分页机制给我们带来的好处.它是实现虚拟存储的关键,位于线性地址与物理地址之间,在使用这种内存分页管理方法时,每个执行中的进程(任务)可以使用比实际内存容量大得多的连续地址空间.而且当系统内存实际上被分成很多凌乱的块时,它可以建立一个大而连续的内存空间的映象,好让程序不用操心和管理这些分散的内存块.分页机制增强了分段机制的性能.页地址

Linux内存寻址之分段机制

http://blog.xiaohansong.com/2015/10/03/Linux内存寻址之分段机制/ .段的起始地址.段的长度等等,而在保护模式下则复杂一些.IA32将它们结合在一起用一个8字节的数表示,称为描述符 .IA32的一个通用的段描述符的结构从图可以看出,一个段描述符指出了段的32位基地址和20位段界限(即段长).这里我们只关注基地址和段界限,其他的属性略过. 段描述符表 各种各样的用户描述符和系统描述符,都放在对应的全局描述符表.局部描述符表和中断描述符表中.描述符表(即段表

分段机制(个人理解)

分段机制可用于实现多种系统设计.这些设计范围从使用分段机制的最小功能来保护程序的平坦模型,到使用分段机制创建一个可同时可靠地运行多个程序(或任务)的具有稳固操作环境的多段模型. 多段模型能够利用分段机制全部功能提供由硬件增强的代码,数据结构,程序和任务的保护措施.通常,每个程序(或任务)都是用自己的段描述符以及自己的段.对程序来说段能够完全是私有的,或者是程序之间共享的.对所有段以及系统上运行程序各自执行环境的访问都由硬件控制. 访问检查不仅能够用来保护对段界限以外地址的引用,而且也能用来在某些

内核保护模式之分段机制

CPU的三种模式 1982年,intel推出了80286处理器,第一次提出了保护模式,在保护模式下,段寄存器中存储的不再是段基址,而是段选择子. 真正的段基址存储在描述符高速缓存中,80286处理器访问内存,不需要段寄存器左移加上偏移. 在x86体系的CPU下,支持三种模式 实模式:兼容16位CPU的模式,当前的PC系统处于实模式(16位模式)运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB,且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力.实模式将整个物理

分段机制

段的定义 段的介绍 分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存单元. 80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成.段是虚拟地址到线性地址转化的基础.每个段有三个参数定义: 段基地址,指定段在线性地址空间中的开始地址.基地址是线性地址对应于段中偏移 0 处. 段限长,是虚拟地址空间中段内最大可用偏移地址.定义了段的长度. 段属性,指定段的特性.如该段是否可读.可写或可作为一个程序执行,段的特权级等. 多个段映射到线性地址中的范围可以部分

程序员的自我修养——操作系统篇(转)

也许,只需这一篇文章,便能让你全面的认识操作系统! 在阅读本文之前,推荐阅读“自己动手制作4位计算机”. 目录: 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式. 5. 线程的实现方式. (用户线程与内核线程的区别) 6. 用户态和核心态的区别. 7. 用户栈和内核栈的区别. 8. 内存池.进程池.线程池. 9. 死锁的概念,导致死锁的原因,导致死锁的四个必要条件,处理死锁的四个方式,预防死锁的方法.避免死锁

《30天自制操作系统》读书笔记(5) GDT&IDT

梳理项目结构 项目做到现在, 前头的好多东西都忘了, 还是通过Makefile重新理解一下整个项目是如何编译的: 现在我们拥有这么9个文件: ipl10.nas    InitialProgramLoader, 占用了软盘的第一个扇区并符合启动盘的规范, 默认被载入地址是0x7c00 到 0x7e00, 负责将10个柱面读入到0x8200到0x34fff (10个柱面共10*2*18 = 360 个扇区但是第一个没有被读入); asmhead.nas     包含一些暂时未知的设定; naskf

计算机考研复试面试常问问题 操作系统篇

计算机考研复试面试常问问题 操作系统篇 在复习过程中,我用心查阅并整理了在考研复试面试中可能问到的大部分问题,并分点整理了答案,可以直接理解背诵并加上自己的语言润色!极力推荐打印下来看,效率更高! 此系列一共有8篇:编程语言篇|数据结构篇|操作系统篇|组成原理篇|计算机网络篇|数据库篇|软件工程篇|计算机专业英语篇(还未全部完成,敬请期待,你们的支持和关注是我最大的动力!) 个人整理,不可用于商业用途,转载请注明出处. 作者各个平台请搜索:程序员宝藏.快来探索属于你的宝藏吧! 需要pdf直接打印

操作系统基本分段存储管理方式

操作系统基本分段存储管理方式 引入分段存储管理方式的目的:满足程序员在编程和使用上多方面的要求.这种存储管理方式已经成为当今所有存储管理方式的基础. 1.分段存储管理方式的引入 主要满足用户和程序员以下需求: 1).方便编程 用户把自己的作业按照逻辑管理划分为若干段,每个段都是从0开始编址,并有自己的名字和长度.因此,希望要访问的逻辑地址是由段名(段号)和段内偏移量(段内地址)决定的. LOAD1,[A] | <D>;//将分段A中D单元内的值读入寄存器1 STORE1,[B] | <C