首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Linux内存分段

Linux内存分段
EN

Stack Overflow用户
提问于 2019-05-22 00:15:20
回答 2查看 0关注 0票数 0

看一下Linux和内存管理的内部结构,我偶然发现了Linux使用的分段分页模型。

如果我错了,请纠正我,但Linux(保护模式)确实使用分页将线性虚拟地址空间映射到物理地址空间。由页面构成的线性地址空间被分成用于处理平面存储器模型的四个段,即:

  • 内核代码段(__KERNEL_CS);
  • 内核数据段(__KERNEL_DS);
  • 用户代码段(__USER_CS);
  • 用户数据段(__USER_DS);

存在称为空段的第五存储器段但未使用。

这些段的CPL(当前特权级别)为0(主管)或3(用户空间)。

为了简单起见,我将专注于32位内存映射,具有4GiB可地址空间,3GiB用于用户空间进程空间(以绿色显示),1GiB用于管理程序内核空间(以红色显示):

虚拟内存空间
虚拟内存空间

所以,红色部分由两个部分组成__KERNEL_CS,并__KERNEL_DS和两个段的绿色部分__USER_CS__USER_DS

这些部分彼此重叠。分页将用于userland和内核隔离。

然而,随着维基百科中提取这里

[...]许多32位操作系统通过将所有段的基数设置为0来模拟平坦存储器模型,以便对程序进行分段中立。

这里查看GDT的linux内核代码:

代码语言:javascript
复制
[GDT_ENTRY_KERNEL32_CS]       = GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),
[GDT_ENTRY_KERNEL_CS]         = GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),
[GDT_ENTRY_KERNEL_DS]         = GDT_ENTRY_INIT(0xc093, 0, 0xfffff),
[GDT_ENTRY_DEFAULT_USER32_CS] = GDT_ENTRY_INIT(0xc0fb, 0, 0xfffff),
[GDT_ENTRY_DEFAULT_USER_DS]   = GDT_ENTRY_INIT(0xc0f3, 0, 0xfffff),
[GDT_ENTRY_DEFAULT_USER_CS]   = GDT_ENTRY_INIT(0xa0fb, 0, 0xfffff),

正如彼得指出的那样,每个段从0开始,但那些标志是什么0xc09b0xa09b等等?我倾向于认为它们是段选择器,如果不是,如果它们的寻址空间都从0开始,我将如何从内核段访问用户域段?

不使用分段。仅使用分页。段的seg_base地址设置为0,将其空间扩展到0xFFFFF,从而提供完整的线性地址空间。这意味着逻辑地址与线性地址没有区别。

此外,由于所有段彼此重叠,是否提供存储器保护(即存储器分离)的寻呼单元?

分页提供保护,而不是分段。内核将检查线性地址空间,并根据边界(通常称为TASK_MAX)检查所请求页面的权限级别。

EN

Stack Overflow用户

发布于 2019-05-22 08:57:23

是的,Linux使用分页,因此所有地址始终是虚拟的。(为了访问已知物理地址的内存,Linux将所有物理内存1:1映射到一系列内核虚拟地址空间,因此它可以使用物理地址作为偏移量简单地索引到该“数组”。模数复杂度为32具有比内核地址空间更多的物理RAM的系统上的内核。)

由页面构成的该线性地址空间被分成四个段

不,Linux使用平面内存模型。所有4个段描述符的基数和限制为0和-1(无限制)。即它们全部重叠,覆盖整个32位虚拟线性地址空间。

所以,红色部分由两个部分组成__KERNEL_CS,并__KERNEL_DS

不,这是你出错的地方。 x86段寄存器用于分段; 它们是x86传统行李,仅用于x86-64上的CPU模式和权限级别选择。AMD没有为长模式添加新的机制并完全丢弃段,而只是在长模式下进行了绝对分段(基本固定为0,就像所有在32位模式中使用的所有人一样)并且仅将段用于机器配置目的而不是除非你实际编写的代码切换到32位模式或其他任何东西,否则特别有趣。

