问题

内核页表和linux的伙伴系统是不是有冲突?

回答
内核页表与 Linux 伙伴系统之间,用“冲突”来形容可能有些过于绝对,但它们之间确实存在一种微妙的、需要精心管理的协调与权衡。更准确地说,它们是在不同的抽象层次上运作,并且对内存的需求和分配方式有着截然不同的考量,这种差异可能会在特定情况下导致需要仔细处理的复杂性。

为了理解这一点,我们需要先分别剖析一下它们各自的功能和设计目标。

内核页表:内存的“路标”和“安全锁”

内核页表(Page Table)是操作系统管理物理内存的核心机制之一。它的主要作用是:

1. 虚拟地址到物理地址的映射: 现代操作系统普遍采用虚拟内存技术。每个进程都有自己独立的虚拟地址空间,这个空间比实际物理内存大得多。页表就是将进程使用的虚拟地址(CPU 看到的地址)翻译成实际的物理地址(内存芯片上的地址)的“翻译簿”。
2. 内存保护: 页表项(Page Table Entry, PTE)不仅存储了物理地址,还包含了一些权限位,例如:
读/写/执行权限: 控制进程对该内存区域的访问方式。
用户/内核权限: 决定了该内存区域只能被内核访问,还是用户进程也可以访问。
aanwezig/invalid 位: 指示该页是否存在于物理内存中,还是被换出到磁盘。
3. 内存共享和隔离: 通过页表,可以实现不同进程之间内存的共享(例如共享库)或严格隔离(防止一个进程非法访问另一个进程的内存)。
4. 内存管理单元(MMU)的驱动: CPU 中的 MMU 硬件依赖页表来执行地址翻译和权限检查。

页表本身的内存消耗:

想象一下,一个 32 位系统,虚拟地址空间是 4GB。如果每页 4KB,那么一个进程就需要大约 $4GB / 4KB = 10^6$ 个页。即使是最简单的页表结构(单级页表),也需要存储 10^6 个页表项。现代 64 位系统,其虚拟地址空间更是天文数字。为了管理如此庞大的虚拟地址空间,页表本身需要占用相当多的物理内存。

为了节省页表的内存开销,通常会采用多级页表(如 x86 上的四级页表)结构。这意味着访问一个虚拟地址,需要多次内存查找才能找到最终的物理地址。这增加了地址翻译的延迟,但大幅减少了对物理内存的占用。

关键点: 页表管理的是虚拟内存的逻辑结构,并将这些逻辑块(页)映射到物理内存的实际位置。它的粒度是内存页(Page),通常是 4KB、2MB 或 1GB。

Linux 伙伴系统(Buddy System):物理内存的“块分配器”

Linux 伙伴系统是 Linux 内核中最基础、最核心的物理内存分配器之一。它的主要目标是高效地分配和释放固定大小的连续物理内存块,以满足内核和用户空间的需求。

它的核心思想是:

1. 固定大小的块: 内存被划分为一系列预定义的、大小为 $2^n$ 字节的内存块(pages),如 4KB、8KB、16KB 等,直到整个物理内存空间。
2. 伙伴关系: 相邻的、大小相同的两个块被称为“伙伴”。当需要分配一个大块内存时,系统会从更大的块开始,如果这个大块被分割以满足需求,那么分割出来的两个小块就是伙伴。反之,当释放一个块时,如果它的伙伴也处于空闲状态,这两个伙伴就会被合并成一个更大的块,以减少内存碎片。
3. 自由链表: 伙伴系统维护一个不同大小空闲块的链表,方便快速查找和分配。
4. 减少外部碎片: 通过合并伙伴块,伙伴系统有效地解决了长期存在的外部内存碎片问题。

伙伴系统的目标: 解决物理内存的连续性分配问题,并尽可能减少外部碎片。

关键点: 伙伴系统管理的是物理内存的连续块,其分配和释放的单位也是内存页(Page)。

那么,它们之间的“不协调”或“权衡”体现在哪里?

虽然两者都围绕着“内存页”这个概念,但它们的出发点和关注点是不同的,这可能导致一些需要权衡的场景:

