面试不懂 Linux 内存管理?我用 20 张图给你讲明白( 四 )


分配实例比如:我需要申请4个页框,但是长度为4个连续页框块链表没有空闲的页框块,伙伴系统会从连续8个页框块的链表获取一个,并将其拆分为两个连续4个页框块,取其中一个,另外一个放入连续4个页框块的空闲链表中 。释放的时候会检查,释放的这几个页框前后的页框是否空闲,能否组成下一级长度的块 。
命令查看[lemon]]# cat /proc/buddyinfo Node 0, zoneDMA10002110113 Node 0, zoneDMA323198410849404773403021848911806732330 Node 0, zoneNormal4243837404160354386610121223001slab分配器看到这里你可能会想,有了伙伴系统这下总可以管理好物理内存了吧?不,还不够,否则就没有slab分配器什么事了 。
那什么是slab分配器呢?
一般来说,内核对象的生命周期是这样的:分配内存-初始化-释放内存,内核中有大量的小对象,比如文件描述结构对象、任务描述结构对象,如果按照伙伴系统按页分配和释放内存,对小对象频繁的执行「分配内存-初始化-释放内存」会非常消耗性能 。
伙伴系统分配出去的内存还是以页框为单位,而对于内核的很多场景都是分配小片内存,远用不到一页内存大小的空间 。slab分配器,「通过将内存按使用对象不同再划分成不同大小的空间」,应用于内核对象的缓存 。
伙伴系统和slab不是二选一的关系,slab 内存分配器是对伙伴分配算法的补充 。
大白话说原理对于每个内核中的相同类型的对象,如:task_struct、file_struct 等需要重复使用的小型内核数据对象,都会有个 slab 缓存池,缓存住大量常用的「已经初始化」的对象,每当要申请这种类型的对象时,就从缓存池的slab 列表中分配一个出去;而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片,同时也大大提高了内存分配性能 。
主要优点

  • slab 内存管理基于内核小对象,不用每次都分配一页内存,充分利用内存空间,避免内部碎片 。
  • slab 对内核中频繁创建和释放的小对象做缓存,重复利用一些相同的对象,减少内存分配次数 。
数据结构
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
slab分配器
kmem_cache 是一个cache_chain 的链表组成节点,代表的是一个内核中的相同类型的「对象高速缓存」,每个kmem_cache 通常是一段连续的内存块,包含了三种类型的 slabs 链表:
  • slabs_full (完全分配的 slab 链表)
  • slabs_partial (部分分配的slab 链表)
  • slabs_empty ( 没有被分配对象的slab 链表)
kmem_cache 中有个重要的结构体 kmem_list3 包含了以上三个数据结构的声明 。
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
kmem_list3 内核源码
slab 是slab 分配器的最小单位,在实现上一个 slab 由一个或多个连续的物理页组成(通常只有一页) 。单个slab可以在 slab 链表之间移动,例如如果一个「半满slabs_partial链表」被分配了对象后变满了,就要从 slabs_partial 中删除,同时插入到「全满slabs_full链表」中去 。内核slab对象的分配过程是这样的:
  1. 如果slabs_partial链表还有未分配的空间,分配对象,若分配之后变满,移动 slab 到slabs_full 链表
  2. 如果slabs_partial链表没有未分配的空间,进入下一步
  3. 如果slabs_empty 链表还有未分配的空间,分配对象,同时移动slab进入slabs_partial链表
  4. 如果slabs_empty为空,请求伙伴系统分页,创建一个新的空闲slab,按步骤 3 分配对象

面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
slab分配图解
命令查看上面说的都是理论,比较抽象,动动手来康康系统中的 slab 吧!你可以通过 cat /proc/slabinfo 命令,实际查看系统中slab 信息 。
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
slabinfo查询
slabtop 实时显示内核 slab 内存缓存信息 。
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
slabtop查询
slab高速缓存的分类slab高速缓存分为两大类,「通用高速缓存」和「专用高速缓存」 。
通用高速缓存slab分配器中用 kmem_cache 来描述高速缓存的结构,它本身也需要 slab 分配器对其进行高速缓存 。cache_cache 保存着对「高速缓存描述符的高速缓存」,是一种通用高速缓存,保存在cache_chain 链表中的第一个元素 。


推荐阅读