slub分配器学习系列之linux5.10

article/2025/11/9 11:35:06

前言

前一篇文章对 linux5.10 的 slab 分配器底层实现进行了探究与学习。进一步地,本篇文章将对 linux5.10 的 slub 分配器进行探究,对比看看两者的实现有何不同,做了哪些"必要的"改进。

slub 分配器

关于 slub 分配器的基本原理与 slab 分配器类似,只是相较于 slab 更快更直接以及更简单,主要对涉及的结构体进行学习,再以 kmalloc 为入口开始探究,并对比一下初始化过程的不同

主要数据结构

struct kmem_cache {struct kmem_cache_cpu __percpu *cpu_slab;/* Used for retrieving partial slabs, etc. */slab_flags_t flags;unsigned long min_partial;unsigned int size;	/* The size of an object including metadata */unsigned int object_size;/* The size of an object without metadata */struct reciprocal_value reciprocal_size;unsigned int offset;	/* Free pointer offset */struct kmem_cache_order_objects oo;/* Allocation and freeing of slabs */struct kmem_cache_order_objects max;struct kmem_cache_order_objects min;gfp_t allocflags;	/* gfp flags to use on each alloc */int refcount;		/* Refcount for slab cache destroy */void (*ctor)(void *);unsigned int inuse;		/* Offset to metadata */unsigned int align;		/* Alignment */unsigned int red_left_pad;	/* Left redzone padding size */const char *name;	/* Name (only for display!) */struct list_head list;	/* List of slab caches */
};struct kmem_cache_cpu {void **freelist;	/* Pointer to next available object */unsigned long tid;	/* Globally unique transaction id */struct page *page;	/* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIALstruct page *partial;	/* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATSunsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};
struct kmem_cache_node {spinlock_t list_lock;unsigned long nr_partial;struct list_head partial;
}
struct page {unsigned long flags;		/* Atomic flags, some possiblystruct list_head slab_list;struct kmem_cache *slab_cache; /* not slob *//* Double-word boundary */void *freelist;		/* first free object */struct {			/* SLUB */unsigned inuse:16;unsigned objects:15;unsigned frozen:1;};atomic_t _refcount;
};

其中,page 用了很多共同体,笔者将其简化只留下与 slub 相关的。对比 slab 分配器数据结构,主要不同在于:

  1. slub 本地缓存 kmem_cache_cpu 相较于 slab 的 array_cache ,由存储 obj 指针的数组变为直接存储一整个 slub ,并添加了 partial 串联小部分可用 slub。该改变意味着本地缓存可分配容量变大,当需要分配内存时,基本上都能够通过本地缓存进行分配,缩短了分配路径提升了分配效率
  2. kmem_cache_node 结构被简化,由多链表变成单链表,且没有共享缓存,进一步缩短了分配路径
  3. 丢弃了 color 着色

对此,绘制关系图如下
在这里插入图片描述

根据上述结构关系图,简单地描述一下 slub 分配器的分配过程,由于 slub 不在用 obj 指针数组来管理本地缓存,因此分配内存的流程会有所变化

  1. 首先根据 size 从 kmem_caches 中获取对应的 kmem_cache
  2. 从 kmem_cache 中获取本地缓存 kmem_cache_cpu ,根据其 freelist 可得到空闲 obj 的地址,该 obj 对应页由 page 指针记录
  3. 如果 freelist 为空说明当前 page 已经被分配完,则搜索 partial 关联的 slub 链表,看是否有空闲 obj ,有则将 freelist 指向,并修改 page 指针指向该 sub 所在页
  4. 如果 partial 依然没有空闲 slub ,则进入 kmem_cache_node 的 partial 链表进行进一步地搜索。如果搜索到空闲的 obj ,则只需要直接返回该 obj 地址,同时修改 kmem_cache_cpu 中 freelist 和 page 指针指向。
  5. 同时,判断 kmem_cache_cpu 中的 partial 串联的 slub 数量是否满足 slub_cpu_partial ,如果不满足则申请 page 并初始化为 slub 并添加到 kmem_cache_cpu 的 partial 中

从上述描述中,可以明显地感觉到, slub 并不需要对本地缓存进行过多的维护,对于 slab 而言,却需要总是更新 obj 指针数组的下标。

