Linux 内存管理(1):伙伴分配器与 SLAB 分配器深度解析


   在 Linux 内核中,伙伴分配器(Buddy Allocator)和 SLAB 分配器(SLAB Allocator)是内存管理系统的两大核心组件,它们协同工作实现了高效的内存分配。下面我将详细解释它们的工作原理、区别和协同关系。


一、伙伴分配器(Buddy Allocator) - 大内存分配专家

1.1 伙伴分配器核心概念表格

术语定义
页(Page)内存管理的基本单位,通常为4KB。
页块由连续页组成的内存块,大小为 2^n 页。
阶(Order)表示页块的大小,n阶页块包含 2^n 个连续页。例如:0阶=1页,1阶=2页。
伙伴(Buddy)满足以下条件的同阶页块:
1. 物理地址连续。
2. 起始地址是 2^n 的倍数。
3. 合并后起始地址是 2^{n+1} 的倍数。
内部碎片分配的页块大小必须是 2^n,可能导致实际使用内存小于分配内存。
外部碎片无法合并的小空闲块,导致无法满足大块分配请求。

1.2 伙伴分配器分配流程

在这里插入图片描述

示例:分配100KB

  1. 计算所需阶数:100KB ≈ 25页 → 需要5阶(2^5=32页)。
  2. 查找5阶空闲页块
    • 若无空闲 → 查找6阶页块,分裂为两个5阶页块。
    • 分配一个5阶页块(32页),剩余5阶页块加入空闲链表。

1.3 伙伴分配器释放流程

释放页块 → 检查其伙伴是否空闲:
  ✅ 伙伴空闲 → 合并为更高阶页块 → 递归检查合并;
  ❌ 无法合并 → 将页块加入对应阶的空闲链表。

示例:释放64KB

  1. 释放一个6阶页块(64页)
  2. 检查伙伴是否空闲
    • 若伙伴空闲 → 合并为7阶页块(128页)。
    • 继续检查更高阶伙伴是否可合并,直到无法合并为止。

1.4 伙伴分配器数据结构表格

数据结构作用
free_area数组每个内存区域维护一个数组,下标对应阶数(0~MAX_ORDER)。
free_list链表每个阶数维护多个链表(按迁移类型划分),存储空闲页块。
nr_free记录当前阶的空闲页块总数。
per_cpu_pages每个CPU缓存小阶页块(如0~3阶),减少锁竞争,提升单页分配性能。

1.5 伙伴分配器优缺点对比表

优点缺点
1. 分配效率高:按阶管理,避免线性扫描。1. 内部碎片:分配的页块大小必须是 2^n,可能导致内存浪费。
2. 减少外部碎片:释放时合并伙伴页块。2. 释放时需记录分配阶数,调用者需提供正确参数。
3. 支持NUMA架构:优先分配本地节点内存。3. 固定大小的分割可能不适用于所有场景(需配合Slab分配器)。

1.6 伙伴分配器与Slab分配器协作关系

伙伴分配器Slab分配器
管理大块内存(如4KB~4MB)。管理小对象(如内核数据结构)。
分配单位:阶(2^n页)。分配单位:固定大小的内存块(Slab)。
直接分配物理页块。从伙伴分配器获取大块内存,划分为小对象。
优化目标:减少外部碎片。优化目标:减少内部碎片。

1.7 查看伙伴系统状态

cat /proc/buddyinfo 
# 输出示例:
# Node 0, zone DMA    1    1    0    0    2 ...
# 表示DMA区域有:
#   1个order0块(4KB), 1个order1块(8KB), 0个order2块(16KB)...

在这里插入图片描述
  图片显示的是 Linux 系统中 /proc/pagetypeinfo 文件的内容,该文件提供了关于系统内存页面类型的详细信息。以下是对其内容的详细分析:
Page block order 和 Pages per block

  • Page block order: 9
    • 这表示每个内存块的阶数(order)为 9。
  • Pages per block: 512
    • 每个内存块包含 512 个页面(2^9 = 512)。

Free pages count per migrate type at order

