四、保护模式之内存分段管理机制

江枫思渺然 提交于 2019-12-02 18:51:07

    以我的理解,内存的分段管理机制,就是人为的把内存分成几个小段(Segment),各段独立使用。

    为什么要把内存分成很多小段使用呢?原因一是要多个程序同时运行:开QQ勾搭妹子,开网易云音乐听歌,开浏览器看网页。。。。。。好几个程序一起开着呢!如果几个程序都用同一块内存,那就乱了!所以,把内存分成几段:QQ用一段,别的程序不能访问;网易云音乐用另一段,其他程序也不能访问。。。。。。原因二是不同程序的权力不一样:操作系统可以启动、关闭应用程序,应用程序可不能对操作系统指手画脚,也就是各段内存的被访问权限是不同的。

    很明显,要准确定义一个段要三个参数:该段从物理内存的哪个位置开始(段开始的实际内存地址)、该段占用多大的内存空间(段的长度)、该段能被哪些程序访问(段的属性),用专业术语说就是段基址(Segment Base)、段界限(Segment Limit)、段属性(Segment Attritbute)。

    用一个数据结构来描述段的三个属性,这个数据结构就叫描述符(Descriptor)。由于历史的原因(为了和80286兼容),这个数据结构看起来相当纠结:三个参数竟然不各自独立连续存放,而是被拆开混存的!

; 段描述符图示

;

;  ------ ┏━━┳━┓内存高地址

;            ┃ 7 ┃ 段  ┃

;            ┣━━┫ 基  ┃

;            ┆    ┆ 址  ┆

;  字节   ┆    ┆ 高  ┆

;   7       ┣━━┫ 8   ┃

;            ┃ 0 ┃ 位 ┃

;  ------ ┣━━╋

;            ┃ 7 ┃ G  ┃

;            ┣━━╉━━┨

;            ┃ 6 ┃D/B┃

;            ┣━━╉━━┨

;            ┃ 5 ┃ 未  ┃

;            ┣━━┫ 定  ┃

;            ┃ 4 ┃ 义  ┃

;  字节   ┣━━╉━━┨

;   6       ┃ 3 ┃      ┃

;            ┣━━┫ 段  ┃

;            ┃ 2 ┃ 界  ┃

;            ┣━━┫ 限  ┃

;            ┃ 1 ┃ 高  ┃

;            ┣━━┫ 4   ┃

;            ┃ 0 ┃ 位  ┃

;  ------ ┣━━╋━┫

;            ┃ 7 ┃ P   ┃

;            ┣━━╉━━┨

;            ┃ 6 ┃ D   ┃

;            ┣━━┫ P   ┃

;            ┃ 5 ┃ L   ┃

;            ┣━━╉━━┨

;            ┃ 4 ┃ S   ┃

;  字节   ┣━━╉━┨

;   5      ┃ 3 ┃      ┃

;           ┣━━┫ T   ┃

;           ┃ 2 ┃ Y   ┃

;           ┣━━┫ P   ┃

;           ┃ 1 ┃ E   ┃

;           ┣━━┫      ┃

;           ┃ 0 ┃      ┃

;  ----- ┣━━╋━━┫

;           ┃23┃     ┃

;           ┣━━┫     ┃

;           ┃22┃ 段 ┃

;           ┣━━┫ 基 ┃

;           ┆    ┆ 址 ┆

;  字节  ┆    ┆ 低 ┆

; 2,3  ┣━━┫ 24 ┃

;   4      ┃ 1 ┃ 位 ┃

;           ┣━━┫     ┃

;           ┃ 0 ┃     ┃

;  ----- ┣━━╋━━┫

;           ┃15┃     ┃

;           ┣━┫     ┃

;           ┃14┃ 段 ┃

;           ┣━━┫ 界 ┃

;           ┆    ┆ 限 ┆

;  字节   ┆    ┆ 低 ┆

;  0,1   ┣━━┫ 16┃

;            ┃ 1 ┃ 位 ┃

;            ┣━━┫     ┃

;            ┃ 0 ┃     ┃

;  ------ ┗━━┻━━┛内存低地址

    段基址和段界限好理解。段属性包含的内容好多,今天先跳过不管,以后再补吧!

   从上面的宏知道一个段描述符长 8 字节,即 8 * 8 = 64 位。如果直接通过一个 64 位的段描述符来引用一个段的时候,就必须使用一个 64 位长的段寄存器装入这个段描述符。但Intel为了保持向后兼容,将段寄存器仍然规定为 16 位,找不到 64 位长度的段寄存器来直接引用 64 位的段描述符。怎么办?

    既然内存被分成了很多段,每个段都由一个段描述符定义,这些个数量不定的段描述符可以用线性表来存储。这个存放描述符的线性表就叫段描述符表(Descriptor Table)。要引用描述符表中的某一项,只要知道该描述符在描述符表中的序号就行,即描述符索引(Index),专业术语叫段选择子(Segment Selector)。现在好办了,旧的 16 位段寄存器就可以通过将段寄存器中的值作为下标索引来间接引用 64 位的段描述符了。

    实际上,段选择子里除了放了描述符索引之外,还存了描述符表指示位(Table Index)、请求特权级(Requested Privilege Level)。一个段选择子 16 位,低 2 位(第0、1位)存放请求特权级,用于特权检查;第 2 位存放描述符指示位,= 0 表示从全局描述符表中读取描述符, = 1 表示从局部描述符表中读取描述符;高 13 位存放描述符索引,即描述符在描述符表中的序号。管理全部物理内存的描述符表叫全局描述符表(Global Descriptor Table,GDT)。实际上,在 GDT 中存放的不仅仅是段描述符,还有其它描述符。

    还有两个问题,描述符表和段选择子放在哪里呢?怎么访问呢?

    描述符表必然是放在内存里的,而且可以被放在内存的任何位置。实际上全局描述符表本身也占用一个内存段,只不过这个段比较特殊,不能用一个段描述符定义后作为一个表项存放到别的描述符表中去。全局描述符表需要在定义后,程序员自己保存其基址和界限。有一个寄存器 GDTR 专门用来存放 GDT 的入口地址,可以通过 LGDT 指令将 GDT 的入口地址装入此寄存器,CPU 根据此寄存器中的内容作为 GDT 的入口来访问 GDT。

    段选择子存放在段寄存器里!实模式下段寄存器里存放段地址,保护模式下段寄存器里存放段选择子,段寄存器的值就是段选择子。要访问内存的某个段,先要把该段的选择子加载到某一个段寄存器。CPU 提供了 6 个段寄存器:CS、DS、SS、ES、FS、GS,即同一时刻有 6 个段可供立即访问。实际上每个段寄存器都支持某种特定类型(代码、数据、堆栈)的内存引用:代码段 CS,数据段 DS,堆栈段 SS,辅助数据段 ES、FS、GS。

    有两点建议:

1、LDT 只是一个可选的数据结构,你完全可以不用它。使用它或许可以带来一些方便性,但同时也带来复杂性,如果你想让你的OS内核保持简洁性,以及可移植性,则最好不要使用它。

2、如果一个 OS 不使用虚拟内存,段模式会是一个不错的选择,无需考虑页模式(实际是段页模式)。

    在启动阶段只需要初步设置一下 GDT,等真正进入保护模式,启动了 Kernel 之后,具体 OS 打算如何设置 GDT,使用何种内存管理模式,由 Kernel 自身来设置,启动只需要给 Kernel 的数据段和代码段设置全部线性空间就可以了。


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!