并且,可以看出本地缓存 kmem_cache_cpu 的 partial 一旦被初始化到指定数量后,就不会进行变更。变的只有 freelist 和 page 指针。意味着,本地缓存当前所使用的 slub 可能来自 kmem_cache_node 的 partial,也可能来自 kmem_cache_node 的 partial。且如果当前 slub 被分配完,搜寻下一个空闲 obj 的顺序仍然是先搜寻本地缓存的 partial ,再到 kmem_cache_node 的 partial

kmalloc

与 slub 的探究思路类似,从 kmalloc 入口进入,看看 slub 分配器如何进行内存分配的。

kmalloc => __kmalloc => __do_kmalloc\--- kmalloc_slab : 根据 size 从 kmem_caches 中获取相应 kmem_cache--- slab_alloc => slab_alloc_node--- raw_cpu_ptr : 获取本地缓存--- __slab_alloc => ___slab_alloc : 进一步获取obj--- kasan_kamlloc

kmalloc_slab 根据传入参数 size ,从 kmalloc_caches 获取相应的 kmem_cache,比较简单

struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{unsigned int index;if (size <= 192) {if (!size)return ZERO_SIZE_PTR;index = size_index[size_index_elem(size)];} else {if (WARN_ON_ONCE(size > KMALLOC_MAX_CACHE_SIZE))return NULL;index = fls(size - 1);}return kmalloc_caches[kmalloc_type(flags)][index];
}

slab_alloc_node 相较于 slab 分配器的 ____cache_alloc 就简单了许多。

  1. 先是通过 raw_cpu_ptr 获取当前 cpu 对应的本地缓存 kmem_cache_cpu ,并从中获取可用的 obj(即 freelist 指向的地址)
  2. 如果没有则调用 __slab_alloc 进入 本地缓存链表 partial 进行获取,还是没有则从 kmem_cache_node 的 partial 获取,再没有只能申请新的 page 并初始化 slub
static __always_inline void *slab_alloc_node(struct kmem_cache *s,gfp_t gfpflags, int node, unsigned long addr)
{void *object;struct kmem_cache_cpu *c;struct page *page;unsigned long tid;struct obj_cgroup *objcg = NULL;s = slab_pre_alloc_hook(s, &objcg, 1, gfpflags);if (!s)return NULL;
redo:do {tid = this_cpu_read(s->cpu_slab->tid);c = raw_cpu_ptr(s->cpu_slab);} while (IS_ENABLED(CONFIG_PREEMPTION) &&unlikely(tid != READ_ONCE(c->tid)));barrier();object = c->freelist;page = c->page;if (unlikely(!object || !page || !node_match(page, node))) {object = __slab_alloc(s, gfpflags, node, addr, c);} // 省略部分代码return object;
}

假设本地缓存的 freelist 中已经获取不到空闲 obj,则需要通过 __slab_alloc 进行进一步地获取,该函数的调用阶段主要如下所示

