0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

图文详解Linux分段机制

程序员cxuan 来源:程序员cxuan 2023-05-08 16:24 次阅读

上一篇聊到分段机制是为了提供了隔绝代码、数据和堆栈区域的机制,能够使得多个程序运行在同一个内存空间中不会相互干扰,这是对内存平坦模型的一种保护。内存经过分段机制后会变为一个个的段,这称为多段模型。多段模型能够利用分段机制的功能提供由硬件增强代码、数据结构、程序和任务的保护措施。

现在我们知道了分段的目的是为了什么,但是我们好像还不知道什么是段,以及段有哪些特征。

段的定义

保护模式中的 80x86 架构提供了 4GB 的物理地址空间。这是 CPU 在地址总线上可以寻址的地址空间。这段地址空间是一种平坦模型地址空间,地址范围从 0 到 0xFFFFFFFF。

平坦模型:相对于多个段的模型来说,平坦模型指的就是一个段,比如在实模式下,处理器最大可寻址 64 KB(2 ^ 16)的地址空间,在保护模式下,处理器最大可寻址 4GB (2 ^ 32)的地址空间,如果访问超过最大地址空间的数据指令,需要重新指定段。

需要注意下的是,段地址 + 偏移地址确实能寻址 1MB 的地址空间,但这却不是平坦模型的访问方式,而是多段模型。

再来啰嗦一下分段机制的目的:分段机制就是把虚拟地址空间中的虚拟内存组织成一个个长度可变的段,这个段是虚拟地址到线性地址转换的基础,一般来说,段由三部分组成:

段基址(Base address):段的初始地址,可以认为是段的开始,段基址的段内偏移为 0 。

段限长(limit):该段最大可用的偏移位置,它定义了段的长度,也是段内偏移最大能够寻址到的位置。

段属性(Attributes):指的是该段的特性,比如段是否可读、是否可写、是否能够作为程序执行,段的特权级等。

段基址和段限长一起定义了段所映射的线性地址空间的范围。段内从 0 到 limit 地址范围会对应线性地址空间中的 base 到 base + limit ,偏移量是无法大于段限长的,如果偏移地址大于段限长会引发异常,除此之外,如果访问的这个段没有符合段属性,也会引发异常。

不同的段可以映射到相同的线性地址空间,这种映射是操作系统所允许的。也就是说不同的段可以在线性地址空间中覆盖或者完全重叠,如下图所示

e8a85c58-ec2e-11ed-90ce-dac502259ad0.png

段基址、段限长和段属性都存储在段描述符这个结构中,可以说段描述符就是能够查找段重要信息的结构,在虚拟地址到线性地址的转换过程中,就需要用到段描述符。那么段描述符被存储在哪里呢?段描述符被存储在段描述符表中,这个表就是一个数组,这个数组的下标就是段选择子,还记得我们上篇文章聊过段选择子吗?段选择子中的 Index 是这么描述的:

e8c77f48-ec2e-11ed-90ce-dac502259ad0.pngimage-20230415223616884

为了把逻辑地址转换成为线性地址,CPU 会执行以下操作:

使用段选择子中的 Index 属性通过查询 GDT/LDT 表定位相应的段描述符。

利用段描述符检验段的访问权限和范围,用于确保该段是可访问并且偏移量位于段界限内。

把段描述符中取得的段基地址加上 Index ,最后形成偏移地址。

e8da8ebc-ec2e-11ed-90ce-dac502259ad0.png

如果没有开启分页机制,那么此时的线性地址就等同于物理地址;如果开启分页机制,那么此时的线性地址会经过分页机制转换后才会把线性地址映射成为物理地址。

段选择子

上篇文章提到了段选择子,大致介绍了一下它的结构,并没有细致说明,这篇文章就来细致说明一下。

段选择子又称段选择符,它是一个 16 位的标识符,如下图所示,段选择子并不指向段,它指向段描述符表中的段描述符。

e8f643dc-ec2e-11ed-90ce-dac502259ad0.png

段选择子总共分为三个部分:

RPL(Request Privilege Level):请求特权级,表示进程应该以什么权限来访问段,数值越大权限越小。

TI(Table Indicator):表示应该查询哪个表,TI = 0 查 GDT 表;TI = 1 查 LDT 表。

Index:CPU 会自动将 Index * 8,在加上 GDT 和 LDT 中的段基址,就是要加载的段描述符。

下面是几幅段选择子的示意图,大家明白图中所示也就明白段选择子是如何表示的了。

