Linux 不使用分段而只使用分页吗?

Tim*_*Tim 33 linux kernel virtual-memory

Linux 编程接口显示了进程的虚拟地址空间的布局。图中的每个区域都是一个段吗?

在此处输入图片说明

了解 Linux 内核

下面的意思是MMU中的分段单元将段和段内的偏移量映射到虚拟内存地址,然后分页单元将虚拟内存地址映射到物理内存地址是否正确?

内存管理单元(MMU)通过称为分段单元的硬件电路将逻辑地址转换为线性地址;随后,称为分页单元的第二个硬件电路将线性地址转换为物理地址(见图 2-1)。

在此处输入图片说明

那为什么说Linux不使用分段而只使用分页呢?

分段已包含在 80x86 微处理器中,以鼓励程序员将他们的应用程序拆分为逻辑相关的实体,例如子程序或全局和本地数据区。但是, Linux 以非常有限的方式使用分段。实际上,分段和分页是有些多余的,因为两者都可以用来分隔进程的物理地址空间:分段可以为每个进程分配不同的线性地址空间,而分页可以将相同的线性地址空间映射到不同的物理地址空间. 出于以下原因,Linux 更喜欢分页而不是分段:

• 当所有进程使用相同的段寄存器值时,即当它们共享同一组线性地址时,内存管理更简单。

• Linux 的设计目标之一是可移植到广泛的体系结构中。特别是 RISC 体系结构对分段的支持有限。

2.6 版本的 Linux 仅在 80x86 架构需要时才使用分段。

sou*_*edi 24

x86-64 架构在长模式(64 位模式)下不使用分段。

四个段寄存器:CS、SS、DS 和 ES 强制为 0,限制为 2^64。

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

操作系统不再可能限制可用的“线性地址”范围。因此它不能使用分段来保护内存;它必须完全依赖分页。

不要担心 x86 CPU 的细节,这些细节仅适用于在传统 32 位模式下运行时。用于 32 位模式的 Linux 使用不多。它甚至可以被认为是“多年处于良性忽视状态”。请参阅Fedora [LWN.net, 2017] 中的32 位 x86 支持

(碰巧 32 位 Linux 也不使用分段。但是你不需要相信我,你可以忽略它:-)。

  • 这有点夸大其词了。对于传统的原始 8086 段 (CS/DS/ES/SS),长模式下的基数/限制固定为 0/-1,但 FS 和 GS 仍然具有任意的段基数。并且加载到 CS 中的段描述符决定了 CPU 是在 32 位模式还是 64 位模式下执行。x86-64 Linux 上的用户空间使用 FS 进行线程本地存储(`mov eax, [fs:rdi + 16]`)。内核使用 GS(在 `swapgs` 之后)在 `syscall` 入口点中查找当前进程的内核堆栈。但是,是的,分段不用作主要操作系统内存管理/内存保护机制的一部分。 (2认同)

ilk*_*chu 12

图中的每个区域都是一个段吗?

不。

虽然分段系统(在 x86 上处于 32 位保护模式)旨在支持单独的代码、数据和堆栈段,但实际上所有段都设置为相同的内存区域。也就是说,它们从 0 开始并在内存(*)的末尾结束。这使得逻辑地址和线性地址相等。

这称为“平面”内存模型,比具有不同段然后在其中包含指针的模型要简单一些。特别是,分段模型需要更长的指针,因为除了偏移指针之外还必须包括段选择器。(16 位段选择器 + 32 位偏移量,总共 48 位指针;而只是一个 32 位平面指针。)

除了平面内存模型之外,64 位长模式甚至不真正支持分段。

如果您要在 286 上以 16 位保护模式进行编程,您将需要更多的段,因为地址空间是 24 位,而指针只有 16 位。

(* 请注意,我不记得 32 位 Linux 如何处理内核/用户空间分离。分段将允许通过设置用户空间段限制来实现这一点,以便它们不包括内核空间。分页允许这样做,因为它提供了一个每页保护级别。)

那为什么说Linux不使用分段而只使用分页呢?

x86 仍然有段,你不能禁用它们。它们只是尽可能少地使用。在 32 位保护模式下,需要为平面模型设置段,即使在 64 位模式下,它们仍然存在。


Ral*_*edl 9

因为 x86 有段,所以不可能不使用它们。但是cs(代码段)和ds(数据段)基地址都设置为零,因此实际上没有使用分段。线程本地数据是一个例外,通常未使用的段寄存器之一指向线程本地数据。但这主要是为了避免为此任务保留通用寄存器之一。

并不是说 Linux 不在 x86 上使用分段,因为这是不可能的。您已经强调了一部分,Linux 以非常有限的方式使用分段。第二部分是Linux 仅在 80x86 架构需要时才使用分段

您已经引用了原因,分页更容易且更便携。


Pet*_*des 6

图中的每个区域都是一个段吗?

