文章目录

  • 物理内存
  • 物理内存分配
    • 外部碎片
    • 内部碎片
    • 伙伴系统(buddy system)
    • slab分配器

物理内存

在Linux中,内核将物理内存划分为三个区域。

在解释DMA内存区域之前解释一下什么是DMA:

DMA(直接存储器访问) 使用物理地址访问内存,将数据从一个地址空间复制到另外一个地址空间,从而加快磁盘和内存之间数据的交换,不经过MMU(内存管理单元),这时CPU可以去干别的事,大大增加了效率。

  • DMA内存区域(ZONE_DMA): 包含 0M~16M 之内的内存页框,该区域的物理页面专门供I/O设备的DMA使用,DMA需要连续的缓冲区,为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
  • 普通内存区域(ZONE_NORMAL): 包含 16MB~896M 以上的内存页框,可以直接映射到内核空间中的直接映射区。
  • 高端内存区域(ZONE_HIGHMEM): 包含 896M 以上的内存页框,不可以进行直接映射,可以通过 高端内存映射区中的永久内存映射区 以及 临时内存映射区(固定内存映射区中的一部分) 来对这块物理内存进行访问。

内存分布如下图:


物理内存分配

在Linux中,通过分段和分页的机制,将物理内存划分为4k大小的内存页(page),并且将作为物理内存分配与回收的基本单位。通过分页机制我们可以灵活的对内存进行管理。

  • 如果用户申请了小块内存,我们可以直接分配一页给它,就可以避免因为频繁的申请、释放小块内存而发起的系统调用带来的消耗。
  • 如果用户申请了大块内存,我们可以将多个页框组合成一大块内存后再进行分配,非常的灵活。

但是,这种直接的内存分配非常容易导致内存碎片的出现,下面就分别介绍内部碎片外部碎片这两种内存碎片。

为了方便接下来的阅读,这里科普一下 页框

  • 分页单元认为所有的RAM被分成了固定长度的 页框 ,页框是主存的一部分,是一个实际的存储区域。
  • 是指一系列的线性地址和包含于其中的数据,每页被视为一个数据块。而存放数据块的物理内存就是 页框 ,也就是说一个 页框 的长度和一个 的长度是一样的, 可以存放在任何页框或磁盘中。

外部碎片

当我们需要分配大块内存时,操作系统会将连续的页框组合起来,形成大块内存,来将其分配给用户。但是,频繁的申请和释放内存页,就会带来 内存外碎片 的问题,如下图。

假设我们这块内存块中有10个页框,我们一开始先是分配了3个页框给 进程A ,而后又分配了5个页框给 进程B 。当进程A结束后,其释放了申请的3个页框,此时我们剩余空间就是内存块起始位置的3个页框,以及末尾位置的2个页框。

假如此时我们运行了 进程C ,其需要5个页框的内存,此时虽然这块内存中还剩下5个页框,但是由于我们频繁的申请和释放小块空间导致内存碎片化,因此如果我们想申请5个页框的空间,只能到其他的内存块中申请,这块内存的空闲页框就被浪费了。


要想解决 外部碎片 的问题,无非就两种方法:

  • 外碎片问题的本质就是 空闲页框不连续 ,所以可以将 非连续的空闲页框 映射到 连续的虚拟地址空间 ,如果 现存的空闲页框总大小 满足进程的需求,则允许将一个进程分散地分配到许多不相邻的分区中,从而避免直接申请新的内存块;
  • 记录现存的 连续空闲页框块 的情况,如果有 能满足的小块内存需求 直接从记录中分配 相等或大于 内存需求的 连续空闲页框块 ,从而避免直接申请新的内存块。

第一种方法就是将上面举例中的 C进程 一部分分配到前面的 3个页框 , 另一部分分配到后面的 2个页框 ,如此一来不用申请新的内存块即可满足C进程的需求,详细内容将在分页知识中讲述。

第二种方法就是,虽然 C进程 要申请新的内存块,但是如果接下来 A进程 又开始运行,那我们就将 B进程 所在的内存块中 3块连续空闲页框块 分配给 A进程 而不是直接申请新的 10块连续页框 分配给 A进程

