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

article/2025/11/10 2:34:34

kernel: 5.10
Arch: aarch64


上文 介绍过, 伙伴系统在分配内存时是以物理页为单位的,但在很多场景下,内存需要分配的大小是以字节为单位的,达不到一个物理页的大小。如果继续使用伙伴系统进行内存分配, 那么就会出现严重的内存内碎片问题。 内存内碎片指的是已经被分配出去却不能被利用的内存空间, 一般分配的内存空间大于请求所需的内存空间就会产生内存内碎片。

slub分配器就是为了解决小内存分配问题的。

原理

slub分配器依赖于伙伴系统,slub分配器所做的事情就是把伙伴系统分配的大块内存进一步的细分成很多份小块内存进行管理。
如下图所示:
在这里插入图片描述

至上而下:
(1) slab缓存: 一个或多个大小相同的slab页会组成一个slab缓存。主要控制slab页的布局。
(2) slab页: 一个slab由一个或多个连续的物理页组成
(3) object对象:细粒度内存分配对象。确定一个size,依照该大小将上面的 SLAB 切分成相同大小的小块内存。

这篇文章描述伙伴系统buddy和slub系统的关系就是批发商和零售商的关系。我觉得很合理。

伙伴系统是某白酒生产商,不过该生产商只按罐来卖。 然后有小的零售商去批发白酒,有的零售商批发10罐,有的零售商批发20罐, 这些零售商就是slab缓存, 批发的一罐一罐的饮白酒就是slab页。 批发好的白酒会继续进行加工,为了形成差异化的竞争,有的零售商包装成500ml每瓶, 有的零售商包装成1000ml每瓶, 这些瓶装酒就是object对象。 消费者想要喝多大容量的酒,就去对应的零售商那里买就可以了。
在这里插入图片描述

用户态

内核实现

1. 数据结构

kmem_cache是slab缓存的数据结构
kmem_cache: [include/linux/slub_def.h]

/** Slab cache management.*/
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 */unsigned int offset;	/* Free pointer offset */
#ifdef CONFIG_SLUB_CPU_PARTIAL/* Number of per cpu partial objects to keep around */unsigned int cpu_partial;
#endifstruct 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 */
#ifdef CONFIG_NUMAunsigned int remote_node_defrag_ratio;
#endif
#ifdef CONFIG_SLAB_FREELIST_RANDOMunsigned int *random_seq;
#endifunsigned int useroffset;	/* Usercopy region offset */unsigned int usersize;		/* Usercopy region size */struct kmem_cache_node *node[MAX_NUMNODES];
};
成员描述
cpu_slabpercpu 类型的 slab, percpu 类型的 slab, 是每个CPU的本地对象缓冲池,主要是为了解决多核之间的锁竞争问题。
flagsslab 相关的标志位
min_partial每个node结点中部分空slab缓冲区数量不能低于这个值
size一个缓存块(object)所占用的内存空间,包含对齐字节
object_sizeobject 实际大小
offsetslub 中利用空闲的 object 内存,来保存下一个空闲 object 的指针,以此组成一个链表结构,该 offset 就是存放 next 指针的基地址偏移,通常情况下是 0。
cpu_partial限制 cpu_slab 上保存的 partial 链表数量
oostruct kmem_cache_order_objects 的结构体, 低16位表示object 数量,高16位表示slab的order,即slab 占用2 ^ order个页
max限定 oo 的上限
min限定 oo 的下限
allocflags从 buddy 子系统分配内存时使用的掩码
refcount引用计数, 内存回收机制会用到
ctor创建slab时的构造函数
inuse元数据的偏移量
align对齐字节数
red_left_pad用于检测左oob
nameslab缓存的名称
list链表节点,通过该节点将当前 kmem_cache 链接到 slab_caches 链表中。
remote_node_defrag_ratio用于NUMA架构,该值越小,越倾向于在本结点分配对象
useroffsetusercopy区域的偏移量
usersizeusercopy区域的大小
mode[MAX_NUMNODES]是每个内存节点的共享对象缓冲池。MAX_NUMNODES 就是 NUMA node 节点的数量

再着重看下cpu_slab和node, 分别对应kmem_cache_cpukmem_cache_node 这两个数据结构