(除非您可以为FS和/或GS设置非零基础,Linux也会为线程本地存储设置。但这与copy_from_user()实现方式或任何内容无关。它只需要检查指针值,不参考段描述符的任何段或CPL / RPL。)

在32位传统模式中,可以编写使用分段内存模型的内核,但是没有一个主流操作系统实际上这样做。有些人希望这已成为一件事,但是,例如看到这个答案感叹x86-64使得Multics风格的操作系统变得不可能。但这不是 Linux的工作原理。

Linux是一个https://wiki.osdev.org/Higher_Half_Kernel,其中内核指针有一个值范围(红色部分),用户空间地址在绿色部分。如果映射了正确的用户空间页表,内核可以简单地取消引用用户空间地址,它不需要翻译它们或对段做任何事情; 这就是拥有平坦内存模型的意义。(内核可以使用“用户”页表项,但反之亦然)。有关x86-64的具体内容,请参阅https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt以获取实际的内存映射。

这4个GDT条目都需要分开的唯一原因是出于特权级别原因,并且数据与代码段描述符具有不同的格式。(GDT条目不仅包含基数/限制;这些是需要不同的部分。请参阅https://wiki.osdev.org/Global_Descriptor_Table

特别是https://wiki.osdev.org/Segmentation#Notes_Regarding_C,它描述了GDT通常被“普通”操作系统用于创建平面内存模型的方式和原因,每个权限级别都有一对代码和数据描述符。

对于32位Linux内核,只能gs为线程本地存储获取非零基础(因此寻址模式[gs: 0x10]会访问依赖于执行它的线程的线性地址)。或者在64位内核(和64位用户空间)中,Linux使用fs。(因为x86-64使GS特别使用该swapgs指令,旨在用于syscall内核查找内核堆栈。)

但无论如何,FS或GS的非零基数不是来自GDT条目,它们是根据wrgsbase指令设置的。(或者在不支持它的CPU上,写入MSR)。

但那些旗帜是什么0xc09b0xa09b等等?我倾向于认为他们是细分选择者

不,分段选择器是GDT的索引。内核使用指定的初始化语法将GDT定义为C数组[GDT_ENTRY_KERNEL32_CS] = initializer_for_that_selector

(实际上,选择器的低2位,即段寄存器值,是当前的特权级别。所以GDT_ENTRY_DEFAULT_USER_CS应该是`__USER_CS >> 2.)

mov ds, eax 触发硬件索引GDT,而不是线性搜索它以匹配内存中的数据!

GDT数据格式:

您正在查看x86-64 Linux源代码,因此内核将处于长模式而非保护模式。我们可以说,因为有单独的入口USER_CSUSER32_CS。32位代码段描述符的L位将被清除。当前的CS段描述是将x86-64 CPU置于32位compat模式与64位长模式之间的原因。要输入的32位用户空间,一个iretsysret将设置CS:RIP到用户模式32位的段选择。

认为你也可以让CPU处于16位compat模式(比如compat模式不是实模式,但默认的操作数大小和地址大小是16)。但是,Linux不会这样做。

无论如何,如https://wiki.osdev.org/Global_Descriptor_Table和Segmentation中所述,

每个段描述符包含以下信息:

  • 段的基地址
  • 段中的默认操作大小(16位/ 32位)
  • 描述符的特权级别(Ring 0 - > Ring 3)
  • 粒度(段限制为字节/ 4kb单位)
  • 分段限制(分段内的最大合法偏移)
  • 细分市场(是否存在)
  • 描述符类型(0 =系统; 1 =代码/数据)
  • 段类型(代码/数据/读/写/访问/符合/不符合/扩展/扩展)

这些是额外的比特。我并不特别感兴趣哪些位是因为我(我想)理解不同GDT条目的高级图片以及它们的作用,而没有深入了解实际编码的细节。

但是,如果您检查x86手册或osdev wiki以及这些init宏的定义,您应该会发现它们会导致GDT条目的L位设置为64位代码段,并为32位代码段清除。显然,类型(代码与数据)和权限级别不同。

票数 0
EN
查看全部 2 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/-100009046

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档