Linux选择了第二种方法,引入 伙伴系统算法 ,来解决 外部碎片 的问题。


内部碎片

内部碎片 是 的未被利用的空闲区域。一开始的时候也说了,由于页是物理内存分配的基本单位,因此即使我们需求的内存很小,Linux也会至少给我们分配 4k 的内存页,此时会造成内存浪费。

举个例子:当一个进程需要 7K 大小的内存时,我们必须给他分配 2个页框 以满足需求,但是第 2 个页框我们只使用了其中 3K 的内存,因此有 1K 的内存被浪费掉了。

如上图,倘若我们需求的只有几个字节,那该内存页中又有大量的空间未被使用,就造成了内存浪费的问题,而如果我们频繁的进行小块内存的申请,这种浪费现象就会愈发严重。

内碎片问题的本质就是 页内空闲内存 无法被其他进程再次利用。而 SLAB分配器 就可以 对内部碎片进行再利用 ,从而解决内部碎片问题。


伙伴系统(buddy system)

什么是伙伴系统算法呢?其实就是 把相同大小的连续页框块用链表串起来 ,这使页框之间看起来就像是手拉手的伙伴,这也就是其名字的由来。

伙伴系统将所有的空闲页框分组为11块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块,即2的0~10次方,最大可以申请 1024 个连续页框,对应 4MB(最大连续页框数 * 每个页的大小 = 1024 * 4k) 大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。

因为任何正整数都可以由 2^n 的和组成,所以我们总能通过拆分与合并,来找到合适大小的内存块分配出去,减少了外部碎片产生 。

倘若我们需要分配1MB的空间,即256个页框的块,我们就会去查找在256个页框的链表中是否存在一个空闲块,如果没有,则继续往下查找更大的链表,如查找512个页框的链表。如果存在空闲块,则将其拆分为两个256个页框的块,一个用来进行分配,另一个则放入256个页框的链表中。

释放时也同理,它会将多个连续且空闲的页框块进行合并为一个更大的页框块,放入更大的链表中。


slab分配器

虽然伙伴系统很好的解决了外部碎片的问题,但是它还是以页作为内存分配和释放的单位,而我们在实际的应用中则是以字节为单位,例如我们要申请2个字节的空间,其还是会向我们分配一页,也就是 4096字节(4K) 的内存,因此其还是会存在内部碎片的问题。

为了解决这个问题,slab分配器就应运而生了。其以 字节 为基本单位,专门用于对 小块内存 进行分配。slab分配器并未脱离伙伴系统,而是对伙伴系统的补充,它将伙伴系统分配的大内存进一步细化为小内存分配(对内部碎片的再利用)。

那么它的原理是什么呢?

对于内核对象,生命周期通常是这样的: 分配内存->初始化->释放内存 。而内核中如文件描述符、pcb等小对象又非常多,如果按照伙伴系统按页分配和释放内存,不仅存在大量的空间浪费,还会因为频繁对小对象进行 分配-初始化-释放 这些操作而导致性能的消耗。

所以为了解决这个问题,对于内核中这些需要重复使用的小型数据对象,slab通过一个缓存池来缓存这些常用的已初始化的对象

  • 当我们需要申请这些小对象时,就会直接从缓存池中的slab列表中分配一个出去。
  • 而当我们需要释放时,我们不会将其返回给伙伴系统进行释放,而是将其重新保存在缓存池的slab列表中。

通过这种方法,不仅避免了内部碎片的问题,还大大的提高了内存分配的性能。

PS:这里说的 缓存池 是对真正的缓存—— 硬件缓存(cache) 原理的一种模仿:

  • 硬件缓存是为了解决快速的CPU和速度较慢的内存之间速度不匹配的问题,CPU访问cache的速度要快于内存,如果将常用的数据放到硬件缓存中,使用时CPU 直接访问cache而不再访问内存 ,从而提升系统速度。
  • 而这里的 缓存池 实际上使在内存中预先开辟一块空间,使用时直接从这一块空间中去取所需对象(访问的是内存而不是cache),是SLAB分配器为了便于对小块内存的管理而建立的。

下面就由大到小,来画出底层的数据结构:

slab 分配器把每一个 请求的内存 称之为 对象 ,每种 对象 分配一个 高速缓存(kmem_cache) ,所有的 高速缓存 通过双链表组织在一起,形成 高速缓存链表(cache_chain) ,每个 高速缓存 所占内存区被划分为多个 slab ,这些 slab 都属于一个 slab列表 ,每个 slab列表 是一段连续的内存块,并包含3种类型的 slabs链表

  • slabs_full(完全分配的slab)
  • slabs_partial(部分分配的slab)
  • slabs_empty(空slab,或者没有对象被分配)。

slab 是 slab分配器的 最小单位 ,在具体实现上一个 slab 由一个或者多个连续的物理页组成(通常只有一页)。单个 slab 可以在 slab链表 中进行移动,例如一个 未满的slab节点 ,其原本在 slabs_partial 链表中,如果它由于分配对象而变满,就需要从原先的 slabs_partial 中删除,插入到完全分配的链表 slabs_full 中。

举个具象的例子:

slab分配器 将进程描述符和索引节点对象放在一个 cache_chain ,该 cache_chain 下辖两个 kmem_cache :一个 kmem_cache 用于存放进程描述符,而另一个 kmem_cache 存放索引节点对象,然后这些 kmem_cache 又被划分为多个 slab ,每个 slab 都管辖着若干个对象(进程描述符/索引节点对象),而这些 slab 又根据状态(已满、半满、全空)分布在3个 slabs链表 中,3个 slabs链表 共同构成一个 slab列表

举个例子以说明slab的分配过程:

如果在 cache_chain 里有一个名叫 inode_cachepkmem_cache 节点,它存放了一些 inode 对象。当内核请求分配一个新的 inode 对象时,slab分配器 就开始工作了:

  1. 首先要查看 inode_cachepslabs_partial 链表,如果 slabs_partial 非空,就从中选中一个 slab返回一个指向已分配但未使用的inode结构的指针。 完事之后,如果这个 slab 满了,就把它从 slabs_partial 中删除,插入到 slabs_full 中去,结束;
  2. 如果 slabs_partial 为空,也就是没有半满的 slab ,就会到 slabs_empty 中寻找。如果 slabs_empty 非空,就选中一个 slab返回一个指向已分配但未使用的inode结构的指针 ,然后将这个 slabslabs_empty 中删除,插入到 slabs_partial(或者 slab_full )中去,结束;
  3. 如果 slabs_empty 也为空,那么没办法,cache_chain 内存已经不足,只能新创建一个 slab 了。

内核中slab分配对象的全过程:

  1. 根据对象的类型找到 cache_chain 中对应的高速缓存 kmem_cache
  2. 如果 slabs_partial 链表非空,则选择其中一个 slab ,将 slab 中一个未分配的对象分配给需求来源。如果分配之后这个 slab 已满,则移动这个 slabslabs_full 链表
  3. 如果 slabs_partial 链表没有未分配的空间,则去查看 slabs_empty 链表
  4. 如果 slabs_empty 非空,则选择其中一个 slab ,将 slab 中一个未分配的对象分配给需求来源,同时移动 slab 进入 slabs_partial 链表中
  5. 如果 slabs_empty 也没有未分配的空间,则说明此时空间不足,就会请求伙伴系统分页,并创建新的空闲 slab 节点放入 slabs_empty 链表中,回到步骤3