kmem_cache_cpu: [include/linux/slub_def.h]

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
};
成员描述
freelist指向下一个可用的object。
tid这是一个用作校验的字段,主要用来判断tid和kmem_cache是否由同一个CPU访问。
page指向当前使用的slab
partial指向当前cpu上缓存的部分空闲slab链表

kmem_cache_node: [mm/slab.h]

struct kmem_cache_node {spinlock_t list_lock;unsigned long nr_partial;struct list_head partial;
};
成员描述
list_lock自旋锁,保护数据
nr_partial当前 node 上保留的 partial slab 的数量
partial连接 partial slab 的链表头

page中描述Slub信息的字段:

struct page {/* 如果flag设置成PG_slab,表示页属于slub分配器 */unsigned long flags;union {struct address_space *mapping;  /* 指向当前slab中第一个object */void *s_mem;      /* slab first object */atomic_t compound_mapcount;  /* first tail page */};union {pgoff_t index;    /* Our offset within mapping. *//* 指向当前slab中第一个空闲的object */void *freelist;    /* sl[aou]b first free object */};union {unsigned counters;struct {union {atomic_t _mapcount;unsigned int active;    /* SLAB */struct {      /* SLUB *//* 该slab中已经分配使用的object数量 */unsigned inuse:16;/* 该slab中的所有object数量 */unsigned objects:15;/* * 如果slab在kmem_cache_cpu中,表示处于冻结状态;* 如果slab在kmem_cache_node的部分空闲slab链表中,表示处于解冻状态*/unsigned frozen:1;};int units;      /* SLOB */};atomic_t _refcount;};};union {/* 作为链表节点加入到kmem_cache_node的部分空闲slab链表中struct list_head lru;  /* Pageout list   */struct dev_pagemap *pgmap; struct {    /* slub per cpu partial pages */struct page *next;  /* Next partial slab */int pages;  /* Nr of partial slabs left */int pobjects;  /* Approximate # of objects */};struct rcu_head rcu_head;struct {unsigned long compound_head; /* If bit zero is set */unsigned int compound_dtor;unsigned int compound_order;};};union {unsigned long private;struct kmem_cache *slab_cache;  /* SL[AU]B: Pointer to slab */};......}

在这里插入图片描述

2. API

2.1 kmem_cache_create

内核通过kmem_cache_create()接口来创建一个slab缓存。

struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,slab_flags_t flags, void (*ctor)(void *))

一共有5个参数。

  • name: 要创建的slab对象的名称
  • size: slab对象的大小
  • align: slab对象的对齐大小
  • flags: slab内存分配器的掩码和标志位, 比如常用的SLAB_HWCACHE_ALIGN标志位,创建的kmem_cache管理的object按照硬件cache 对齐
  • ctor: 对象的构造函数

kmem_cache_create()函数的流程如下图所示:
在这里插入图片描述

  • kmem_cache_alias()函数用于查找是否有现成的slab描述符可以使用, 如果有直接退出。
  • create_cache()函数创建slab描述符
  • kmem_cache_zalloc()函数分配一个kmem_cache数据结构
  • kmem_cache_open()函数是核心函数, 对结构体的成员进行初始化。
    calcute_sizes() 用于初始化object数目, 大小,分配order等值;
    set_min_partial()设置kmem_cache中的min_partial,它表示kmem_cache_node中partial链表可挂入的slab数量;
    set_cpu_partial()设置kmem_cache中的cpu_partial,它表示per cpu partial上所有slab中free object总数;
    init_kmem_cache_nodes()为每个节点分配kmem_cache_node;
    alloc_kmem_cache_cpus()为kmem_cache_cpu变量创建每CPU副本;

2.2 kmem_cache_alloc

在这里插入图片描述

  • get_freelist() 从percpu缓存的页面中获取freelist, 获取成功则返回
  • new_slab_object()会先调用get_partial()从Node的partial链表中获取slab页,获取成功则返回; 如果没有获取到,则allocate_slab()从伙伴系统分配slab页面,并初始化slab页面中的空闲对象
  • get_free_pointer_safe() 获取下一个object对象
  • this_cpu_cmpxhcg_double()比较object和next object并进行交换处理
  • prefetch_freepointer() 将next_object地址放到cacheline,提高命中率

