Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器
文章目录
- 物理内存
- 物理内存分配
- 外部碎片
- 内部碎片
- 伙伴系统(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_cachep
的 kmem_cache
节点,它存放了一些 inode
对象。当内核请求分配一个新的 inode
对象时,slab分配器
就开始工作了:
- 首先要查看
inode_cachep
的slabs_partial
链表,如果slabs_partial
非空,就从中选中一个slab
, 返回一个指向已分配但未使用的inode结构的指针。 完事之后,如果这个slab
满了,就把它从slabs_partial
中删除,插入到slabs_full
中去,结束; - 如果
slabs_partial
为空,也就是没有半满的slab
,就会到slabs_empty
中寻找。如果slabs_empty
非空,就选中一个slab
, 返回一个指向已分配但未使用的inode结构的指针 ,然后将这个slab
从slabs_empty
中删除,插入到slabs_partial
(或者slab_full
)中去,结束; - 如果
slabs_empty
也为空,那么没办法,cache_chain
内存已经不足,只能新创建一个slab
了。
内核中slab分配对象的全过程:
- 根据对象的类型找到
cache_chain
中对应的高速缓存kmem_cache
- 如果
slabs_partial
链表非空,则选择其中一个slab
,将slab
中一个未分配的对象分配给需求来源。如果分配之后这个slab
已满,则移动这个slab
到slabs_full
链表 - 如果
slabs_partial
链表没有未分配的空间,则去查看slabs_empty
链表 - 如果
slabs_empty
非空,则选择其中一个slab
,将slab
中一个未分配的对象分配给需求来源,同时移动slab
进入slabs_partial
链表中 - 如果
slabs_empty
也没有未分配的空间,则说明此时空间不足,就会请求伙伴系统分页,并创建新的空闲slab
节点放入slabs_empty
链表中,回到步骤3
从上面可以看出,slab分配器的本质其实就是 将内存按使用对象不同再划分成不同大小的空间,即对内核对象的缓存操作 。
Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器相关推荐
- linux内存管理(七)-slab分配器
linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器 三.slab分配器 Linux内核中基于伙伴算法实现的分区页框分配器适合大块内存的请求,它所分配的内存区是以页框为基本单位的.有时 ...
- linux内存管理(六)-伙伴分配器
linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器 伙伴分配器 当系统内核初始化完毕后,使用页分配器管理物理页,当使用的页分配器是伙伴分配器,伙伴分配器的特点是算法简单且高效,支持内 ...
- Linux 内存管理 | 物理内存管理:物理内存、内存碎片、伙伴系统、slab分配器
文章目录 物理内存 物理内存分配 内存碎片 外部碎片 内部碎片 伙伴系统(buddy system) slab分配器 本文举例为32位Linux 物理内存 在Linux中,内核将物理内存划分为三个区域 ...
- Linux内存管理: 物理内存的释放(回收).为物理页面抬棺
前情提要: 地址转换 物理页面的分配 终于到了物理内存的释放. 内存页面如生命一般. 有生有死. 接下来我们就要为物理页面抬棺收尸了. 1.要点: 如何为兄弟抬棺回收? 分配时跟谁分开的, 回收时要跟 ...
- Linux内存管理--物理内存分配【转】
转自:http://blog.csdn.net/myarrow/article/details/8682819 1. First Fit分配器 First Fit分配器是最基本的内存分配器,它使用bi ...
- Linux 内存管理 | 地址映射:分段、分页、段页
文章目录 分段 分页 多级页表 快表(TLB) 段页式 Linux Linux 内存管理 | 物理内存管理:内存碎片.伙伴系统.slab分配器 Linux 内存管理 | 虚拟内存管理:虚拟内存空间.虚 ...
- linux内存管理(十一)-页回收总览
随着linux系统不断分配内存,当系统内存压力越来越大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文件页进行的.对于匿名页,内存回收过程中会筛选出一些不经常使用的匿名页 ...
- Linux内存管理之slab 1:slab原理(+buddy伙伴系统)
Linux内存管理之slab 1:slab原理(+buddy伙伴系统) 1. 为什么有了Buddy(伙伴系统)还需要slab? 1.1 什么是伙伴系统? 1.1.1 伙伴系统思想 1.2 伙伴系统例子 ...
- Linux内存管理:内存分配:slab分配器
<linux内核之slob.slab.slub> <Linux内核:kmalloc()和SLOB.SLAB.SLUB内存分配器> <Linux内存管理:内存分配:slab ...
最新文章
- newlisp debugger
- 【OpenGL从入门到精通(一)】Windows搭建OpenGL的渲染环境,并初始化一个OPenGL窗口
- 简单Android app开发_如何简单快速开发外卖app?
- android studio导入jar包和so库,Android实战技巧之十二:Android Studio导入第三方类库、jar包和so库(示例代码)...
- 宁德时代机器人编程开发_高通发布5G机器人开发平台,内置强大AI算力。各大厂商竞相发布机器人处理平台,万物互联的时代即将到来...
- Media Player Classic - HC 源代码分析 4:核心类 (CMainFrame)(3)
- ZOJ 2301 离散化
- springboot高校学生健康打卡系统的设计与实现毕业设计源码021009
- 英语单词拼写游戏开发纪录
- 12.学习Camera之——android binder 机制架构
- 微信小程序开发相关资料
- java在线查看PDF
- 【前端技术】一篇文章搞掂:JS
- Ae:摄像机设置与摄像机选项
- java dya01 HelloWorld与环境变量
- 【考研政治】时政(思维导图)
- day13_雷神_前端01
- 傅盛离职内情:从360叛将到腾讯马前卒 --学习周鸿祎的优点+360怎么做起来的
- python xlrd+xlwt+xlutils处理excel
- day1 request的使用
热门文章
- 陈伯雄lisp_基于AutoLisp的AutoCAD二次开发自动生成系统图
- mysql gui 分区_一文彻底搞懂MySQL分区
- Target “xxx” links to target “Boost::filesystem“ but the target was not found
- java实现c语言的函数_C语言实现返回字符串函数的四种方法
- java编写代码时易出错_写Java程序最容易犯的错误有哪些呢?
- 智能集群理论优化控制_数学学科学术报告九:机器人集群的智能协同控制方法_中国计量大学...
- python 读取outlook_如何用 Python 读取 Outlook 中的电子邮件
- hive sql 怎么实现循环_Hive存储过程实现-hpsql
- 【转】异步编程系列(Thread、Task、async/await、ajax等)
- 【转】WCF Data Service 使用小结 (一)—— 了解OData协议