这一部分展示了在不同迁移类型(migrate type)和阶数(order)下的空闲页面数量。

  • Node 0, zone DMA32, type Unmovable

    • DMA32 区域中,Unmovable 类型的空闲页面在各个阶数上的数量均为 0。
  • Node 0, zone DMA32, type Movable

    • DMA32 区域中,Movable 类型的空闲页面在阶数 0 到 10 上的数量分别为:4, 5, 4, 3, 6, 5, 4, 4, 4, 4, 960。
  • Node 0, zone DMA32, type Reclaimable

    • DMA32 区域中,Reclaimable 类型的空闲页面在各个阶数上的数量均为 0。
  • Node 0, zone DMA32, type HighAtomic

    • DMA32 区域中,HighAtomic 类型的空闲页面在各个阶数上的数量均为 0。
  • Node 0, zone DMA32, type Isolate

    • DMA32 区域中,Isolate 类型的空闲页面在各个阶数上的数量均为 0。
  • Node 0, Normal 区域中, zone Normal, type Unmovable
    -Unmovable 类型的 在空闲页面在阶数 0 到 10 上的数量分别为:0, 175, 69, 50, 50, 15, 4, 3, 0, 0, 0。

  • Node 0, zone Normal, type Movable

    • Normal 区域中,Movable 类型的空闲页面在阶数 0 到 10 上的数量分别为:352, 189, 54, 27, 24, 14, 6, 6, 6, 3, 2690。
  • Node 0, zone Normal, type Reclaimable

    • Normal 区域中,Reclaimable 类型的空闲页面在阶数 0 到 10 上的数量分别为:24, 50, 95, 56, 25, 13, 0, 0, 0, 0, 0。
  • Node 0, zone Normal, type HighAtomic

    • Normal 区域中,HighAtomic 类型的空闲页面在各个阶数上的数量均为 0。
  • Node 0, zone Normal, type Isolate

    • Normal 区域中,Isolate 类型的空闲页面在各个阶数上的数量均为 0。

Number of blocks type

这一部分展示了在不同迁移类型下的内存块数量。

  • Node 0, zone DMA32

    • Unmovable: 3 个块
    • Movable: 1981 个块
    • Reclaimable: 0 个块
    • HighAtomic: 0 个块
    • Isolate: 0 个块
  • Node 0, zone Normal

    • Unmovable: 373 个块
    • Movable: 5770 个块
    • Reclaimable: 17 个块
    • HighAtomic: 0 个块
    • Isolate: 0 个块

总结

  • DMA32 区域 主要用于可以直接被硬件(如旧式显卡)访问的内存区域,通常在 32 位系统或特定硬件需求下使用。
  • Normal 区域 是标准的内存区域,适用于大多数内存分配需求。
  • Unmovable 类型 的页面通常是内核代码、某些驱动程序数据等不能被移动的页面。
  • Movable 类型 的页面是可以被移动的,常见于用户空间的内存分配。
  • Reclaimable 类型 的页面是可以被回收的,如缓存页面。
  • HighAtomic 类型 的页面用于原子操作,确保在高并发情况下的内存分配。
  • Isolate 类型 的页面通常用于内存隔离,防止被其他进程访问。
  • 内存分配倾向:系统在 DMA32 和 Normal 区域都倾向于为可移动的内存分配保留较多资源,这有助于内核进行内存碎片整理和动态内存管理。
  • 内存碎片化情况:从高阶数空闲页数量来看,Normal 区域的可移动内存有较多大页块可用,说明该区域的内存碎片化程度相对较低;而不可移动和可回收内存的高阶数空闲页较少,可能存在一定的碎片化问题。
  • 迁移类型使用情况:可移动内存类型在两个区域的使用最为广泛,而高原子和隔离类型的内存几乎未被使用,可能是因为系统当前的工作负载不需要这些特殊的内存分配类型。

1.8 伙伴分配器分配内存示例

  我们假设系统有 16 个连续的物理页(64KB,每页 4KB),初始状态如下:
在这里插入图片描述
分配请求 1:申请 3 页内存

  1. 计算最小满足阶数

    • 3 页不是 2^n,向上取整到 4 页(2^2)
    • 需要 Order 2 的块(4 页)
  2. 查找空闲块

    • Order 2 链表:空
    • Order 3 链表:空
    • Order 4 链表:有 1 个 16 页块
  3. 分裂高阶块

    Order 4: 16页
    分裂
    Order 3: 8页
    0x0000
    Order 3: 8页
    0x2000
  4. 继续分裂

    • Order 2 仍为空,继续分裂 Order 3:
    Order 3: 8页
    分裂
    Order 2: 4页
    0x0000
    Order 2: 4页
    0x1000
  5. 分配并更新

    • 分配 0x0000 的 Order 2 块(4 页)
    • 剩余空闲块:
      Order 2: 0x1000 (4页)
      Order 3: 0x2000 (8页)
      

分配请求 2:申请 1 页内存

  1. 计算最小满足阶数

    • 1 页 = 2^0 → 需要 Order 0 块
  2. 查找空闲块

    • Order 0 链表:空
    • 最低可用块:Order 2 的 0x1000(4 页)
  3. 分裂过程

    Order 2: 4页
    0x1000
    分裂
    Order 1: 2页
    0x1000
    Order 1: 2页
    0x1800
    分裂
    Order 0: 1页
    0x1000
    Order 0: 1页
    0x1400
  4. 分配并更新

    • 分配 0x1000 的 Order 0 块(1 页)
    • 剩余空闲块:
      Order 0: 0x1400 (1页)
      Order 1: 0x1800 (2页)
      Order 3: 0x2000 (8页)
      

分配请求 3:申请 6 页内存

  1. 计算最小满足阶数

    • 6 页 → 向上取整到 8 页(2^3)
    • 需要 Order 3 块
  2. 查找空闲块

    • Order 3 链表:有 0x2000(8 页)
  3. 直接分配

    • 分配整个 Order 3 块
    • 剩余空闲块:
      Order 0: 0x1400 (1页)
      Order 1: 0x1800 (2页)
      

内存释放场景
释放第一次分配的 4 页(0x0000)

  1. 释放 Order 2 块
    • 检查伙伴块:地址计算规则
    伙伴地址 = 块地址 ^ (1 << (order + PAGE_SHIFT))
    0x0000 ^ 0x4000 = 0x4000
    
    • 0x4000 无空闲块 → 直接加入 Order 2 链表
    • 空闲块更新:
      Order 0: 0x1400
      Order 1: 0x1800
      Order 2: 0x0000
      

释放第二次分配的 1 页(0x1000)

  1. 释放 Order 0 块
    • 检查伙伴 0x1400(相同 Order 0)
    • 0x1400 是空闲块 → 合并为 Order 1 块(0x1000-0x17FF)
    • 再检查新块的伙伴:0x1800(Order 1)
    • 0x1800 是空闲块 → 合并为 Order 2 块(0x1000-0x1FFF)
    • 空闲块更新:
      Order 2: 0x0000
      Order 2: 0x1000(新合并)
      

位图更新示例
初始位图(Order 0-4):

Order 4: [1]  // 1表示空闲
Order 3: [0,0]
Order 2: [0,0,0,0]
Order 1: [0,0,0,0,0,0,0,0]
Order 0: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

分配 4 页后:

Order 4: [0]
Order 3: [1,0]  // 0x2000空闲
Order 2: [0,1,0,0]  // 0x1000空闲
Order 1: [0,0,0,0,0,0,0,0]
Order 0: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

1.9 伙伴分配器关键特性总结

1. 地址计算规则

伙伴块地址计算公式:伙伴地址 = 块地址 ^ (1 << (order + PAGE_SHIFT))
  • 示例:Order 1 块(2 页)在 0x1000:
    0x1000 ^ (1 << (1+12)) = 0x1000 ^ 0x2000 = 0x3000
    

2. 内部碎片问题

请求大小实际分配碎片率
1 页1 页0%
3 页4 页25%
5 页8 页37.5%
7 页8 页12.5%

二、SLAB分配器 - 小对象分配大师

2.1 核心功能

  • 管理内核对象(task_struct, inode等)
  • 分配小内存(几十字节到几KB)
  • 减少内部碎片初始化开销

2.2 三级架构