e9030522-ec2e-11ed-90ce-dac502259ad0.png

需要注意的是,段选择子 0x0008 和 段选择子 0x000f 指向的是同一个段即段 1;段选择子 0x0010 和 段选择子 0x0017 也指向的是同一个段即段 2。段选择子 0ffff 指向段 8191,而段选择子 0x0000 指向的的是一个空段,因为 CPU 不使用 GDT 表中的第一项,所以指向该段的选择子用作空选择子。当把空选择子加载到段寄存器(CS 和 SS 除外)中时,处理器不产生异常,但是当含有空选择子的段寄存器用于访问内存时,会产生异常。把空选择子加载到 CS 和 SS 中也会产生异常。

段选择子 a b c d 分别指向 linux 0.1x 中的内核代码段、内核数据段、任务代码段和任务数据段。

一般把段选择子放在段寄存器中,每个寄存器支持特定类型的内容引用,这部分引用可以是代码、数据或者堆栈;每个程序都会把有效的段选择子加载到 CS、SS 或者是 DS 中,另外,处理器还提供了另外三个段寄存器即 FS、GS、ES 作为辅助,这三个寄存器提供当前 CPU 访问段寄存器不够时使用。

e9214f0a-ec2e-11ed-90ce-dac502259ad0.png

从上图可以看到,每个段寄存器都由两部分组成,一部分是段选择子,一部分是 "段基址、段限长和段属性信息",段选择子是存在于段寄存器中显示的部分,而段基址、段限长和段属性是隐藏部分。

为什么会有隐藏部分呢?

隐藏部分也被称为描述符缓冲或者是影子寄存器,当一个段选择子被加载到段寄存器中可见部分时,处理器也会同时把段基址等信息加载到段寄存器的隐藏部分,缓存在段寄存器中隐藏部分使得处理器在进行地址转换的时候不用再去段描述符中读取段的相关信息。

段寄存器中的隐藏部分相当于是段描述符的一个镜像,或者说是拷贝。因此操作系统必须要确保对段描述符的改动反映在描述符缓冲中,如果更改了段描述符却没有在描述符缓冲中进行修改,就会造成段不一致的现象。所以最快捷的方法就是在对描述附表做过改动之后就立刻重新加载 6 个段寄存器。这将会把描述附表中的相应段信息加载到描述符缓冲中。

处理器提供了两类加载指令用于加载段的相关信息:

一类是 MOV、POP、LDS、LES、LSS、LGS 以及 LFS 指令,这些指令显示的直接引用段寄存器;

一类是隐式加载指令,例如 CALL、JMP 和 RET 指令、IRET、INTn、INTO 和 INT3 等指令。这些指令在操作过程中会附带改变 CS 寄存器的内容。

段描述符

段描述符是 GDT 和 LDT 表中的一个数据项,用于向处理器提供有关一个段的位置和大小信息以及访问控制的状态信息。每个段描述符长度是 8 字节,含有三个主要字段:段基址、段限长和段属性,其他是一些细节字段。段描述符通常是由编译器、链接器、加载器或者操作系统来创建。

e930700c-ec2e-11ed-90ce-dac502259ad0.png

这是一个比较详细的段描述符的结构,下面来具体介绍一下这些字段的含义:

段限长字段 LIMIT --- Segment limit field

段限长用于指定段的长度,处理器会把段描述符中两个段限长字段组合成一个 20 位的值,并且根据颗粒度标志 G 来指定段限长 Limit 值的实际含义。如果 G = 0,则段长度 Limit 范围可以从 1 到 1MB 字节。如果 G = 1,则段长度 Limit 的范围可以是从 4KB 到 4GB ,单位是 4KB。

基地址字段 BASE --- Base address field

这个字段定义在 4GB 线性地址空间中一个段字节 0 所处的位置。处理器会把 3 个分立的基地址字段组合成为一个 32 位的值,段基址应该对其 16 字节边界,这样做性能比较高。

段类型字段 TYPE --- Type field

类型字段指定段或门(Gate)的类型、说明段的访问种类以及段的扩展方向。这个字段依赖与描述符类型标志 S 指明的是一个应用描述符还是系统描述。TYPE 字段的编码对代码、数据或系统描述符都不同。

e93f8cd6-ec2e-11ed-90ce-dac502259ad0.png

e961abfe-ec2e-11ed-90ce-dac502259ad0.png

e970c8e6-ec2e-11ed-90ce-dac502259ad0.png

描述符类型标志 S --- Descriptor type flag

