linux slub分配器,slub分配器

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

原标题:slub分配器

概述:

Linux的物理内存管理采用了以页为单位的buddy system(伙伴系统),但是很多情况下,内核仅仅需要一个较小的对象空间,而且这些小块的空间对于不同对象又是变化的、不可预测的,所以需要一种类似用户空间堆内存的管理机制(malloc/free)。然而内核对对象的管理又有一定的特殊性,有些对象的访问非常频繁,需要采用缓冲机制;对象的组织需要考虑硬件cache的影响;需要考虑多处理器以及NUMA架构的影响。90年代初期,在Solaris 2.4操作系统中,采用了一种称为“slab”(原意是大块的混凝土)的缓冲区分配和管理方法,在相当程度上满足了内核的特殊需求。

多年以来,SLAB成为linux kernel对象缓冲区管理的主流算法,甚至长时间没有人愿意去修改,因为它实在是非常复杂,而且在大多数情况下,它的工作完成的相当不错。但是,随着大规模多处理器系统和 NUMA系统的广泛应用,SLAB 分配器逐渐暴露出自身的严重不足:

1). 缓存队列管理复杂;

2). 管理数据存储开销大;

3). 对NUMA支持复杂;

4). 调试调优困难;

5). 摒弃了效果不太明显的slab着色机制;

针对这些SLAB不足,内核开发人员Christoph Lameter在Linux内核2.6.22版本中引入一种新的解决方案:SLUB分配器。SLUB分配器特点是简化设计理念,同时保留SLAB分配器的基本思想:每个缓冲区由多个小的slab 组成,每个 slab 包含固定数目的对象。SLUB分配器简化kmem_cache,slab等相关的管理数据结构,摒弃了SLAB 分配器中众多的队列概念,并针对多处理器、NUMA系统进行优化,从而提高了性能和可扩展性并降低了内存的浪费。为了保证内核其它模块能够无缝迁移到SLUB分配器,SLUB还保留了原有SLAB分配器所有的接口API函数。

(注:本文源码分析基于linux-4.1.x)

整体数据结构关系如下图所示:

ecfd9ef14db948050294c52cc776a245.png

1 SLUB分配器的初始化

SLUB初始化有两个重要的工作:第一,创建用于申请struct kmem_cache和struct kmem_cache_node的kmem_cache;第二,创建用于常规kmalloc的kmem_cache。

1.1申请kmem_cache的kmem_cache

第一个工作涉及到一个“先有鸡还是先有蛋”的问题,因为创建kmem_cache需要从kmem_cache的缓冲区申请,而这时候还没有创建kmem_cache的缓冲区。kernel的解决办法是先用两个静态变量boot_kmem_cache和boot_kmem_cache_node来保存struct kmem_cach和struct kmem_cache_node缓冲区管理数据,以两个静态变量为基础申请大小为struct kmem_cache和struct kmem_cache_node对象大小的slub缓冲区,随后再从这些缓冲区中分别申请两个kmem_cache,然后把boot_kmem_cache和boot_kmem_cache_node中的内容拷贝到新申请的对象中,从而完成了struct kmem_cache和struct kmem_cache_node管理结构的bootstrap(自引导)。

void __init kmem_cache_init(void)

{

//声明静态变量,存储临时kmem_cache管理结构

static __initdata struct kmem_cache boot_kmem_cache,

boot_kmem_cache_node;

•••

kmem_cache_node =&boot_kmem_cache_node;

kmem_cache =&boot_kmem_cache;

//申请slub缓冲区,管理数据放在临时结构中

create_boot_cache(kmem_cache_node,"kmem_cache_node",

sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN);

create_boot_cache(kmem_cache,"kmem_cache",

offsetof(struct kmem_cache, node)+

nr_node_ids *sizeof(struct kmem_cache_node *),

SLAB_HWCACHE_ALIGN);

//从刚才挂在临时结构的缓冲区中申请kmem_cache的kmem_cache,并将管理数据拷贝到新申请的内存中

kmem_cache = bootstrap(&boot_kmem_cache);

//从刚才挂在临时结构的缓冲区中申请kmem_cache_node的kmem_cache,并将管理数据拷贝到新申请的内存中

kmem_cache_node = bootstrap(&boot_kmem_cache_node);

•••

}

1.2创建kmalloc常规缓存

原则上系统会为每个2次幂大小的内存块申请一个缓存,但是内存块过小时,会产生很多碎片浪费,所以系统为96B和192B也各自创建了一个缓存。于是利用了一个size_index数组来帮助确定小于192B的内存块使用哪个缓存

void __init create_kmalloc_caches(unsignedlong flags)

{

•••

/*使用SLUB时KMALLOC_SHIFT_LOW=3,KMALLOC_SHIFT_HIGH=13

也就是说使用kmalloc能够申请的最小内存是8B,最大内存是8KB

申请内存是向上对其2的n次幂,创建对应大小缓存保存在kmalloc_caches [n]*/

for(i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++){

if(!kmalloc_caches[i]){

kmalloc_caches[i]= create_kmalloc_cache(NULL,

1<< i, flags);

}

/*

* Caches that are not of the two-to-the-power-of size.

* These have to be created immediately after the

* earlier power of two caches

*/

/*有两个例外,大小为64~96B和128B~192B,单独创建了两个缓存

保存在kmalloc_caches [1]和kmalloc_caches [2]*/

if(KMALLOC_MIN_SIZE <=32&&!kmalloc_caches[1]&& i ==6)

kmalloc_caches[1]= create_kmalloc_cache(NULL,96, flags);

if(KMALLOC_MIN_SIZE <=64&&!kmalloc_caches[2]&& i ==7)

kmalloc_caches[2]= create_kmalloc_cache(NULL,192, flags);

}

•••

}