SLAB分配器
缓存 Cache
SLAB 1
SLAB 2
SLAB 3
对象1
对象2
...

2.3 核心概念

组件说明
对象(Object)SLAB中分配的实际内存单元
缓存(Cache)同类型对象的集合(如task_struct缓存)
SLAB由1个或多个连续页组成的内存块

2.4 工作流程

  1. 创建缓存kmem_cache_create("task_struct", sizeof(task_struct), ...)
  2. 分配对象task = kmem_cache_alloc(task_struct_cache, GFP_KERNEL)
  3. 释放对象kmem_cache_free(task_struct_cache, task)
  4. 销毁缓存kmem_cache_destroy(task_struct_cache)

2.5 SLAB状态

cat /proc/slabinfo
# 输出示例:
# name            active_objs  num_objs  objsize  objperslab
# task_struct      120          150       1792     4

表示:

  • 每个task_struct对象1792字节

  • 每个SLAB容纳4个对象

  • 总共150个对象(37.5个SLAB)

  • 活跃对象120个
    在这里插入图片描述
      /proc/slabinfo 文件提供了内核 slab 分配器的详细信息。下面对输出的每列信息进行解释:

  • slabinfo - version: 2.1:表示当前 slabinfo 的版本是 2.1。

  • 表头各列的含义:

    • name:slab 缓存的名称,通常与使用该缓存的内核数据结构或设备相关。
    • <active_objs>:当前活跃对象的数量,即正在被使用的对象数量。
    • <num_objs>:缓存中对象的总数。
    • <objsize>:每个对象的大小(以字节为单位)。
    • <objperslab>:每个 slab 中对象的数量。
    • <pagesperslab>:每个 slab 所占用的物理页面数量。
    • tunables:包括 <limit><batchcount><sharedfactor>,这些是 slab 缓存的可调参数,用于控制缓存的行为。
    • slabdata:包含 <active_slabs><num_slabs><sharedavail>,分别表示活跃 slab 的数量、总 slab 的数量和共享可用的数量。

ext4_groupinfo_4k 这行为例:

ext4_groupinfo_4k      55             56          288        28            2              : tunables  0        0             0              : slabdata  2               2            0               
  • ext4_groupinfo_4k:这是一个与 ext4 文件系统组信息相关的 slab 缓存名称。
  • <active_objs> = 55:表示当前有 55 个对象正在被使用。
  • <num_objs> = 56:表示该缓存中总共有 56 个对象。
  • <objsize> = 288:每个对象的大小是 288 字节。
  • <objperslab> = 28:每个 slab 中包含 28 个对象。
  • <pagesperslab> = 2:每个 slab 占用 2 个物理页面。
  • tunables 部分的 <limit> = 0<batchcount> = 0<sharedfactor> = 0:这些可调参数目前都被设置为 0。
  • slabdata 部分:
    • <active_slabs> = 2:表示有 2 个活跃的 slab。
    • <num_slabs> = 2:表示总共有 2 个 slab。
    • <sharedavail> = 0:共享可用的数量为 0。

  很多 slab 缓存的活跃对象和对象总数为 0,这可能意味着对应的内核数据结构或设备当前没有在使用或者处于空闲状态。而像 ext4_groupinfo_4kext4_groupinfo_1kcan_receiver 等有一定数量的活跃对象,说明这些相关的内核功能正在运行并使用相应的 slab 缓存来管理内存。


三、伙伴分配器 vs SLAB分配器 : 协同工作流程

特性伙伴分配器SLAB分配器
管理单元物理页框内核对象
分配粒度4KB~几MB几十字节~几KB
主要目标大块连续内存小对象高效分配
碎片处理解决外部碎片解决内部碎片
性能特点O(log n)分配O(1)分配
使用场景页表分配、DMA缓存进程描述符、文件对象

App SLAB 伙伴分配器 申请task_struct(1.7KB) 申请4KB页面 返回4KB页框 分割为2个task_struct对象 返回对象指针 释放对象 标记对象为空闲 定期回收完全空闲的SLAB App SLAB 伙伴分配器

欢迎阅读下一篇:Linux 内存管理(2):了解内存回收机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值