表明描述符的类型,0 - 表示系统描述符,1 - 代码或数据段描述符。

描述符特权级 --- DPL Descriptor priviledge level

DPL 表示描述符的特权级,特权级范围从 0 - 3 ,3 最低,0 最高,DPL 用于控制对段的访问;

我在内核访问相关的描述中也提到了一个特权级,大家还记得是啥吗?

段存在标志 --- P Segment present

P 标志位表示一个段是在内存中 p = 1 还是不在内存中 p = 0。当段描述符的 P 标志为 0 时,那么把指向这个段描述符的选择符加载进段寄存器将导致产生一个段不存在异常。

D/B --- 默认操作大小/默认栈指针大小和/或上界限 Default operation size/default stack pointer size and/or upper bound

根据段描述符表示的是可执行代码段、下扩数据段还是堆栈段,这个标志具有不同的功能(如果是 32 位,这个标志应该设置为 1,16 位应该设置为 0 )。如果是可执行代码段时,这个标志是 D 标志;如果是栈段和下扩数据段,这个标志是 B 标志;

颗粒度标志 --- G Granularity

这个字段用于确定段限长字段 Limit 值的单位,如果颗粒度标志为 0 ,则段限长值的单位是字节;如果设置了颗粒度标志,则段限长使用 4KB 单位。

可用和保留比特位 --- Available and reserved bits

段描述符的第 2 个双字的位 20 供系统软件使用,位 21 是保留位并且设置为 0 。

段描述符表

段描述符表是存储段描述符的一个数组,索引是由段选择子提供。段描述符表的长度可变,最多可以包含 8192 个 8 字节的描述符,段描述符有两种:即全局描述符表(Global descriptor table)和局部描述符表(Local descriptor table)。

e991f4c6-ec2e-11ed-90ce-dac502259ad0.png

描述符表由操作系统中的特殊数据结构来维护着,并且由内存管理硬件来引用。虚拟内存空间被分割成大小相等的两半,一半由 GDT 来映射变换成为线性地址,一半由 LDT 来映射,由于段描述符表最大可以包含 8192 个 8 字节的描述符,也就是 2 ^ 13 = 8192 ,所以整个虚拟地址空间是 2 ^ 14 = 16384 个段了,通过指定 TI = 1 or 0 就可以查找到指令的段描述符。

当发生任务切换时,LDT 会更换成新任务的 LDT,但是 GDT 内容却不会变。因此可以看出,GDT 相当于是全局共有的,系统中所有任务共享的段用 GDT 来映射,而 LDT 是当前任务特有的,可以把 LDT 看成是操作系统的数据。

下面是一副关于 GDT、LDT 的映射图。

e9b0dd50-ec2e-11ed-90ce-dac502259ad0.png

上图中共有六个段,分别是用于任务 A 的 CodeA 、DataA,任务 B 的 CodeB、DataB,用于操作系统的 Codeos 和 Dataos,系统中的任务 A 和 任务B 分别是两个不同的应用程序,并且每个任务都有自己的 Code 和 Data,在各自的 LDT 表中保存着 Code 和 Data。包含操作系统内核的两个段 Codeos 和 Dataos 在 GDT 中映射,并且 GDT 表示任务 A 和任务 B 共同享有的全局映射,GDT 表还保存着 LDTA 和 LDTB。

当任务 A 在运行时,可访问的段包括 LDTA 的 CodeA 和 DataA,加上 GDT 映射的 Codeos 和 Dataos,任务 B 运行时,可访问的段包括 LDTB 的 CodeB 和 DataB,加上 GDT 映射的 Codeos 和 Dataos 。任务 A 在运行时,是无法访问任务 B 的两个段的;同样的任务 B 在运行时,也是无法访问任务 A 的,这正是虚拟地址提供的保护机制,还记得上篇文章写到的吗?

e9c68308-ec2e-11ed-90ce-dac502259ad0.png

GDT 本身并不是一个段,它只是线性地址空间中的一个数据结构。GDT 的基地址 (Base Address)+ 段长度(Limit)会被直接加载进 GDTR 寄存器中。GDT 的基地址应该进行内存 8 字节对齐,用已得到最佳的处理性能。GDT 的限长以字节为单位。

处理器并不会使用 GDT 表中的第 1 个描述符,第 1 个描述符也是空描述符,把这个描述符加载进数据段寄存器 DS、FS、GS 和 ES 后不会产生异常,但是使用空描述符的段选择符访问内存时就肯定会产生一般保护性异常。

