草庐IT

Linux内存分段

coder 2023-06-19 原文

在研究 Linux 和内存管理的内部结构时,我偶然发现了 Linux 使用的分段分页模型。

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

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

  • 称为 Null 段的第五个内存段存在但未使用。

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

    为简单起见,我将重点介绍 32 位内存映射,其中 4GiB 可寻址空间,3GiB 用于用户级进程空间(以绿色显示),1GiB 用于主管内核空间(以红色显示):



    所以红色部分由两部分组成 __KERNEL_CS__KERNEL_DS ,以及两段的绿色部分__USER_CS__USER_DS .

    这些部分相互重叠。 分页将用于用户态和内核隔离。

    然而,正如摘自维基百科 here :

    [...] many 32-bit operating systems simulate a flat memory model by setting all segments' bases to 0 in order to make segmentation neutral to programs.



    查看 GDT 的 linux 内核代码 here :
    [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 开始,但是那些标志是什么,即 0xc09b , 0xa09b等等?我倾向于相信它们是段选择器,如果不是,如果它们的寻址空间都从 0 开始,我将如何从内核段访问用户空间段?

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

    此外,由于所有段彼此重叠,它是提供内存保护(即内存分离)的分页单元吗?

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

    最佳答案

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

    This linear address space constituted of pages, is split into four segments



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

    So the red part consists of two segments __KERNEL_CS and __KERNEL_DS



    不,这是你出错的地方。 x86 段寄存器不用于分段;它们是 x86 遗留包,仅用于 x86-64 上的 CPU 模式和特权级别选择 . AMD 并没有为此添加新机制并完全为长模式删除段,而是在长模式下中性分段(基数固定为 0,就像在 32 位模式下使用的每个人一样),并仅将段用于机器配置目的,而不是特别有趣,除非您实际上正在编写切换到 32 位模式或其他方式的代码。

    (除了你可以为 FS 和/或 GS 设置一个非零基数,而 Linux 为线程本地存储这样做。但这与 copy_from_user() 的实现方式或任何事情无关。它只需要检查指针值,不引用任何段或段描述符的 CPL/RPL。)

    在 32 位遗留模式下,可以编写使用分段内存模型的内核,但实际上没有一个主流操作系统这样做。不过,有些人希望这已经成为一种东西,例如见 this answer lamenting x86-64 making a Multics-style OS impossible .但这不是 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 使用 swapgs 指令使 GS 变得特别,旨在与 syscall 一起使用,以便内核找到内核堆栈。)

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

    but what are those flags, namely 0xc09b, 0xa09b and so on ? I tend to believe they are the segments selectors



    不,段选择器是 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_CS 有单独的条目。和 USER32_CS . 32 位代码段描述符将有其 L位清除。当前的 CS 段描述是将 x86-64 CPU 置于 32 位兼容模式与 64 位长模式的原因。要进入 32 位用户空间,一个 iretsysret将 CS:RIP 设置为用户模式 ​​32 位段选择器。

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

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

    Each segment descriptor contains the following information:

    • The base address of the segment
    • The default operation size in the segment (16-bit/32-bit)
    • The privilege level of the descriptor (Ring 0 -> Ring 3)
    • The granularity (Segment limit is in byte/4kb units)
    • The segment limit (The maximum legal offset within the segment)
    • The segment presence (Is it present or not)
    • The descriptor type (0 = system; 1 = code/data)
    • The segment type (Code/Data/Read/Write/Accessed/Conforming/Non-Conforming/Expand-Up/Expand-Down)


    这些是额外的位。我对哪些位不是特别感兴趣,因为我(认为我)了解不同 GDT 条目的用途及其作用的高级图片,而没有深入了解其实际编码方式的细节。

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

    关于Linux内存分段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56213569/

    有关Linux内存分段的更多相关文章

    1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    2. ruby-on-rails - Ruby 中的内存模型 - 2

      ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

    3. 键删除后 ruby​​ 哈希内存泄漏 - 2

      你好,我无法成功如何在散列中删除key后释放内存。当我从哈希中删除键时,内存不会释放,也不会在手动调用GC.start后释放。当从Hash中删除键并且这些对象在某处泄漏时,这是预期的行为还是GC不释放内存?如何在Ruby中删除Hash中的键并在内存中取消分配它?例子:irb(main):001:0>`ps-orss=-p#{Process.pid}`.to_i=>4748irb(main):002:0>a={}=>{}irb(main):003:0>1000000.times{|i|a[i]="test#{i}"}=>1000000irb(main):004:0>`ps-orss=-p

    4. ruby-on-rails - HTTParty 的内存问题和下载大文件 - 2

      这会导致Ruby出现内存问题吗?我知道如果大小超过10KB,Open-URI会写入TempFile。但是HTTParty会在写入TempFile之前尝试将整个PDF保存到内存吗?src=Tempfile.new("file.pdf")src.binmodesrc.writeHTTParty.get("large_file.pdf").parsed_response 最佳答案 您可以使用Net::HTTP。参见thedocumentation(特别是标题为“流媒体响应机构”的部分)。这是文档中的示例:uri=URI('http://e

    5. 【Linux操作系统】——网络配置与SSH远程 - 2

      Linux操作系统——网络配置与SSH远程安装完VMware与系统后,需要进行网络配置。第一个目标为进行SSH连接,可以从本机到VMware进行文件传送,首先需要进行网络配置。1.下载远程软件首先需要先下载安装一款远程软件:FinalShell或者xhell7FinalShellxhell7FinalShell下载:Windows下载http://www.hostbuf.com/downloads/finalshell_install.exemacOS下载http://www.hostbuf.com/downloads/finalshell_install.pkg2.配置CentOS网络安装好

    6. Linux磁盘分区中物理卷(PV)、卷组(VG)、逻辑卷(LV)创建和(LVM)管理 - 2

      文章目录一基础定义二创建逻辑卷2-1准备物理设备2-2创建物理卷2-3创建卷组2-4创建逻辑卷2-5创建文件系统并挂载文件三扩展卷组和缩减卷组3-1准备物理设备3-2创建物理卷3-3扩展卷组3-4查看卷组的详细信息以验证3-5缩减卷组四扩展逻辑卷4-1检查卷组是否有可用的空间4-2扩展逻辑卷4-3扩展文件系统五删除逻辑卷5-1备份数据5-2卸载文件系统5-3删除逻辑卷5-4删除卷组5-5删除物理卷六LVM逻辑卷缩容6-1缩容注意事项6-2标准缩容步骤一基础定义LVM,LogicalVolumeManger,逻辑卷管理,Linux磁盘分区管理的一种机制,建立在硬盘和分区上的一个逻辑层,提高磁盘分

    7. ruby - 如何在 Ruby 中获取 linux 系统信息 - 2

      如何在Ruby中获取linux系统(这必须适用于Fedora、Ubuntu等)的软件/硬件信息? 最佳答案 Chef背后的优秀人才,拥有一颗名为Ohai的优秀gemhttps://github.com/opscode/ohai以散列形式返回系统信息,例如操作系统、内核、规范、fqdn、磁盘、空间、内存、用户、接口(interface)、sshkey等。它非常完整,非常好。它还会安装命令行二进制文件(也称为ohai)。 关于ruby-如何在Ruby中获取linux系统信息,我们在Stack

    8. ruby - rbenv:在 Linux Mint 上找不到 gem 命令 - 2

      我在LinuxMint17.2上。我最近使用apt-getpurgeruby​​删除了ruby​​。然后我安装了rbenv然后rbenvinstall2.3.0所以现在,~/.rbenv/versions/2.3.0/bin/ruby存在。但是现在,我无法执行geminstallrubocop。我明白了:$geminstallrubocoprbenv:gem:commandnotfoundThe`gem'commandexistsintheseRubyversions:2.3.0但是我可以~/.rbenv/versions/2.3.0/bin/geminstallrubocop。但是,

    9. ruby - 在 Linux 上编译 Ruby 1.9.2 所需的先决条件? - 2

      我是Ruby和RoR的新手。我有一个带有Ubuntu镜像的干净Linode实例,我想从源代码编译Ruby而不是使用apt-get。我已经在谷歌上搜索了执行此操作的说明,但经过一些尝试后,当我尝试运行一些教程示例时,我不断收到有关缺少zlib和其他一些包的错误。任何人都可以给我详细的说明(或链接),教我如何在从源代码编译Ruby之前安装必要的必备包吗?我的目的是编译Ruby的最新稳定版本,然后安装Rubygems和Rails。提前感谢您的帮助!!! 最佳答案 Thisblogpost涵盖从源代码编译ruby​​所需的包和安装过程;它引

    10. ruby-on-rails - 内存中具有相同 ID 的更多对象? - 2

      在部署在heroku上的Rails应用程序(v:3.1)中,我在内存中获得了更多具有相同ID的对象。我的heroku控制台日志:>>Project.find_all_by_id(92).size=>2>>ActiveRecord::Base.connection.execute('select*fromprojectswhereid=92').to_a.size=>1这怎么可能?可能是什么问题? 最佳答案 解决方案根据您的SQL查询,您的数据库中显然没有重复条目。也许您的类项目中的size或length方法已被覆盖。我试过find_

    随机推荐