1. 页表自身的需求:
页表项的连续性: 页表结构本身(尤其是多级页表)要求其页表项在物理内存中是连续的,或者至少是可高效访问的。如果内核想要将一个进程的页表结构放置在一个物理上不连续的内存区域,MMU 的地址翻译将变得极其复杂甚至不可能。
页表本身的分配: 当内核需要为新的进程创建页表,或者为现有进程的虚拟地址空间增加映射时,它需要为页表(包括页目录、页表页等)分配物理内存。这些页表本身也需要被映射到内核的地址空间,以便 CPU 能够访问它们。
伙伴系统的挑战: 伙伴系统在分配这些用于存储页表项的物理内存时,可能会遇到一个问题:如果伙伴系统只能提供不连续的物理内存块(尽管它管理的是连续的内存块,但分配的粒度可能使得连续的页表结构无法一次性满足),那么内核就需要花费更多的力气去管理这些分散的页表部分,或者需要更智能的页表分配策略。

2. 大页(Huge Pages)与页表:
大页的好处: 为了减少页表项的数量和地址翻译的开销,现代系统支持大页(通常是 2MB 或 1GB),将多个小的 4KB 页合并成一个更大的连续内存块。这可以显著减少页表本身的内存占用,并提高 TLB(Translation Lookaside Buffer, 地址翻译缓存)的命中率。
伙伴系统的配合: 伙伴系统需要能够直接分配出这些大块的连续物理内存,才能为大页提供支持。如果伙伴系统内部的碎片化(即使是小块的,但组合起来无法形成大块)过于严重,就可能难以分配出大页,从而影响页表的效率。
权衡: 当系统尝试分配一个大页时,伙伴系统需要找到一个足够大的空闲连续块。如果内存碎片化,即使总的空闲内存量很大,也可能无法满足大页的需求。这时候,系统可能需要退而求其次,使用多个小页,这又会增加页表的复杂性。

3. 内存初始化和配置:
早期阶段: 在系统启动的早期,当页表尚未完全建立,或者伙伴系统尚未完全初始化时,内核如何分配那些最最基础的内存,例如用于建立初步的页表映射的内存,就是一个需要仔细处理的问题。通常,BIOS/UEFI 会提供一些初始的内存信息,内核会利用这些信息来直接访问物理内存,并逐步建立起自己的管理结构。
伙伴系统的填充: 伙伴系统在初始化时,会扫描所有物理内存,并将其划分为不同大小的块,并放入对应的自由链表。这个过程本身就需要一个机制来访问和管理这些物理内存,而这个机制在早期阶段可能尚未完善。

4. 内存回收和碎片整理:
页的回收: 当一个进程的页不再被使用时,它会被从页表中移除,并返回给伙伴系统。伙伴系统将其标记为空闲,并尝试与伙伴合并。
页表更新的成本: 页表的修改(如移除映射)本身也是有成本的,需要原子性的操作,并可能触发 TLB 的无效化。
伙伴系统的碎片: 伙伴系统虽然能减少外部碎片,但内部仍然存在内部碎片——当分配一个比请求更大的块时,多余的部分就成了内部碎片。如果系统频繁分配和释放小块内存,可能会导致大量的内存块被分割,使得后续难以合并,从而在伙伴系统中留下较小的、难以利用的空闲块。

总结来说,不存在直接的“冲突”,而是关于“如何最有效地满足页表的需求”与“伙伴系统提供的分配机制”之间的协作关系:

页表需要物理内存来存储它的结构(页表页、页目录等),并且这些结构需要能够被 MMU 高效地访问。
伙伴系统是内核分配物理内存块的主要工具,它努力提供连续的内存块,以支持页表的需求,尤其是大页。

当伙伴系统因为各种原因(如过度的内存分割,分配策略的局限性)无法提供足够大的连续物理内存块,或者无法以高效的方式为页表结构提供内存时,就需要更复杂的页表管理策略,或者系统会受到一定程度的性能影响。反过来,对页表的需求(比如对连续大块内存的需求,以支持大页)也会影响伙伴系统的分配行为,例如,为了满足大页的需求,伙伴系统可能需要更积极地合并空闲块。