2缓存的创建与销毁

2.1缓存的创建

创建缓存通过接口kmem_cache_create进行,在创建新的缓存以前,尝试找到可以合并的缓存,合并条件包括对对象大小以及缓存属性的判断,如果可以合并则直接返回已存在的kmem_cache,并创建一个kobj链接指向同一个节点。

创建新的缓存主要是申请管理结构暂用的空间,并初始化,这些管理结构包括kmem_cache、kmem_cache_nodes、kmem_cache_cpu。同时在sysfs创建kobject节点。最后把kmem_cache加入到全局cahce链表slab_caches中。

96e236ca2953c483a472a3d82309b45e.png

2.2缓存的销毁

销毁过程比创建过程简单的多,主要工作是释放partial队列所有page,释放kmem_cache_cpu,释放每个node的kmem_cache_node,最后释放kmem_cache本身。

489681e1d29f4fa2ce4fc3b9cfc9dd21.png

3申请对象

对象是SLUB分配器中可分配的内存单元,与SLAB相比,SLUB对象的组织非常简洁,申请过程更加高效。SLUB没有任何管理区结构来管理对象,而是将对象之间的关联嵌入在对象本身的内存中,因为申请者并不关心对象在分配之前内存的内容是什么。而且各个SLUB之间的关联,也利用page自身结构进行处理。

每个CPU都有一个slab作为本地高速缓存,只要slab所在的node与申请者要求的node匹配,同时该slab还有空闲对象,则直接在cpu_slab中取出空闲对象,否则就进入慢速路径。

每个对象内存的offset偏移位置都存放着下一个空闲对象,offset通常为0,也就是复用对象内存的第一个字来保存下一个空闲对象的指针,当满足条件(flags & (SLAB_DESTROY_BY_RCU | SLAB_POISON)) 或者有对象构造函数时,offset不为0,每个对象的结构如下图。

cpu_slab的freelist则保存着当前第一个空闲对象的地址。

50dbe03cb7e32e90650ce17b6d1bdd7a.png

如果本地CPU缓存没有空闲对象,则申请新的slab;如果有空闲对象,但是内存node不相符,则deactive当前cpu_slab,再申请新的slab。

d58391d3f04d3059bae3d5424a373eab.png

deactivate_slab主要进行两步工作:

第一步,将cpu_slab的freelist全部释放回page->freelist;

第二部,根据page(slab)的状态进行不同操作,如果该slab有部分空闲对象,则将page移到kmem_cache_node的partial队列;如果该slab全部空闲,则直接释放该slab;如果该slab全部占用,而且开启了CONFIG_SLUB_DEBUG编译选项,则将page移到full队列。

page的状态也从frozen改变为unfrozen。(frozen代表slab在cpu_slub,unfroze代表在partial队列或者full队列)。

申请新的slab有两种情况,如果cpu_slab的partial队列不为空,则取出队列中下一个page作为新的cpu_slab,同时再次检测内存node是否相符,还不相符则循环处理。

如果cpu_slab的partial队列为空,则查看本node的partial队列是否为空,如果不空,则取出page;如果为空,则看一定距离范围内其它node的partial队列,如果还为空,则需要创建新slab。

创建新slab其实就是申请对应order的内存页,用来放足够数量的对象。值得注意的是其中order以及对象数量的确定,这两者又是相互影响的。order和object数量同时存放在kmem_cache成员kmem_cache_order_objects中,低16位用于存放object数量,高位存放order。order与object数量的关系非常简单:((PAGE_SIZE << order) - reserved) / size。

下面重点看calculate_order这个函数

staticinlineint calculate_order(int size,int reserved)

{

•••

//尝试找到order与object数量的最佳配合方案

//期望的效果就是剩余的碎片最小

min_objects = slub_min_objects;

if(!min_objects)

min_objects =4*(fls(nr_cpu_ids)+1);

max_objects = order_objects(slub_max_order, size, reserved);

min_objects = min(min_objects, max_objects);

//fraction是碎片因子,需要满足的条件是碎片部分乘以fraction小于slab大小

// (slab_size - reserved) % size <= slab_size / fraction

while(min_objects >1){

fraction =16;

while(fraction >=4){

order = slab_order(size, min_objects,

slub_max_order, fraction, reserved);

if(order <= slub_max_order)

return order;

//放宽条件,容忍的碎片大小增倍

fraction /=2;

}

min_objects--;

}

//尝试一个slab只包含一个对象

order = slab_order(size,1, slub_max_order,1, reserved);

if(order <= slub_max_order)

return order;

//使用MAX_ORDER且一个slab只含一个对象

order = slab_order(size,1, MAX_ORDER,1, reserved);

if(order < MAX_ORDER)

return order;

return-ENOSYS;

}

4释放对象

从上面申请对象的流程也可以看出,释放的object有几个去处:

1)cpu本地缓存slab,也就是cpu_slab;

2)放回object所在的page(也就是slab)中;另外要处理所在的slab:

2.1)如果放回之后,slab完全为空,则直接销毁该slab;

2.2)如果放回之前,slab为满,则判断slab是否已被冻结;如果已冻结,则不需要做其他事;如果未冻结,则将其冻结,放入cpu_slab的partial队列;如果cpu_slab partial队列过多,则将队列中所有slab一次性解冻到各自node的partial队列中。

值得注意的是cpu partial队列的功能是个可选项,依赖于内核选项CONFIG_SLUB_CPU_PARTIAL,如果没有开启,则不使用cpu partial队列,直接使用各个node的partial队列。返回搜狐,查看更多

责任编辑:


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

相关文章

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

获取电信光猫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…