__slab_alloc--- redo:--- get_freelist--- load_freelist:--- new_slab:--- slub_percpu_partial--- new_slab_objects--- get_partial--- get_partial_node
  1. 调用 slub_percpu_partial 先从本地缓存的 partial 链表上进行空闲 obj 的获取
  2. 如果获取不到,则调用 new_slab_objects–>get_partial_node 进入 kmem_cache_node 的 partial 链表进行获取
  3. 获取到的 obj 地址要同步到 kmem_cache_cpu 的 freelist 上,并返回
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,unsigned long addr, struct kmem_cache_cpu *c)
{void *freelist;struct page *page;stat(s, ALLOC_SLOWPATH);page = c->page;if (!page) {/** if the node is not online or has no normal memory, just* ignore the node constraint*/if (unlikely(node != NUMA_NO_NODE &&!node_state(node, N_NORMAL_MEMORY)))node = NUMA_NO_NODE;goto new_slab;}
redo:if (unlikely(!node_match(page, node))) {/** same as above but node_match() being false already* implies node != NUMA_NO_NODE*/if (!node_state(node, N_NORMAL_MEMORY)) {node = NUMA_NO_NODE;goto redo;} else {stat(s, ALLOC_NODE_MISMATCH);deactivate_slab(s, page, c->freelist, c);goto new_slab;}}/** By rights, we should be searching for a slab page that was* PFMEMALLOC but right now, we are losing the pfmemalloc* information when the page leaves the per-cpu allocator*/if (unlikely(!pfmemalloc_match(page, gfpflags))) {deactivate_slab(s, page, c->freelist, c);goto new_slab;}/* must check again c->freelist in case of cpu migration or IRQ */freelist = c->freelist;if (freelist)goto load_freelist;freelist = get_freelist(s, page);if (!freelist) {c->page = NULL;stat(s, DEACTIVATE_BYPASS);goto new_slab;}stat(s, ALLOC_REFILL);load_freelist:/** freelist is pointing to the list of objects to be used.* page is pointing to the page from which the objects are obtained.* That page must be frozen for per cpu allocations to work.*/VM_BUG_ON(!c->page->frozen);c->freelist = get_freepointer(s, freelist);c->tid = next_tid(c->tid);return freelist;new_slab:if (slub_percpu_partial(c)) {page = c->page = slub_percpu_partial(c);slub_set_percpu_partial(c, page);stat(s, CPU_PARTIAL_ALLOC);goto redo;}freelist = new_slab_objects(s, gfpflags, node, &c);if (unlikely(!freelist)) {slab_out_of_memory(s, gfpflags, node);return NULL;}page = c->page;if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))goto load_freelist;/* Only entered in the debug case */if (kmem_cache_debug(s) &&!alloc_debug_processing(s, page, freelist, addr))goto new_slab;	/* Slab failed checks. Next slab needed */deactivate_slab(s, page, get_freepointer(s, freelist), c);return freelist;
}

具体地,slub_percpu_partial 如果能获取到可用的 slub ,则直接修改 kmem_cache_cpu 的 page 指针指向该 slub ,然后进入 redo 阶段,将该 page 的 freelist 拿出来,并作为 kmem_cache_cpu 新的 freelist ,进行返回

如果 slub_percpu_partial 获取不到 slub ,则调用 new_slab_objects 进行获取。其调用链如下

new_slab_objects--- get_partial--- get_partial_node--- get_any_partial--- new_slab

首先,调用 get_partial 进行 kmem_cache_node 关联的 partial 搜索。其中先搜索当前 cpu 对应的 partial ,调用 get_partial_node,如果搜索不到,则调用 get_any_partial 搜索其他 node 上的 partial,还搜不到则通过 new_slab 申请新的 slub

要注意的是,在获取到 obj 地址后,需要将对应 page 的 freelist 置空,并修改 kmem_cache_cpu 的 page 指针,后续也会将 kmem_cache_cpu 的 freelist 指向获取到的 obj 地址

static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,int node, struct kmem_cache_cpu **pc)
{void *freelist;struct kmem_cache_cpu *c = *pc;struct page *page;WARN_ON_ONCE(s->ctor && (flags & __GFP_ZERO));freelist = get_partial(s, flags, node, c);if (freelist)return freelist;page = new_slab(s, flags, node);if (page) {c = raw_cpu_ptr(s->cpu_slab);if (c->page)flush_slab(s, c);/** No other reference to the page yet so we can* muck around with it freely without cmpxchg*/freelist = page->freelist;page->freelist = NULL;stat(s, ALLOC_SLAB);c->page = page;*pc = c;}return freelist;
}

更细节的是,在遍历 partial 的过程中,会进行本地缓存 kmem_cache_cpu 的 partial 填充,直到其满足 available > slub_cpu_partial(s) / 2。要注意的是,尽管填充了 kmem_cache_cpu 的 partial ,但分配的空闲 obj 是 kmem_cache_node 的