从上面可以看出,slab分配器的本质其实就是 将内存按使用对象不同再划分成不同大小的空间,即对内核对象的缓存操作

Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器相关推荐

  1. linux内存管理(七)-slab分配器

    linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器 三.slab分配器 Linux内核中基于伙伴算法实现的分区页框分配器适合大块内存的请求,它所分配的内存区是以页框为基本单位的.有时 ...

  2. linux内存管理(六)-伙伴分配器

    linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器 伙伴分配器 当系统内核初始化完毕后,使用页分配器管理物理页,当使用的页分配器是伙伴分配器,伙伴分配器的特点是算法简单且高效,支持内 ...

  3. Linux 内存管理 | 物理内存管理:物理内存、内存碎片、伙伴系统、slab分配器

    文章目录 物理内存 物理内存分配 内存碎片 外部碎片 内部碎片 伙伴系统(buddy system) slab分配器 本文举例为32位Linux 物理内存 在Linux中,内核将物理内存划分为三个区域 ...

  4. Linux内存管理: 物理内存的释放(回收).为物理页面抬棺

    前情提要: 地址转换 物理页面的分配 终于到了物理内存的释放. 内存页面如生命一般. 有生有死. 接下来我们就要为物理页面抬棺收尸了. 1.要点: 如何为兄弟抬棺回收? 分配时跟谁分开的, 回收时要跟 ...

  5. Linux内存管理--物理内存分配【转】

    转自:http://blog.csdn.net/myarrow/article/details/8682819 1. First Fit分配器 First Fit分配器是最基本的内存分配器,它使用bi ...

  6. Linux 内存管理 | 地址映射:分段、分页、段页

    文章目录 分段 分页 多级页表 快表(TLB) 段页式 Linux Linux 内存管理 | 物理内存管理:内存碎片.伙伴系统.slab分配器 Linux 内存管理 | 虚拟内存管理:虚拟内存空间.虚 ...

  7. linux内存管理(十一)-页回收总览

    随着linux系统不断分配内存,当系统内存压力越来越大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文件页进行的.对于匿名页,内存回收过程中会筛选出一些不经常使用的匿名页 ...

  8. Linux内存管理之slab 1:slab原理(+buddy伙伴系统)

    Linux内存管理之slab 1:slab原理(+buddy伙伴系统) 1. 为什么有了Buddy(伙伴系统)还需要slab? 1.1 什么是伙伴系统? 1.1.1 伙伴系统思想 1.2 伙伴系统例子 ...

  9. Linux内存管理:内存分配:slab分配器

    <linux内核之slob.slab.slub> <Linux内核:kmalloc()和SLOB.SLAB.SLUB内存分配器> <Linux内存管理:内存分配:slab ...

最新文章

  1. newlisp debugger
  2. 【OpenGL从入门到精通(一)】Windows搭建OpenGL的渲染环境,并初始化一个OPenGL窗口
  3. 简单Android app开发_如何简单快速开发外卖app?
  4. android studio导入jar包和so库,Android实战技巧之十二:Android Studio导入第三方类库、jar包和so库(示例代码)...
  5. 宁德时代机器人编程开发_高通发布5G机器人开发平台,内置强大AI算力。各大厂商竞相发布机器人处理平台,万物互联的时代即将到来...
  6. Media Player Classic - HC 源代码分析 4:核心类 (CMainFrame)(3)
  7. ZOJ 2301 离散化
  8. springboot高校学生健康打卡系统的设计与实现毕业设计源码021009
  9. 英语单词拼写游戏开发纪录
  10. 12.学习Camera之——android binder 机制架构
  11. 微信小程序开发相关资料
  12. java在线查看PDF
  13. 【前端技术】一篇文章搞掂:JS
  14. Ae:摄像机设置与摄像机选项
  15. java dya01 HelloWorld与环境变量
  16. 【考研政治】时政(思维导图)
  17. day13_雷神_前端01
  18. 傅盛离职内情:从360叛将到腾讯马前卒 --学习周鸿祎的优点+360怎么做起来的
  19. python xlrd+xlwt+xlutils处理excel
  20. day1 request的使用

热门文章

  1. 陈伯雄lisp_基于AutoLisp的AutoCAD二次开发自动生成系统图
  2. mysql gui 分区_一文彻底搞懂MySQL分区
  3. Target “xxx” links to target “Boost::filesystem“ but the target was not found
  4. java实现c语言的函数_C语言实现返回字符串函数的四种方法
  5. java编写代码时易出错_写Java程序最容易犯的错误有哪些呢?
  6. 智能集群理论优化控制_数学学科学术报告九:机器人集群的智能协同控制方法_中国计量大学...
  7. python 读取outlook_如何用 Python 读取 Outlook 中的电子邮件
  8. hive sql 怎么实现循环_Hive存储过程实现-hpsql
  9. 【转】异步编程系列(Thread、Task、async/await、ajax等)
  10. 【转】WCF Data Service 使用小结 (一)—— 了解OData协议