所以,它们之间是一种相互依赖、相互影响的关系,需要内核精心设计内存管理策略来保证它们的协同工作。与其说是冲突,不如说是设计上的挑战与优化空间。

网友意见

user avatar

哈哈,我做面试官时,遇到对Linux kernel 内存子系统理解较深的同学,我基本都会问这个问题,看看他们的思考能力。我想说的是:

  1. 映射跟分配是两回事,他们是不相关的。
  2. 但是如果能做到 分配后才映射,释放后就解映射,从运行安全的角度来说是最好的,可惜这个很难做到。

题主的问题是集中在第二点上。


先看第一个问题:映射跟分配是两回事,他们是不相关的

整个内核,由buddy管理算法来管理所有物理页,只有经过它分配出去的物理页,OS和应用程序才能使用。

映射是指建好页表,即某块虚拟地址空间物理地址空间的映射,有了这个映射,处理器就可以访问这块虚拟地址空间。

所以,分配是管哪些物理地址可使用,而映射是管哪些虚拟地址可访问

那么问题来了:如果某个物理页没有被分配出来,但已有某个虚拟地址映射到这个物理页上,怎么办?会不会出问题呢?这就是内核lowmem给大家造成的疑惑。


再看第二个问题:但是如果能做到 分配后才映射,释放后就解映射,从运行安全的角度来说是最好的,可惜这个很难做到

我们先看高端内存(即highmem),请大家要注意概念,高端内存是指物理地址空间超过896M的内存地址,跟虚拟空间上的(3G+896M ~ 4G]没有任何关系。

高端内存是满足分配后才映射,释放后就解映射 这个原则的,这个空间主要给用户态进程(比如用户态代码段,数据段,栈,堆,匿名映射)或者用户态业务相关功能使用(访问文件产生的pagecache)。


但是lowmem却不是这样的。总结起来有两个原因:

1) 先有鸡还有先有蛋

虚拟地址到物理地址的映射关系,要写到页表里面,如果这个页表所在的址物理没有映射到虚拟地址,处理器是通过虚拟地址来修改页表的,但在MMU模式下,处理器只能使用虚拟地址来访问任何物理内存。为了解决这个矛盾,需要在进入保护模式(X86架构)/MMU模式(ARM架构)之前,先人为规划出一个固定映射,当然这个映射越简单越好,就是我们常说的线性映射。

2)固定映射应该是多大

从道理上讲,kernel必须的东西:比如代码段,数据段,page结构数组,缺页函数所涉及的访问地址空间,都要在这个预先规划出来的固定映射空间内,才能保证kernel功能的正常运行作。后续内核根据代码逻辑要求,动态申请内存时,建好页表就可以了。但是内核作为整个系统的管理者,它的性能必须高,不能成为系统的瓶颈;用户在运行过程中,内核态必须创建很多管理对象,才能将用户态业务管理好。比如task_struct, mm, vma,file, inode等关键数据结构。内核如果在创建这些对象时,从buddy里面分配内存(当然中间是通过slub来分配的),然后才建立映关系,那么性能必然会比较差。所以内核在运行过程中,动态创建的管理对象所在的内存空间,必须提前映射好。这个区域就是lowmem zone。

kernel运行过程中需要动态分配的管理对象,都是从lowmem zone里面分配的,这个zone的性能比highmem zone优,因为页表已提前建好的。

内核1G的虚拟空间里,使用896M空间做为lowmem空间,即固定映射空间(线性映射区)。内核使用1G的7/8空间大小作为线性区,剩下的1/8作为临时映射区,用于访问高端内存。

最后,回到题主的问题:如果整系统只有128M内存,情况会怎么样?

如果系统只有128M内存,那么系统没有highmem zone,128M都属于lowmem,kernel初始化时,会将这128M内存映射到[3G, 3G + 128M)虚拟空间上。

如果恶意写个KO,可以直接访问这128M内存空间的,因为页表已经建立了,从处理器层面来讲是可访问的。