kmem_cache_alloc() 首先会通过local_irq_save()函数关闭本地中断,防止在处理percpu的slabs 不会因为内核调度而产生变化。
和Buddy System中分配页面类似,slub分配器存在快速路径和慢速路径两种,所谓的快速路径就是per-CPU缓存,可以无锁访问,因而效率更高。
大致流程是:会先判断本地缓冲池有没有空闲的对象,有的话直接获取slab对象,—> 如果没有就会则从Node管理的slab页面中迁移slab页到per-CPU缓存中,再重新分配---->当Node管理的slab页面也不足的情况下,则从Buddy System中分配新的页面,添加到per-CPU缓存中。

3. kmalloc

内核中常用的kmalloc()函数的核心实现就是slub机制.

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{if (__builtin_constant_p(size)) {                     ---------------1if (size > KMALLOC_MAX_CACHE_SIZE)                ---------------2return kmalloc_large(size, flags);index = kmalloc_index(size);                      -------------------3if (!index)return ZERO_SIZE_PTR;return kmem_cache_alloc_trace(kmalloc_caches[kmalloc_type(flags)][index],flags, size);}return __kmalloc(size, flags);
}

(1) __builtin_constant_p编译器内联函数,判断传入参数是否为常量。如果是变量,直接调用__kmalloc()函数。
(2) 如果分配的size大于KMALLOC_MAX_CACHE_SIZE , 直接调用kmalloc_large()函数

#ifdef CONFIG_SLUB
#define KMALLOC_SHIFT_HIGH	(PAGE_SHIFT + 1)
#define KMALLOC_MAX_CACHE_SIZE	(1UL << KMALLOC_SHIFT_HIGH)
#endif

可以看出KMALLOC_MAX_CACHE_SIZE 大小为2 * PAGE_SIZE = 8k.