static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,struct kmem_cache_cpu *c, gfp_t flags)
{struct page *page, *page2;void *object = NULL;unsigned int available = 0;int objects;if (!n || !n->nr_partial)return NULL;spin_lock(&n->list_lock);list_for_each_entry_safe(page, page2, &n->partial, slab_list) {void *t;if (!pfmemalloc_match(page, flags))continue;t = acquire_slab(s, n, page, object == NULL, &objects);if (!t)break;available += objects;if (!object) {c->page = page;stat(s, ALLOC_FROM_PARTIAL);object = t;} else {put_cpu_partial(s, page, 0);stat(s, CPU_PARTIAL_NODE);}if (!kmem_cache_has_cpu_partial(s)|| available > slub_cpu_partial(s) / 2)break;}spin_unlock(&n->list_lock);return object;
}
static int transfer_objects(struct array_cache *to,struct array_cache *from, unsigned int max)
{/* Figure out how many entries to transfer */int nr = min3(from->avail, max, to->limit - to->avail);if (!nr)return 0;memcpy(to->entry + to->avail, from->entry + from->avail -nr,sizeof(void *) *nr);from->avail -= nr;to->avail += nr;return nr;
}

对于 new_slab ,主要做的事是申请一页page,并根据 slub 的要求进行初始化,在此就不展开。需要留意的是申请好了 slub 之后,似乎并没有将其关联到前述两类 partial 中,而是将地址直接返回,并更新 kmem_cache_cpu 的 page 指针。

至此,slub 的分配与扩容梳理完成


http://chatgpt.dhexx.cn/article/ZlO1CRtV.shtml

相关文章

linux内核虚拟内存之slub分配器

上一章主要讲述以页为最小单位进行内存分配的伙伴管理算法&#xff0c;较大程度上避免了内存碎片问题。而实际上对内存的申请却不是每次都申请一个页面的&#xff08;比如文件节点&#xff0c;任务描述符等结构体内存&#xff09;&#xff0c;通常是远小于一个内存页面的大小&a…

4.19内核SLUB内存分配器

初始化 内核的大部分管理数据结构都是通过kmalloc分配内存的&#xff0c;那么slab本身结构的内存管理就出现了一个鸡与蛋的问题&#xff0c;slab数据结构所需内存远小于一整页的内存块&#xff0c;这些最适合kmalloc分配&#xff0c;而kmalloc只有在slab初始化完之后才能使用。…

SLUB内存管理之slub初始化

在讲slub内存管理涉及的四个函数之前&#xff0c;先从slub内存分配算法的初始化开始。系统启动时&#xff0c;会进行slub内存分配算法的初始化&#xff0c;函数流程是&#xff1a;start_kernel() -> mm_init()->kmem_cache_init()。在start_kernel()函数中的setup_arch()…

SLUB DEBUG原理

1. 前言 在工作中&#xff0c;经常会遇到由于越界导致的各种奇怪的问题。为什么越界访问导致的问题很奇怪呢&#xff1f;在工作差不多半年的时间里我就遇到了很多越界访问导致的问题&#xff08;不得不吐槽下IC厂商提供的driver&#xff0c;总是隐藏着bug&#xff09;。比如说…

slub allocator工作原理

前言 在Linux中&#xff0c;伙伴系统&#xff08;buddy system&#xff09;是以页&#xff08;1page等于4K&#xff09;为单位管理和分配内存。对于小内存的分配&#xff0c;如果还是使用伙伴系统进行内存分配&#xff0c;就会导致严重浪费内存。此时&#xff0c;slab分配器就应…

SLUB缓存管理

注&#xff1a;本文分析基于linux-4.18.0-193.14.2.el8_2内核版本&#xff0c;即CentOS 8.2 1、关于SLUB 目前大多数系统都不使用slab作为缓存的管理模式&#xff0c;转而使用slub&#xff0c;比如CentOS 7和8默认都是使用slub管理器。slub是基于slab的进一步优化改进&#x…

slub debug(linux4.16.1)

在命令行选项中添加slub_debugUFPZ&#xff0c;以使能相关检测功能&#xff0c;其含义如下&#xff1a; &#xff08;1&#xff09;U&#xff1a;跟踪该slab内存的相关属性&#xff0c;如其创建时所属的cpu、进程、时间 &#xff08;2&#xff09;F&#xff1a;开启sanity检查功…

linux内核内存管理slub

一、概述 linux内存管理核心是伙伴系统&#xff0c;slab&#xff0c;slub&#xff0c;slob是基于伙伴系统之上提供api&#xff0c;用于内核内存分配释放管理&#xff0c;适用于小内存&#xff08;小于&#xff11;页&#xff09;分配与释放&#xff0c;当然大于&#xff11;页…

