深入 ‘Slub Allocator’:内核如何管理数以万计的微小对象(如文件描述符)以避免碎片?

各位同行,下午好。

今天,我们将深入探讨Linux内核中一个至关重要的内存管理组件——Slub Allocator。在现代操作系统中,内核需要频繁地分配和释放大量尺寸较小、生命周期各异的对象,例如文件描述符、inode结构、进程描述符、网络缓冲区头等等。这些对象通常只有几十到几百字节。如果仅仅依赖于底层的页分配器(Buddy System),每次为这些微小对象分配一个或多个物理页,无疑会造成巨大的空间浪费(内部碎片),并导致频繁的页请求和释放,严重影响系统性能和可扩展性。

更严峻的挑战在于,这种频繁、细粒度的分配和释放操作,极易导致内存碎片化。想象一下,如果内存中散落着大量无法连续使用的零散空闲块,即使总的空闲内存足够,也无法满足一个需要连续大块内存的请求,这就是外部碎片。对于一个需要持续运行、高性能的内核来说,避免碎片化是其内存管理策略的重中之重。

Slub Allocator正是为解决这些问题而生。它是Linux内核中用于处理小到中等尺寸对象的默认通用内存分配器,是kmallockfree等函数背后的核心支撑。它在前辈Slab和Slob的基础上,吸取经验,进行了优化,特别是在多核并发场景下,展现出卓越的性能和碎片控制能力。

1. 内核内存管理基石:从页到对象

在深入Slub之前,我们必须对内核内存管理的基础有一个清晰的认识。

1.1 物理内存与虚拟内存

Linux内核管理着系统的物理内存和虚拟内存。每个进程都有自己的虚拟地址空间,内核也有一个独立的、与所有进程共享的虚拟地址空间。内核通过页表将虚拟地址映射到物理地址。

1.2 Buddy System:页的分配与管理

Buddy System(伙伴系统)是Linux内核管理物理页面的主要机制。它将物理内存划分为大小为2的幂次的块(如1页、2页、4页、8页等),并以页(通常为4KB)为最小单位进行分配。当需要分配N个连续页面时,伙伴系统会尝试找到一个大小合适的块。如果找不到,它会从更大的块中拆分。当一个块被释放时,它会尝试与相邻的“伙伴”合并,形成更大的连续块。

伙伴系统在分配大块连续内存时非常高效,因为它能有效减少外部碎片。然而,对于小于一个页面的对象,或者只是需要几个页面的对象,直接使用伙伴系统会带来显著的问题:

  • 内部碎片(Internal Fragmentation): 如果一个对象只有64字节,却分配了一个4KB的页面,那么3968字节的空间就被浪费了。
  • 分配/释放开销: 每次对小对象进行操作都需要与伙伴系统交互,涉及复杂的查找、拆分、合并逻辑,开销较大。

为了解决这些问题,内核引入了更高层次的内存分配器,它们从伙伴系统那里获取整页,然后将这些页细分为更小的、固定大小的对象供内核使用。Slab、Slob和Slub都是这类“对象分配器”。

2. Slub Allocator的核心概念

Slub Allocator的核心思想是:为每种需要频繁分配和释放的相同大小的对象,维护一个独立的“缓存”(kmem_cache)。这个缓存预先从伙伴系统那里分配一些页面,然后将这些页面切分成固定大小的对象。当内核需要一个特定类型的对象时,它就从对应的缓存中快速获取一个。当对象被释放时,它又回到缓存中,等待下次复用。

