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

  一、前言

    在《操作系统篇-浅谈实模式与保护模式》中提到了两种模式,我们说在操作系统中,其实大部分时间是待在保护模式中的。因此若想理解操作系统程序中的启动相关的部分,必须要理解保护模式下的编程,而分段机制是保护模式编程下的基础。而且,由于实模式与保护模式的不同,对保护模式下的分段机制更需要注意。
  二、线性地址
  在保护模式下编程,访问内存时,需要在程序中给出段地址和偏移量,因为分段是保护模式的基本特征之一。传统上,段地址和偏移地址称为逻辑地址,偏移地址叫做有效地址,在指令中给出有效地址的方式叫做寻址方式。
  段的管理是由处理器的段部件负责进行的,段部件将段地址和偏移地址相加,得到访问内存的地址。一般来说,段部件产生的地址就是物理地址。
  在分段模型下,内存的分配是不定长的,时间长了,内存空间就会碎片化,就有可能出现一种情况:内存空间是有的,但都是小块,无法分配给某个任务。为了解决这个问题,在支持分页功能后,分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为 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是进入保护模式必须了解的基础之一,其是保护模式中进行寻址的关键,也是以后理解代码跳转的基础,必须要熟练。

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

  1. 操作系统篇-调用门与特权级(CPL、DPL和RPL)

    || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言 在前两篇文章(<操作系统篇-浅谈实模式与保护模式>和<操作系统篇-分段机制与GDT|LDT>)中,我们提到 ...

  2. 【转】操作系统 gdt ldt

    GDT的由来:     在Protected Mode下,一个重要的必不可少的数据结构就是GDT(Global Descriptor Table). 为什么要有GDT?我们首先考虑一下在Real Mo ...

  3. 总结一下linux中的分段机制

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 这篇文章主要说一下linux对于分段机制的处理,虽然都说linux不使用分段机制,但是分段机制属于CPU的一个功 ...

  4. Linux内存寻址之分段机制及分页机制【转】

    前言 本文涉及的硬件平台是X86,如果是其他平台的话,如ARM,是会使用到MMU,但是没有使用到分段机制: 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为 ...

  5. Linux内存寻址之分段机制

    前言 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为自己对分段分页机制已经理解了,结果发现其实是一知半解.于是,查找了很多资料,最终理顺了内存寻址的知识. ...

  6. Linux内核学习笔记3——分段机制和分页机制

    一 分段机制 1.什么是分段机制 分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存块单元. 2.什么是段 每个段由三个参数定义:段基地址.段限长和段属性. 段的基地址.段限长以及 ...

  7. Windows操作系统及其安全机制

    kali视频学习请看 http://www.cnblogs.com/lidong20179210/p/8909569.html Windows操作系统及其安全机制 Windows文件系统 FAT (F ...

  8. 操作系统篇-hello world(免系统运行程序)

     || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言     今天起开始分享关于操作系统的相关知识,本人也是菜鸟一个,正处于学习阶段,这整个操作系统篇也是我边学习边总结的一些结果,希 ...

  9. 段描述符表(GDT+LDT)的有感

    [0]写在前面 要知道,在汇编中,代码的装入顺序决定了在内存中的地址位置.所有的代码或者数据都在硬盘上,当调试或者启动的时候,加载到内存:当需要对数据进行处理的时候,我们通过将数据从内存载入到regi ...

随机推荐

  1. C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 template <class T> class sing ...

  2. H5单页面手势滑屏切换原理

    H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 ...

  3. .NET Core 系列5 :使用 Nuget打包类库

    NuGet是个开源项目,项目包括 NuGet VS插件/NuGet Explorer/NuGetServer/NuGet命令行等项目,.NET Core项目完全使用Nuget 管理组件之间的依赖关系, ...

  4. 1.初始Windows Server 2012 R2 Hyper-V + 系统安装详细

    干啥的?现在企业服务器都是分开的,比如图片服务器,数据库服务器,redis服务器等等,或多或少一个网站都会用到多个服务器,而服务器的成本很高,要是动不动采购几十台,公司绝对吃不消的,于是虚拟化技术出来 ...

  5. Android业务组件化之子模块SubModule的拆分以及它们之间的路由Router实现

    前言: 前面分析了APP的现状以及业务组件化的一些探讨(Android业务组件化之现状分析与探讨),以及通信的桥梁Scheme的使用(Android业务组件化之URL Scheme使用),今天重点来聊 ...

  6. ASP.NET Core的路由[2]:路由系统的核心对象——Router

    ASP.NET Core应用中的路由机制实现在RouterMiddleware中间件中,它的目的在于通过路由解析为请求找到一个匹配的处理器,同时将请求携带的数据以路由参数的形式解析出来供后续请求处理流 ...

  7. WebApi接口 - 如何在应用中调用webapi接口

    很高兴能再次和大家分享webapi接口的相关文章,本篇将要讲解的是如何在应用中调用webapi接口:对于大部分做内部管理系统及类似系统的朋友来说很少会去调用别人的接口,因此可能在这方面存在一些困惑,希 ...

  8. [译] C# 5.0 中的 Async 和 Await (整理中...)

    C# 5.0 中的 Async 和 Await [博主]反骨仔 [本文]http://www.cnblogs.com/liqingwen/p/6069062.html 伴随着 .NET 4.5 和 V ...

  9. C++ 事件驱动型银行排队模拟

    最近重拾之前半途而废的C++,恰好看到了<C++ 实现银行排队服务模拟>,但是没有实验楼的会员,看不到具体的实现,正好用来作为练习. 模拟的是银行的排队叫号系统,所有顾客以先来后到的顺序在 ...

  10. cesium核心类Viewer简介

    1.简单描述Viewer Viewer类是cesium的核心类,是地图可视化展示的主窗口,cesium程序应用的切入口,扮演必不可少的核心角色. 官网的英文解析如下: A base widget fo ...