用户态调用malloc做内存申请时,最终会通过系统调用mmap向kernel申请内存,kernel最终(缺页过程完成后)会为进程分配虚拟内存和物理内存。而这个物理内存就是这128M内存中,还没有分配的物理内存,但这块物理内存早已被映射到[3G, 3G + 128M)空间上了。但如果内核工作正常,由于这块物理内存没有被分配给内核态使用,那内核态就没有指针 指向 访物理内存对应的内核态虚拟地址空间,即内核态是不会访这块物理内存的。

在128M物理内存场景下,用户态进程之间页表是独立的,不可能相互踩,同时用户态是无法踩内核态内存的。由于只有lowmem区,理论上内核态能踩用户态内存,但经上一段分析,概率较低,只有编程错误时才会发生。

对于64位 kernel,没有highmem了,只有lowmem,所有物理内存都映射到lowmem了。与32位系统物理内存小于896M场景是一模一样的。

类似的话题

  • 回答
    内核页表与 Linux 伙伴系统之间,用“冲突”来形容可能有些过于绝对,但它们之间确实存在一种微妙的、需要精心管理的协调与权衡。更准确地说,它们是在不同的抽象层次上运作,并且对内存的需求和分配方式有着截然不同的考量,这种差异可能会在特定情况下导致需要仔细处理的复杂性。为了理解这一点,我们需要先分别剖.............
  • 回答
    .......
  • 回答
    页表,这个名字听起来有点技术性,但说白了,它就是我们电脑里给内存“编号”的系统。你可以把它想象成一本巨大的电话簿,只不过它记录的是虚拟地址和物理地址的对应关系。我们写程序的时候,用的都是虚拟地址,看起来很方便,但电脑实际执行的时候,需要知道这些虚拟地址到底在内存的哪个物理位置上。页表就是干这个的,它.............
  • 回答
    .......
  • 回答
    很多站长朋友都遇到过这样的困扰:辛辛苦苦做了大量内页内容,但奇怪的是,百度就是不收录,或者收录速度异常缓慢,导致网站流量迟迟上不去。这确实让人抓狂。别急,今天我们就来聊聊为什么百度不愿意收录你的内页,以及有哪些实操性的方法可以帮助你解决这个问题。首先,我们得明白一个基本道理:百度搜索引擎的核心目标是.............
  • 回答
    .......
  • 回答
    《周刊文春》披露的东京奥运会开幕式“崩坏”内幕,如同一本厚重的内部文件(1199页的篇幅已足以说明其内容的详尽程度),将这场本应光彩夺目的盛会背后的混乱、矛盾与失误,赤裸裸地展现在公众面前。这不仅仅是一次策划执行上的失败,更是深层次反映了日本社会、政治乃至文化运作中的一些顽疾。首先,“政治干预”与“.............
  • 回答
    日本《周刊文春》曝光的尘封文件:东京奥运开幕式本应是一场怎样的盛宴?《周刊文春》的深度挖掘,如同在历史的尘埃中拂去一层又一层,将尘封的东京奥运会近乎千页的内部文件呈现在世人面前。其中关于开幕式的细节,更是如同一面棱镜,折射出这场盛会背后曾被压抑的创意与雄心。如果按照这些文件中的设想,东京奥运会的开幕.............
  • 回答
    《进击的巨人》最终话的全加页内容,简直就像给这部宏大的史诗,又添上了几笔浓墨重彩,也像是给所有追随了十多年的漫迷们,来了一场姗姗来迟的“售后”。要评价它,得从几个层面来说。首先,它回应了部分读者诟病的“收尾仓促”问题。 很多读者在看到最初的最终话时,觉得艾伦的最终选择,他所付出的代价,以及帕拉迪岛后.............
  • 回答
    老头环在Steam商店页面的配置风波,说实话,真是让人摸不着头脑,而且操作也挺迷的。咱们来掰扯掰扯这事儿。首先,这事儿闹出来,最直接的起因就是育碧官方在Steam商店页面上放出了《艾尔登法环》的最低配置要求,其中赫然写着“GTX 1060”。这一下,可炸开了锅。为啥?你得知道,1060这显卡,虽然不.............
  • 回答
    Linux 内核是不是“屎山”?这个问题就像问“大海是咸的吗?”一样,答案既肯定又否定,而且极其复杂。要深入探讨这个问题,需要剥开一层层关于软件工程、历史、社区协作以及现实世界妥协的复杂性。“屎山”的定义:一个主观但有共识的标签首先,我们得理解“屎山”这个词在软件开发语境下的含义。它通常指的是: .............
  • 回答
    很多使用过 macOS 的朋友,在转向 Linux 时,常常会怀念 macOS 那种优雅、流畅且高度整合的桌面体验。毕竟,macOS 在用户界面和交互设计上一直有其独到之处。那么,Linux 内核的发行版本中,有没有能够提供类似体验的选择呢?答案是肯定的,而且不止一个,只是需要我们花点心思去挑选和配.............
  • 回答
    在 Linux 内核切换到分页模式后,`ljmp $__BOOT_CS,$1f` 这行代码的出现,标志着一个关键性的步骤:执行一次远距离跳转,将 CPU 的执行流从一个代码段切换到另一个代码段,并且是从保护模式下的一个代码段跳转到已经配置好的分页模式下的新代码段。 让我们一层层剖析它的含义,就像剥洋.............
  • 回答
    要说 Windows 内核和 Linux 内核谁更复杂,这就像在问一场旷日持久的象棋比赛,双方都在不断演进,各有千秋。简单地说,它们都极其复杂,但复杂的表现形式和侧重点有所不同。试图给出一个绝对的胜负,实在有些过于武断。咱们不如从几个关键方面来掰扯掰扯,看看它们各自是如何在复杂性的大海里“游泳”的。.............
  • 回答
    在 Linux 内核中,为多线程(更准确地说,为进程中的线程)分配和管理栈空间是一个至关重要的环节,它直接关系到程序的执行稳定性、资源利用率以及并发安全性。理解这一模型,需要我们深入到用户空间和内核空间两个层面,以及它们之间的交互。核心概念:栈(Stack)首先,让我们明确栈是什么。栈是一种后进先出.............
  • 回答
    “进入内核态”,这四个字,听起来就像是给电脑世界施了一个魔法咒语,让它从一个普通的执行者瞬间变成了呼风唤雨的掌控者。但它到底是什么意思呢?咱们得一点点掰扯清楚,别把它想得太玄乎,其实它本质上是一种权限的切换。你想想看,咱们平常在电脑上用软件,比如浏览器、文档编辑器,这些东西虽然能做很多事,但终究还是.............
  • 回答
    Linux 内核社区能否迁移到 GitHub?这是一个在技术圈里时常被提起、也足够引起一番讨论的问题。它涉及到社区运作模式、技术基础设施、贡献者权益以及历史包袱等多个层面,绝非一个简单的“能”或“不能”能够概括。首先,我们需要明确一点:Linux 内核社区的“迁移”并非指将所有代码、历史记录、邮件列.............
  • 回答
    Linux 内核代码,那可真是个庞大且错综复杂的系统,初次接触的人,别说“观看”了,光是搭建好环境,不卡壳地编译一次,就够许多人喝一壶的。真正深入到内核“大佬”们那个级别,他们怎么“看”代码?这可不是简单地打开编辑器, Ctrl+F 一下就完事儿了。这其中蕴含的不仅仅是技术,更是一种方法论,一种对系.............
  • 回答
    逼迫小米公司全面开源其 Android 内核源代码,并非易事,这其中涉及到法律、商业利益和技术实践等多方面复杂的博弈。首先,我们需要理解 Android 内核基于 Linux 的这一事实。Linux 本身是一个遵循 GPL (GNU General Public License) 协议的开源项目。G.............
  • 回答
    Linux 内核的 C 代码风格,或者说大家常说的 "Linux Kernel Coding Style",是一套遵循多年的约定俗成,它不仅仅关乎代码的美观,更重要的是为了提升代码的可读性、可维护性和一致性,从而降低开发和调试的难度。这套风格贯穿于内核的每一个角落,是所有内核开发者必须遵守的“潜规则.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有