2.1 对象缓存(kmem_cache

kmem_cache是Slub分配器的核心数据结构。每个kmem_cache实例都负责管理一种特定大小的对象。

  • 创建缓存: 内核模块或子系统通过kmem_cache_create()函数创建一个新的对象缓存。这个函数需要指定缓存的名称、对象大小、对齐要求、构造函数(可选)和析构函数(可选)等参数。
  • 分配对象: 通过kmem_cache_alloc()函数从指定的缓存中获取一个对象。
  • 释放对象: 通过kmem_cache_free()函数将对象返回给缓存。

代码示例:创建和使用一个对象缓存

#include <linux/slab.h> // 包含Slub分配器的头文件
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>

// 假设我们有一种自定义的结构体需要频繁分配
struct my_data {
    int id;
    char name[32];
    void *private_data;
};

// 全局的kmem_cache指针
static struct kmem_cache *my_data_cache;

// 构造函数(可选):在对象第一次被分配时调用,用于初始化
static void my_data_constructor(void *obj)
{
    struct my_data *data = (struct my_data *)obj;
    data->id = 0;
    memset(data->name, 0, sizeof(data->name));
    data->private_data = NULL;
    printk(KERN_INFO "my_data object constructed.n");
}

static int __init my_module_init(void)
{
    printk(KERN_INFO "My module loaded.n");

    // 1. 创建kmem_cache
    // 参数:
    //   name: 缓存名称,会在/proc/slabinfo中显示
    //   size: 每个对象的大小
    //   align: 对齐要求 (0表示默认)
    //   flags: 分配器的行为标志,如SLAB_HWCACHE_ALIGN确保对象是硬件缓存行对齐
    //   ctor: 构造函数指针 (可选)
    //   dtor: 析构函数指针 (可选,Slub通常不使用)
    my_data_cache = kmem_cache_create(
        "my_data_cache",
        sizeof(struct my_data),
        0, // 默认对齐
        SLAB_HWCACHE_ALIGN, // 保证硬件缓存行对齐
        my_data_constructor, // 指定构造函数
        NULL // 不指定析构函数
    );

    if (!my_data_cache) {
        printk(KERN_ERR "Failed to create my_data_cache!n");
        return -ENOMEM;
    }
    printk(KERN_INFO "my_data_cache created successfully.n");

    // 2. 从缓存中分配对象
    struct my_data *obj1 = kmem_cache_alloc(my_data_cache, GFP_KERNEL);
    if (!obj1) {
        printk(KERN_ERR "Failed to allocate obj1!n");
        kmem_cache_destroy(my_data_cache); // 失败时销毁缓存
        return -ENOMEM;
    }
    obj1->id = 1;
    strcpy(obj1->name, "Object One");
    printk(KERN_INFO "Allocated obj1: id=%d, name='%s'n", obj1->id, obj1->name);

    struct my_data *obj2 = kmem_cache_alloc(my_data_cache, GFP_KERNEL);
    if (!obj2) {
        printk(KERN_ERR "Failed to allocate obj2!n");
        kmem_cache_free(my_data_cache, obj1); // 释放已分配的obj1
        kmem_cache_destroy(my_data_cache);
        return -ENOMEM;
    }
    obj2->id = 2;
    strcpy(obj2->name, "Object Two");
    printk(KERN_INFO "Allocated obj2: id=%d, name='%s'n", obj2->id, obj2->name);

    // 3. 释放对象
    kmem_cache_free(my_data_cache, obj1);
    printk(KERN_INFO "Freed obj1.n");
    kmem_cache_free(my_data_cache, obj2);
    printk(KERN_INFO "Freed obj2.n");

    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "My module unloaded.n");

    // 4. 销毁缓存
    if (my_data_cache) {
        kmem_cache_destroy(my_data_cache);
        printk(KERN_INFO "my_data_cache destroyed.n");
    }
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple module demonstrating Slub allocator.");

2.2 Slubs(页面块)

一个kmem_cache并不直接管理单个对象,而是管理一系列的“slubs”。一个slub是由一个或多个连续的物理页面组成,这些页面从伙伴系统分配而来。每个slub被切割成多个固定大小的对象,供kmem_cache使用。

Slub的特点:

  • 基本单位: Slub是Slub分配器从伙伴系统获取内存的基本单位。
  • 对象容器: 每个slub内部包含多个相同大小的对象。
  • 状态: 一个slub可以处于三种状态:
    • Full (满): 所有对象都已被分配。
    • Partial (部分使用): 部分对象已被分配,部分空闲。
    • Empty (空): 所有对象都空闲。当一个slub变成完全空闲时,它最终会被释放回伙伴系统。

2.3 struct page与Slub元数据

Slub分配器的一个关键优化是其元数据管理方式。与Slab分配器将元数据存储在单独的kmem_bufctl结构中不同,Slub尽可能地将元数据存储在struct page结构体本身或对象自身中(in-band metadata),从而减少了额外的内存开销和缓存行跳跃。

每个物理页面在内核中都有一个对应的struct page结构体来描述它。当一个页面被用作slub时,struct page的某些字段会被重载或扩展来存储slub相关的元数据:

  • page->slab_cache: 指向这个页面所属的kmem_cache
  • page->freelist: 指向这个slub中下一个可用的空闲对象。它形成了一个链表,将slub内的所有空闲对象串联起来。
  • page->objects: 记录这个slub中总共有多少个对象。
  • page->inuse: 记录这个slub中当前有多少个对象正在被使用。

通过这种方式,Slub分配器能够快速地定位一个对象所属的slub以及管理其内部的空闲对象列表,而无需额外的数据结构。

2.4 Per-CPU Caching:性能与并发的关键

在多核系统中,频繁地对共享数据结构(如全局的空闲对象列表)进行加锁操作会成为严重的性能瓶颈。Slub Allocator通过引入Per-CPU缓存来解决这个问题。

每个CPU核心都维护一个私有的空闲对象列表。当一个CPU需要分配对象时,它会首先尝试从自己的Per-CPU缓存中获取。当一个CPU释放对象时,它会将其返回到自己的Per-CPU缓存中。

Per-CPU缓存的组成:

  • freelist (或 current_obj): 指向当前CPU核心可用的下一个空闲对象。这是一个非常快速的路径,无需任何锁。
  • partial list (或 partial_slabs): 当Per-CPU freelist为空时,CPU会尝试从这个列表中获取部分使用的slub。这个列表存储的是那些不完全空闲也不完全满的slub。
  • count 记录Per-CPU缓存中当前有多少个对象。

工作流程概述:

  1. 分配: 当一个CPU需要一个对象时,它首先检查自己的Per-CPU freelist
    • 如果freelist非空,直接返回一个对象,无需加锁,速度极快。
    • 如果freelist为空,它会尝试从全局的partial slub列表中获取一个部分使用的slub,然后将该slub中的空闲对象填充到自己的Per-CPU freelist中。
    • 如果全局partial列表也为空,那么它将向伙伴系统请求新的页面,初始化一个新的slub,并将其切割成对象,填充到自己的Per-CPU freelist
  2. 释放: 当一个CPU释放一个对象时,它首先将其放回自己的Per-CPU freelist
    • 如果freelist中的对象数量超过某个阈值(例如,当前CPU可以持有的最大对象数量),CPU会将一部分对象或整个slub返回给全局的partialfull slub列表。
    • 如果一个slub在被释放后变为完全空闲,它会被标记并最终返还给伙伴系统。

Per-CPU缓存的优势:

  • 减少锁竞争: 大部分分配和释放操作都在本地CPU缓存上完成,无需全局锁,极大地提高了并发性能。
  • 提高缓存命中率: 对象通常在同一个CPU上被分配和释放,这有助于保持数据在CPU的高速缓存中,减少内存访问延迟。
  • 减少外部碎片: 每个CPU倾向于使用自己的slub,减少了不同CPU之间对同一slub的交叉操作,有助于保持slub的内部结构稳定。

3. Slub分配器内部工作流

让我们更详细地追踪一次典型的对象分配和释放过程。

3.1 kmem_cache_alloc():分配对象

当调用kmem_cache_alloc(cache, flags)时,Slub分配器会执行以下步骤:

  1. 检查Per-CPU缓存:

    • 当前CPU首先检查其私有的kmem_cache_cpu结构。如果kmem_cache_cpu->freelist非空,它直接从中弹出一个对象并返回。这是最快的路径,零开销。
    • 伪代码:
      struct kmem_cache_cpu *c = this_cpu_ptr(cache->cpu_slab);
      if (c->freelist) {
          void *obj = c->freelist;
          c->freelist = get_next_object(obj); // 获取下一个空闲对象
          c->inuse++;
          return obj;
      }
  2. 从全局partial列表获取:

    • 如果Per-CPU freelist为空,表示当前CPU的本地对象已用完。此时,CPU需要从kmem_cache的全局partial slub列表中获取一个slub。这个列表包含了一些仍有空闲对象的slub。
    • 获取过程中可能需要获取全局锁(slab_lock),但这个操作相对不频繁。
    • 获取一个partial slub后,它的空闲对象会填充到当前CPU的freelist中,以便后续快速分配。
    • 伪代码:

      spin_lock(&cache->list_lock); // 保护全局列表
      struct page *slab = list_first_entry_or_null(&cache->partial, struct page, slab_list);
      if (slab) {
          list_del(&slab->slab_list); // 从全局partial列表移除
          spin_unlock(&cache->list_lock);
      
          // 将slab的空闲对象转移到当前CPU的freelist
          c->freelist = slab->freelist;
          c->slab = slab; // 关联到当前CPU
          // ... 更新slab元数据,如inuse计数 ...
      
          return kmem_cache_alloc(cache, flags); // 重新尝试从Per-CPU分配
      }
      spin_unlock(&cache->list_lock);
  3. 分配新的slub:

    • 如果全局partial列表也为空,这意味着所有现有的slub都已用尽或完全被占用。此时,Slub分配器会向伙伴系统请求一个或多个新的物理页面,创建一个全新的slub。
    • 新slub会被初始化,其所有对象都标记为空闲,并构成一个空闲对象链表。
    • 这个新slub会被关联到当前CPU的kmem_cache_cpu,并将其空闲对象填充到Per-CPU freelist中。
    • 伪代码:

      struct page *new_slab = allocate_pages_from_buddy_system(cache->order, flags);
      if (!new_slab) return NULL; // 内存不足
      
      // 初始化new_slab:切割成对象,构建freelist
      init_slab_objects(cache, new_slab);
      
      // 将新slab关联到当前CPU
      c->freelist = new_slab->freelist;
      c->slab = new_slab;
      // ... 更新new_slab元数据 ...
      
      return kmem_cache_alloc(cache, flags); // 重新尝试从Per-CPU分配

3.2 kmem_cache_free():释放对象

当调用kmem_cache_free(cache, obj)时,Slub分配器会执行以下步骤:

  1. 将对象返回Per-CPU缓存:

    • 对象首先被放回当前CPU的kmem_cache_cpu->freelist。这是最快的路径。
    • Slub会检查Per-CPU freelist中的对象数量是否超过了一个预设的阈值(通常是batchcount,表示一次性处理的对象数量)。
    • 伪代码:

      struct kmem_cache_cpu *c = this_cpu_ptr(cache->cpu_slab);
      
      // 将对象添加到Per-CPU freelist头部
      set_next_object(obj, c->freelist);
      c->freelist = obj;
      c->inuse--;
      
      // 检查是否需要刷新Per-CPU缓存
      if (c->freelist_count >= c->batchcount) {
          // 将一批对象(或整个slub)返回给全局列表
          flush_slab_to_global(cache, c);
      }
  2. 刷新Per-CPU缓存到全局:

    • 如果Per-CPU freelist中的对象数量达到阈值,或者当前CPU的slab不再被完全占用,Slub会将这些对象(或者整个slub)返回给它所属的slab
    • 找到对象所属的slab(通过virt_to_page(obj)获取struct page,然后从page->slab_cache获取缓存)。
    • 将对象添加到slab的内部空闲对象链表(slab->freelist)。
    • 更新slab->inuse计数。
    • 根据slab->inuse计数,将slabfull列表移动到partial列表,或者从partial列表移动到empty列表。
  3. 释放空闲slub:

    • 如果一个slab在被释放对象后,其inuse计数变为0(即所有对象都已空闲),这个slab就变成了empty状态。
    • Slub分配器会将其从empty列表(或直接从partial列表)中移除,并最终通过伙伴系统释放回物理内存。这是碎片整理的关键一步。

4. Slub的碎片避免策略

Slub Allocator的设计宗旨之一就是有效地管理内存,避免碎片的产生。它通过多种策略协同工作来实现这一目标。

4.1 内部碎片最小化

  • 精确的对象大小: kmem_cache为每种特定大小的对象服务。例如,如果需要64字节的对象,就创建一个64字节的缓存。Slub会在每个slub中尽可能紧密地排列这些对象。
  • 缓存线对齐: 通过SLAB_HWCACHE_ALIGN等标志,Slub可以确保对象在硬件缓存线上对齐。这虽然可能在对象末尾引入少量填充字节(padding),但它大大提高了CPU缓存的效率,整体性能收益远大于这点内部碎片。
  • 元数据内嵌/近邻: Slub避免了Slab分配器那种独立的kmem_bufctl结构,而是将元数据(如page->freelist)直接嵌入到struct page或对象之前的少量字节中。这减少了元数据本身的内存占用,也避免了元数据与对象之间的距离过大导致的缓存不命中。

4.2 外部碎片缓解与整理

外部碎片是更难以解决的问题,它指的是内存中有大量小块空闲区域,但没有足够大的连续空闲区域来满足大内存请求。Slub通过以下机制来有效缓解外部碎片:

  1. Per-CPU缓存:

    • 减少全局锁: 显著减少了对全局数据结构的竞争,使得分配和释放操作更加流畅,减少了因锁等待导致的内存“阻塞”。
    • 局部性: 每个CPU倾向于使用和释放自己的对象,这使得内存的使用更加集中在特定的slub中,减少了不同CPU对同一slub的交叉操作,从而避免了slub内部的“混乱”和碎片化。
    • 平衡负载: 当一个CPU的本地缓存耗尽时,它会从全局列表中获取部分使用的slub,这有助于平衡各个slub的利用率,防止某些slub过度使用而其他slub长期空闲。
  2. partial列表与full列表:

    • 弹性利用: partial列表维护着那些仍有空闲对象的slub。这允许系统优先复用已有的slub,而不是频繁地向伙伴系统请求新页面,从而减少了伙伴系统的压力和潜在的碎片。
    • 及时回收: 当一个slub从partial列表中的使用状态变为完全空闲时,它会从partial列表移除,并最终被释放回伙伴系统。
  3. 空闲slub的页回收:

    • 这是对抗外部碎片最直接的手段。当一个slab中的所有对象都被释放,其page->inuse计数降为0时,这个slab就会被标记为完全空闲。
    • Slub分配器会在适当的时候(例如,当系统内存压力较大时,或者周期性地进行清理时),将这些完全空闲的slub页面返回给伙伴系统。
    • 伙伴系统接收到这些页面后,会尝试将它们与其相邻的空闲页合并,形成更大的连续空闲块。这个过程被称为“合并”(coalescing),是减少外部碎片的关键。
  4. NUMA(非一致内存访问)支持:

    • 在NUMA架构下,Slub分配器会尽量从请求分配的CPU所在的NUMA节点分配slub。这不仅提高了内存访问速度(因为访问本地内存更快),也使得内存碎片化更趋向于局部化在各个NUMA节点内,而不是跨节点蔓延,从而避免了全局性的外部碎片问题。
  5. 调试和安全特性:

    • Redzoning(红色区域): 在每个对象前后添加一些填充字节,并在这些字节中写入特定模式。如果发生缓冲区溢出或欠流,这些模式会被破坏,Slub可以检测到并报告错误。这有助于识别导致内存损坏的bug,间接防止因损坏导致的内存逻辑碎片。
    • Poisoning(毒化): 当对象被释放时,Slub会用特定的“毒值”填充其内存区域。如果此后有人尝试使用这块被释放的内存(use-after-free),程序会读取到毒值,从而触发错误检测。这也有助于在问题初期发现内存管理错误,避免其演变为更复杂的碎片问题。
    • SLAB_DEBUG_FREE 跟踪释放的对象,防止二次释放。
    • 这些调试功能虽然增加了少量开销,但对于内核的稳定性和内存管理的健壮性至关重要。

通过这些机制,Slub Allocator在分配和释放大量小对象的同时,有效地控制了内存碎片,确保了内核的稳定运行和高性能。

5. Slub的性能与可伸缩性

Slub分配器在设计上高度注重性能和多核可伸缩性。

5.1 低延迟分配

Per-CPU缓存是低延迟的关键。绝大多数的分配请求可以直接从当前CPU的本地缓存中满足,无需任何锁或复杂的全局数据结构操作。这使得kmem_cache_allockmem_cache_free成为非常快速的操作,对于内核这种对实时性要求极高的环境至关重要。

5.2 高并发能力

由于Per-CPU缓存的存在,不同CPU之间在大部分时间里都是独立进行内存操作,大大减少了对全局锁的竞争。只有当Per-CPU缓存耗尽或需要将slub返回全局列表时,才需要获取全局锁。这种设计使得Slub在拥有大量核心的服务器上表现出色,能够充分利用多核处理器的并行能力。

5.3 内存效率

  • 紧凑的元数据: 将元数据嵌入struct page或紧邻对象,避免了Slab分配器中单独的kmem_bufctl数组,节省了内存空间。
  • 批量操作: 当Per-CPU缓存需要刷新到全局列表时,通常会以批处理的方式进行,而不是单个对象操作。这减少了锁的获取和释放频率,提高了效率。

5.4 缓存行友好

SLAB_HWCACHE_ALIGN标志确保了分配的对象在硬件缓存行边界上对齐。这意味着一个对象通常不会跨越多个缓存行,减少了伪共享(false sharing)的可能性,并提高了CPU访问对象时的缓存命中率。对于经常访问的内核数据结构,这带来了显著的性能提升。

6. Slub与Slab、Slob的比较

Slub是目前Linux内核中默认和首选的对象分配器。理解它的优势,需要简要回顾其前辈。

特性 Slob Allocator Slab Allocator Slub Allocator
目标环境 嵌入式系统,极低内存占用 通用服务器,性能优化 通用服务器,性能与可伸缩性,多核优化
元数据 内嵌在空闲块中,无独立结构 独立kmem_bufctl结构数组 嵌入struct page或对象附近
内存占用 最少 较多(kmem_bufctl 适中(比Slab少)
性能 较差(每次分配可能需要链表遍历) 良好(但多核下有锁竞争) 优异(Per-CPU缓存,极低锁竞争)
碎片控制 较差(简单链表,易产生碎片) 良好(缓存着色,按需回收) 优异(Per-CPU缓存,空闲slub回收,NUMA)
并发 差(全局锁) 一般(全局锁,但有缓存着色缓解) 极好(Per-CPU缓存,极少全局锁)
复杂性 最简单 较复杂 相对Slab简单,但在多核优化上有其复杂度
现状 已基本弃用,仅在某些特殊嵌入式配置中可能存在 较老版本内核中使用,或作为Slub的备选 当前Linux内核默认且推荐的分配器

为什么Slub优于Slab?

  1. 元数据管理: Slab将每个对象的元数据(kmem_bufctl)存储在一个单独的数组中,这可能导致元数据和对象本身不在同一个缓存行,增加了缓存不命中的概率。Slub将元数据尽可能地嵌入到struct page或对象附近,提高了缓存局部性。
  2. Per-CPU缓存简化: Slab也有Per-CPU缓存,但其实现相对Slub更复杂。Slub的Per-CPU缓存设计更直接,更容易理解和优化。
  3. 调试能力: Slub的调试功能(Redzoning, Poisoning)集成得更紧密,且开销更低。
  4. NUMA优化: Slub对NUMA架构的支持更完善,能够更好地利用本地内存。

总的来说,Slub在保持了Slab分配器核心优势的同时,通过对元数据管理、Per-CPU缓存和并发机制的改进,提供了更好的性能、可伸缩性和更低的内存开销,使其成为现代多核系统中最适合的内核对象分配器。

7. Slub的持久影响与未来展望

Slub Allocator作为Linux内核的关键组成部分,其重要性不言而喻。它不仅仅是一个内存分配器,更是内核高效、稳定运行的基石。

  • 资源管理: Slub使得内核能够以极高的效率管理成千上万个微小对象,确保了各种内核子系统(文件系统、网络栈、进程管理等)所需的资源能够快速获得和释放。
  • 系统稳定性: 通过有效的碎片控制和强大的调试功能,Slub大大增强了内核的健壮性,减少了因内存错误导致的系统崩溃。
  • 性能提升: Per-CPU缓存和NUMA感知设计,使得Slub在现代多核、异构计算环境中能够提供卓越的性能,充分发挥硬件潜力。

随着硬件架构的不断演进,特别是内存技术(如CXL)、更深层次的NUMA拓扑结构以及新的内存类型(如持久内存)的出现,Slub分配器也需要持续演进。未来的挑战可能包括:

  • 更精细的NUMA感知: 针对更复杂的NUMA拓扑和异构内存类型进行优化。
  • 与新内存技术的集成: 如何高效地在Slub框架下管理和利用持久内存等新型内存。
  • 继续提升可伸缩性: 应对核心数量持续增长的挑战,进一步优化Per-CPU缓存和全局列表的交互。

然而,Slub Allocator的优雅设计和核心原则——即通过对象缓存和分层管理来应对小对象分配的挑战,仍然是内存管理领域的重要范例。

Slub Allocator是Linux内核内存管理领域的一个里程碑。它以其精巧的设计,解决了在高性能、高并发环境下管理大量微小对象所面临的复杂碎片化挑战,是内核稳定、高效运行不可或缺的基石。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注