(3) 系统启动初期会创建多个管理不同大小对象的kmem_cache。 通过`kmalloc_index()函数查找符合满足分配大小的最小kmem_cache。

static __always_inline unsigned int kmalloc_index(size_t size)
{if (!size)return 0;if (size <= KMALLOC_MIN_SIZE)return KMALLOC_SHIFT_LOW;if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)return 1;if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)return 2;if (size <=          8) return 3;if (size <=         16) return 4;if (size <=         32) return 5;if (size <=         64) return 6;if (size <=        128) return 7;if (size <=        256) return 8;if (size <=        512) return 9;if (size <=       1024) return 10;if (size <=   2 * 1024) return 11;if (size <=   4 * 1024) return 12;if (size <=   8 * 1024) return 13;if (size <=  16 * 1024) return 14;if (size <=  32 * 1024) return 15;if (size <=  64 * 1024) return 16;if (size <= 128 * 1024) return 17;if (size <= 256 * 1024) return 18;if (size <= 512 * 1024) return 19;if (size <= 1024 * 1024) return 20;if (size <=  2 * 1024 * 1024) return 21;if (size <=  4 * 1024 * 1024) return 22;if (size <=  8 * 1024 * 1024) return 23;if (size <=  16 * 1024 * 1024) return 24;if (size <=  32 * 1024 * 1024) return 25;if (size <=  64 * 1024 * 1024) return 26;BUG();/* Will never be reached. Needed because the compiler may complain */return -1;
}

比如通过kmalloc(20, GFP_KERNEL)申请内存,系统会从名称“kmalloc-32”管理的slab缓存池中分配一个对象, 即使浪费了12Byte内存。
但是从这个函数来看,最大可以到64MB, 这里应该写的有问题, slab最大宏定义为32M, 64M这个分支多少显得有些多余。

5. 参考资料

图解slub
Slab Memory Allocator
linux内存子系统 - slub 分配器0 - slub原理


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

相关文章

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。然后按照网上大神的破…

获取电信光猫TEWA-600超级管理密码,修改电信光猫为桥接模式

文章转载&#xff1a;玩转盒子 前些年各地运营商响应国家政策&#xff0c;光进铜退小区宽带由网线入户改成光缆入户&#xff0c;这样必须用光猫把光信号转换成电信号。我用的是中国电信的宽带&#xff0c;2017年我们小区也进行了网络改造&#xff0c;网线入户变成了光缆入户。…

友华PT921G光猫破解获取超级密码和更改桥接模式

获取超级密码 1.登陆光猫管理地址192.168.1.1 2.打开新的窗口输入&#xff1a;http://192.168.1.1/romfile.cfg ,就能下载到配置文件 3.用记事本打开romfile.cfg&#xff0c;点击编辑–>查找–>输入telecomadmin->点击查找下一个 4.查找到username“telecomadmin”,而…

为了改桥接,我决定破解中兴F450G V2光猫

还记得我之前买了个猫棒来替换光猫么&#xff1f;用了一个来月&#xff0c;发现这玩意真的不稳定&#xff0c;短则几分钟长则一两天它必定自己重启一次&#xff0c;导致我的网络时不时就会断线。这玩意不好使&#xff0c;我也没有别的光猫&#xff0c;只好找电信装维师傅给我改…

大话设计模式-桥接模式

使用场景&#xff1a;桥接模式的核心意图就是将这些实现独立出来&#xff0c;让它们各自地变化。这就使得每种实现的变化不会影响其他实现&#xff0c;从而达到应对变化的目的。 多用聚合&#xff0c;少用继承 1. 手机软件抽象类、通讯录类、游戏类 package com.hj.designPat…

电信光猫/烽火HG6543c1光猫超级密码获取改桥接模式( 中国电信浙江公司定制天翼网关3.0)

第一步&#xff1a;开telnet 浏览器网址栏输入&#xff1a;192.168.1.1:8080/cgi-bin/telnetenable.cgi?telnetenable1 第二步&#xff1a;使用pytty软件登入光猫 &#xff08;Putty软件下载&#xff1a;https://download.csdn.net/download/qq_34885669/12098009&#xff0…

保姆级-光猫改桥接-路由拨号-openwrt端口转发-阿里云DNS域名解析访问家中设备

准备&#xff1a; 1.公网ip&#xff08;江苏省电信&#xff0c;电话1分钟解决&#xff09; 2.域名(最好备案了) 3.路由器(我的是红米AC2100刷openwrt&#xff0c;重点路由器要有动态dns服务的功能&#xff0c;端口转发功能什么路由器都有) 往期教程 路由器固件刷写 红米AC210…

单臂路由 光猫桥接 一根网线复用

由于弱电箱太小&#xff0c; 二级路由拨号只能放客厅里。 这就导致其他房间无法上网了。 下面是单臂路由设置方法&#xff0c; 光猫桥接的端口下的设备&#xff0c;同样可以上网。 充分利用光猫端口。 前提&#xff1a; 光猫需要超级密码 二级路由固件为 openwrt ​​​​​

光猫桥接模式路由器拨号成功 端口映射失败的原因

路由器拨号成功&#xff0c;光猫必须是桥接模式。 光猫从路由模式设置为桥接模式的操作过程: 在浏览器地址栏中输入光猫的地址192.168.1.1&#xff0c; 进入光猫的登陆界面&#xff0c;输入超级用户名和密码&#xff0c; 用户&#xff1a;CMCCAdmin(或telecomadmin &#xff0…

电信网关改造无线打印服务器,电信天翼网关路由改桥接流程

大家好&#xff0c;好多网友要求我发一份网关路由改桥接的流程&#xff0c;由于平日工作太忙&#xff0c;更新点有慢&#xff0c;以后会多多分享平日工作中碰到的一些关于网络的各种问题。 路由改桥接首先准备以下两样。 1.网关一台(不管是四口还是二口都可以) 2.光猫超级密码&…

DDNS之光猫改桥接

相信有很多小伙伴在折腾远程开机的时候&#xff0c;都遇到了这样一个问题&#xff1a; 在成功实现了局域网唤醒之后&#xff0c;无论怎么设置DDNS、端口映射都没办法实现广域网唤醒。 究竟是什么一个原因导致的呢&#xff1f;亿元程序员今天就给大家讲解其中一个原因&#xff…

电信中兴f452光猫路由改桥接最简单的方式,亲自体验成功。

家里的光猫是中兴f452&#xff0c;默认是路由模式&#xff0c;改为桥接由路由器拨号&#xff0c;过程如下&#xff0c;验证成功。前提是去电信营业厅申请内网IP改公网IP&#xff0c;申请通过后再进行如下操作。 1、网上的方法很多都是用超级用户密码登陆&#xff0c;也就是用户…