内存管理 slub算法

内核管理页面使用了2个算法&#xff1a;伙伴算法和slub算法&#xff0c;伙伴算法以页为单位管理内存&#xff0c;但在大多数情况下&#xff0c;程序需要的并不是一整页&#xff0c;而是几个、几十个字节的小内存。于是需要另外一套系统来完成对小内存的管理&#xff0c;这就是s…

linux slub分配器,slub分配器

原标题&#xff1a;slub分配器 概述&#xff1a; Linux的物理内存管理采用了以页为单位的buddy system(伙伴系统)&#xff0c;但是很多情况下&#xff0c;内核仅仅需要一个较小的对象空间&#xff0c;而且这些小块的空间对于不同对象又是变化的、不可预测的&#xff0c;所以需要…

Linux内存管理(八): slub分配器和kmalloc

kernel: 5.10 Arch: aarch64 上文 介绍过&#xff0c; 伙伴系统在分配内存时是以物理页为单位的&#xff0c;但在很多场景下&#xff0c;内存需要分配的大小是以字节为单位的&#xff0c;达不到一个物理页的大小。如果继续使用伙伴系统进行内存分配&#xff0c; 那么就会出现严…

Linux 踩内存 slub,Linux SLUB 内存分配器分析

本文简介 本文主要介绍了Linux SLUB分配的产生原因、设计思路及其代码分析。适合于对Linux内核&#xff0c;特别是对Linux内存分配器感兴趣的读者。 1.为何需要SLUB&#xff1f; Linux SLUB内存分配器合入Linux主分支已经整整10年了&#xff01;并且是Linux目前默认的内存分配器…

SLUB

 内核管理页面使用了2个算法:伙伴算法和slub算法,伙伴算法以页为单位管理内存,但在大多数情况下,程序需要的并不是一整页,而是几个、几十个字节的小内存。于是需要另外一套系统来完成对小内存的管理,这就是slub系统。slub系统运行在伙伴系统之上,为内核提供小内存管…

linux内存源码分析 - SLUB分配器概述

本文为原创&#xff0c;转载请注明&#xff1a;http://www.cnblogs.com/tolimit/ SLUB和SLAB的区别 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内…

Linux 内存管理(三)—— SLUB

目录 一、概述 二、SLUB 2.1 数据结构 2.2 初始化 2.2.1 静态建立过程 2.3 API 2.3.1 kmem_cache_create 2.3.2 kmem_cache_alloc 2.3.3 kmem_cache_free 2.3.4 kmalloc/kfree 三、参考 一、概述 伙伴系统最小的分配单位是页&#xff0c;这对于较小的内存需求会造…

SLUB内存管理的4个主要接口函数介绍(4)

slub内存管理的4个主要接口函数如下&#xff08;参考kernel-4.19&#xff09;&#xff1a; //slab缓存的创建 struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *)); //slab object的分配 void *k…

Linux内核:内存管理——SLUB分配器

SLUB和SLAB的区别 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内存分配算法&#xff0c;主要用于嵌入式系统。慢慢的slab分配器或许会被slub取代&…

SLUB的引入及举例说明

我们都知道Buddy分配器是按照页的单位分配的&#xff08;Buddy系统分配器实现&#xff09;&#xff0c;如果我们需要分配几十个字节&#xff0c;几百个字节的时候&#xff0c;就需要用到SLAB分配器。 SLAB分配器专门是针对小内存分配而设计的&#xff0c;比如我们驱动中常见的…

电信光猫改桥接模式

如果只是改桥接 可以试试下面这两个地址&#xff1a;http://192.168.1.1/bridge_route.gchhttp://192.168.1.1:8080/bridge_route.gch 转载于:https://www.cnblogs.com/Devopser/p/11257535.html

电信光猫改桥接还在苦苦破解超级密码吗?

电信光猫路由改桥接&#xff0c;不同的地区有不通的方法。比较幸运的地区和终端&#xff0c;有通用的超级密码。但是不幸的地区&#xff0c;就需要通过破解这个超级密码。我就属于比较不幸的地区&#xff0c;遇到不幸的终端&#xff1a;天翼网关TEWA-708G。然后按照网上大神的破…