这些是“segment”这个词的两种几乎完全不同的用法

  • x86 分段/段寄存器:现代 x86 操作系统使用平面内存模型,其中所有段在 32 位模式下具有相同的 base=0 和 limit=max,与硬件在 64 位模式下强制执行相同,使分段有点残留. (除了 FS 或 GS​​,即使在 64 位模式下也用于线程本地存储。)
  • 链接器/程序加载器部分/段。(ELF文件格式中section和segment的区别是什么

惯例有共同的起源:如果使用分段存储器模型(特别是在没有分页的虚拟存储器),则可能必须的数据和BSS的地址是相对于DS段的基础上,相对于SS基堆,并以相对码CS基地址。

因此,多个不同的程序可以加载到不同的线性地址,甚至可以在启动后移动,而无需更改相对于段基址的 16 位或 32 位偏移量。

但是你必须知道一个指针相对于哪个段,所以你有“远指针”等等。(实际的 16 位 x86 程序通常不需要将其代码作为数据访问,因此可以在某处使用 64k 代码段,也可能是另一个具有 DS=SS 的 64k 块,堆栈从高偏移量向下增长,数据位于底部。或者所有段基数相等的微小代码模型)。


x86 分段如何与分页交互

32 / 64 位模式下的地址映射为:

  1. 段:偏移量(保存偏移量的寄存器隐含的段基数,或用指令前缀覆盖)
  2. 32 或 64 位线性虚拟地址 = 基址 + 偏移量。(在像 Linux 这样的平面内存模型中,指针 / 偏移量也 = 线性地址。访问相对于 FS 或 GS​​ 的 TLS 时除外。)
  3. 页表(由 TLB 缓存)线性映射到 32(传统模式)、36(传统 PAE)或 52 位 (x86-64) 物理地址。(/sf/ask/3255640671/)。

    此步骤是可选的:必须在启动期间通过在控制寄存器中设置位来启用分页。如果没有分页,线性地址就是物理地址。

注意,分割并不会让你在单个进程(或线程)使用的虚拟地址空间的超过32位或64位,这是因为扁平(线性)地址空间一切被映射到仅具有相同数目的位作为偏移量本身。(这不是 16 位 x86 的情况,其中分段实际上对于使用超过 64k 的内存以及主要是 16 位寄存器和偏移量很有用。)


CPU 缓存从 GDT(或 LDT)加载的段描述符,包括段基址。当您取消引用一个指针时,根据它所在的寄存器,它默认为 DS 或 SS 作为段。寄存器值(指针)被视为与段基址的偏移量。

由于段基数通常为零,因此 CPU 会对此进行特殊处理。或者从另一个角度来看,如果您确实有一个非零段基址,则加载会有额外的延迟,因为绕过添加基址的“特殊”(正常)情况不适用。


Linux 如何设置 x86 段寄存器:

CS/DS/ES/SS的base和limit在32位和64位模式下都是0/-1。这被称为平面内存模型,因为所有指针都指向相同的地址空间。

(AMD CPU 架构师通过为 64 位模式强制执行平面内存模型来消除分段,因为主流操作系统无论如何都不使用它,除了 no-exec 保护,它通过使用 PAE 或 x86 分页以更好的方式提供 - 64 页表格式。)

  • TLS(线程本地存储):FS 和 GS在长模式下固定在 base=0。(它们是 386 的新内容,没有被任何指令隐式使用,甚至不是rep使用 ES的-string 指令)。x86-64 Linux 将每个线程的 FS 基地址设置为 TLS 块的地址。

    例如,mov eax, [fs: 16]将 16 字节中的 32 位值加载到该线程的 TLS 块中。

  • CS 段描述符选择 CPU 处于什么模式(16/32/64 位保护模式/长模式)。Linux 为所有 64 位用户空间进程使用单个 GDT 条目,为所有 32 位用户空间进程使用另一个 GDT 条目。(要使 CPU 正常工作,还必须将 DS/ES 设置为有效条目,SS 也是如此)。它还选择权限级别(内核(环0)vs.用户(环3)),因此即使返回64位用户空间,内核仍然要安排CS更改,使用iretsysret代替正常跳转或 ret 指令。

  • 在 x86-64 中,syscall入口点用于swapgs将 GS 从用户空间的 GS 翻转到内核的 GS,用于查找此线程的内核堆栈。(线程本地存储的特例)。该syscall指令不会将堆栈指针更改为指向内核堆栈;当内核到达入口点1时,它仍然指向用户堆栈。

  • DS/ES/SS 也必须设置为有效的段描述符,以便 CPU 在保护模式/长模式下工作,即使在长模式下忽略这些描述符的基数/限制。

因此,基本上 x86 分段用于 TLS,以及硬件要求您执行的强制性 x86 osdev 内容。


脚注 1:有趣的历史:在 AMD64 芯片发布前几年,内核开发人员和 AMD 架构师之间有邮件列表存档消息,导致对设计进行了调整,syscall因此它可以使用。有关详细信息,请参阅此答案中的链接。