访问 LDT 表需要使用其段选择符,为了在访问 LDT 时减少地址转换次数,LDT 的段选择符、基地址、段限长和访问权限需要存储在 LDTR 寄存器中。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    18288

    浏览量

    222181
  • 寄存器
    +关注

    关注

    30

    文章

    5032

    浏览量

    117746
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10446

    浏览量

    206572
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206738
  • 数据结构
    +关注

    关注

    3

    文章

    564

    浏览量

    39900

原文标题:图文详解 Linux 分段机制!

文章出处:【微信号:cxuangoodjob,微信公众号:程序员cxuan】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    逆变器电路DIY(图文详解)

    ` 本帖最后由 eehome 于 2013-1-5 09:55 编辑 逆变器电路DIY(图文详解)`
    发表于 08-15 18:59

    详解Linux内核抢占实现机制

    本文详解Linux内核抢占实现机制。首先介绍了内核抢占和用户抢占的概念和区别,接着分析了不可抢占内核的特点及实时系统中实现内核抢占的必要性。然后分析了禁止内核抢占的情况和内核抢占的时机,最后介绍了实现抢占内核所做的改动以及何时
    发表于 08-06 06:16

    详解Linux能力机制

    Linux能力机制
    发表于 04-20 08:23

    图文详解T60机器拆解过程

    图文详解T60机器拆解过程.
    发表于 04-24 15:12 38次下载

    IBM X40拆机图文详解

    IBM X40拆机图文详解
    发表于 12-09 14:50 0次下载

    linux内核rcu机制详解

    Linux内核源码当中,关于RCU的文档比较齐全,你可以在 /Documentation/RCU/ 目录下找到这些文件。Paul E. McKenney 是内核中RCU源码的主要实现者,他也写了很多RCU方面的文章。今天我们而主要来说说linux内核rcu的
    发表于 11-13 16:47 8532次阅读
    <b class='flag-5'>linux</b>内核rcu<b class='flag-5'>机制</b><b class='flag-5'>详解</b>

    供电系统电气图识图使用图文详解

    供电系统电气图识图使用图文详解
    的头像 发表于 07-06 11:51 1.9w次阅读
    供电系统电气图识图使用<b class='flag-5'>图文</b><b class='flag-5'>详解</b>

    LED搭接光立方点阵的图文详解

    本文档的主要内容详细介绍的是LED搭接光立方点阵的图文详解资料免费下载。
    发表于 06-28 08:00 23次下载
    LED搭接光立方点阵的<b class='flag-5'>图文</b><b class='flag-5'>详解</b>

    图文详解:C++虚表的剖析

    图文详解:C++虚表的剖析
    的头像 发表于 06-29 14:23 2295次阅读
    <b class='flag-5'>图文</b><b class='flag-5'>详解</b>:C++虚表的剖析

    图文详解:C++的输出输入

    图文详解:C++的输出输入
    的头像 发表于 06-29 14:53 3118次阅读
    <b class='flag-5'>图文</b><b class='flag-5'>详解</b>:C++的输出输入

    图文详解:信号的时域和空域特性

    图文详解:信号的时域和空域特性
    发表于 07-15 10:25 2次下载
    <b class='flag-5'>图文</b><b class='flag-5'>详解</b>:信号的时域和空域特性

    图文详解:从零开始学电源资源下载

    图文详解:从零开始学电源资源下载
    发表于 04-25 16:11 84次下载
    <b class='flag-5'>图文</b><b class='flag-5'>详解</b>:从零开始学电源资源下载

    图文详解:无刷电机的绕制和接线方法

    图文详解:无刷电机的绕制和接线方法
    发表于 05-25 11:48 123次下载

    一文详解linux的分页模型

    也就是我们实际中编码时遇到的内存地址并不是对应于实际内存上的地址,我们编码中使用的地址是一个逻辑地址,会通过分段和分页这两个机制把它转为物理地址。而由于linux使用的分段
    的头像 发表于 05-18 08:59 1808次阅读
    一文<b class='flag-5'>详解</b><b class='flag-5'>linux</b>的分页模型

    图文详解Linux分页机制

    分页机制是 80x86 内存管理机制的第二种机制分段机制用于把虚拟地址转换为线性地址,而分页机制
    发表于 05-30 09:10 299次阅读
    <b class='flag-5'>图文</b><b class='flag-5'>详解</b><b class='flag-5'>Linux</b>分页<b class='flag-5'>机制</b>