slab分配器(深入理解linux内核)

news/2024/10/5 15:34:53/文章来源:https://www.cnblogs.com/zzzlight/p/18269611

引子

前文介绍了使用为了解决外部碎片,使用Buddy System进行连续内存页面的分配,但对于使用内存的程序而言,Buddy System分配的内存粒度过大,假如我们需要动态申请一个内核结构体(占 20 字节),若仍然分配一页内存,这将严重浪费内存。那么该如何分配呢?slab 分配器专为小内存分配而生,由Sun公司的一个雇员Jeff Bonwick在Solaris 2.4中设计并实现。slab分配器分配内存以字节为单位,基于Buddy System的伙伴分配器的大内存进一步细分成小内存分配。换句话说,slab 分配器仍然从 Buddy 分配器中申请内存,之后自己对申请来的内存细分管理

除了提供小内存外,slab 分配器的第二个任务是维护常用对象的缓存。对于内核中使用的许多结构,初始化对象所需的时间可等于或超过为其分配空间的成本。当创建一个新的slab 时,许多对象将被打包到其中并使用构造函数(如果有)进行初始化。释放对象后,它会保持其初始化状态,这样可以快速分配对象。

SLAB详细思路还可以参考论文:The Slab Allocator: An Object-Caching Kernel Memory Allotor

slub

SLUB是SLAB的改进版本,从版本 2.6.24 开始,SLUB 分配器取代 SLAB,成为 Linux 内核的默认分配器。SLUB 通过减少 SLAB 分配器所需的大量开销,来解决 slab 分配的性能问题,一个改变是,在 SLAB 分配下每个 slab 存储的元数据,移到 Linux 内核用于每个页面的结构 page。此外,对于 SLAB 分配器,每个 CPU 都有队列以维护每个 cache 内的对象,SLUB 会删除这些队列。 对于具有大量处理器的系统,分配给这些队列的内存量是很重要的。因此,随着系统处理器数量的增加,SLUB 性能也更好。

slob

SLOB是用于嵌入式等内存容量不大的场景下的对象分配算法,SLOB 使用简单的链表结构来管理空闲内存块。只维护一个空闲块的链表,所有分配和释放操作都在这个链表上进行。其简单,开销低,但是也有一些缺点如:性能较低,由于采用线性搜索,分配和释放操作的性能较低,尤其在内存块较多时。缺乏复杂功能,如缓存和对象复用,不能满足大型系统的需求。简而言之,它就是用于简单的嵌入式系统这类场景下的。

Slab介绍

包含高速缓存(cache)的主内存区域被划分为多个 slab,每个 slab 由一个或多个连续的页框组成,这些页框既包含已分配的对象(object),也包含空闲的对象

或者说可以用这个图

以下默认皆为linux2.6.11源码(可见于 https://elixir.bootlin.com/linux/v2.6.11/source)

高速缓存

每个高速缓存都是由kmem_cache_t类型(等价于kmem_cache_s类型)的数据结构来描述的
include/linux/slab.h:

/** The slab lists of all objects.* Hopefully reduce the internal fragmentation* NUMA: The spinlock could be moved from the kmem_cache_t* into this structure, too. Figure out what causes* fewer cross-node spinlock operations.*/
struct kmem_list3 {struct list_head slabs_partial; /* partial list first, better asm code */struct list_head slabs_full;struct list_head slabs_free;unsigned long free_objects;int free_touched;unsigned long next_reap;struct array_cache *shared;
};/** kmem_cache_t** manages a cache.*/struct kmem_cache_s {
/* 1) per-cpu data, touched during every alloc/free */struct array_cache *array[NR_CPUS];unsigned int batchcount;unsigned int limit;
/* 2) touched by every alloc & free from the backend */struct kmem_list3 lists;/* NUMA: kmem_3list_t *nodelists[MAX_NUMNODES] */unsigned int objsize;unsigned int flags; /* constant flags */unsigned int num; /* # of objs per slab */unsigned int free_limit; /* upper limit of objects in the lists */spinlock_t spinlock;/* 3) cache_grow/shrink *//* order of pgs per slab (2^n) */unsigned int gfporder;/* force GFP flags, e.g. GFP_DMA */unsigned int gfpflags;size_t colour; /* cache colouring range */unsigned int colour_off; /* colour offset */unsigned int colour_next; /* cache colouring */kmem_cache_t *slabp_cache;unsigned int slab_size;unsigned int dflags; /* dynamic flags *//* constructor func */void (*ctor)(void *, kmem_cache_t *, unsigned long);/* de-constructor func */void (*dtor)(void *, kmem_cache_t *, unsigned long);/* 4) cache creation/removal */const char *name;struct list_head next;/* 5) statistics */...
};typedef struct kmem_cache_s kmem_cache_t;

slab描述符

由于slab的缓存特性,slab 分配器从 buddy 分配器中获取的物理内存称为 内存缓存(与 CPU 的硬件缓存进行区别,下文都称为缓存),使用结构体struct kmem_cache (定义在include/linux/slab.h文件中)描述。

SLAB分配器由可变数量的缓存组成,这些缓存由称为“缓存链”的双向循环链表链接在一起(如下图中的 kmem_cache 链表)。 在slab分配器的上下文中,缓存是特定类型的多个对象的管理器,例如使用cat /proc/slabinfo命令输出的mm_struct 或 fs_cache缓存,其名字保存在kmem_cache->name中(Linux 支持单个最大的 slab 缓存大小为32MB )。kmem_cache 中所有对象的大小是相同的(object_size),并且此 kmem_cache 中所有SLAB的大小也是相同的(gfporder、num)。

每个缓存节点在内存中维护称为slab的连续页块,这些页面被切成小块,用于缓存数据结构和对象。 kmem_cache的 kmem_cache_node 成员记录了该kmem_cache 下的所有 slabs 列表。形成的结构如下图所示。

kmem_cache_noed 记录了3种slab:(可见上方代码中struct kmem_list3部分)

slabs_full :已经完全分配的 slab
slabs_partial: 部分分配的slab
slabs_free:空slab,或者没有对象被分配
以上3个链表保存的是slab 描述符,Linux kernel 使用 struct page 来描述一个slab。单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从 slabs_partial 中被删除,同时插入到 slabs_full 中去。

如果一个 slab 中的对象全部分配出去了,slab cache 就会将其视为一个 full slab,表示这个 slab 此刻已经满了,无法在分配对象了。slab cache 就会到伙伴系统中重新申请一个 slab 出来,供后续的内存分配使用。

当内核将对象释放回其所属的 slab 之后,如果 slab 中的对象全部归位,slab cache 就会将其视为一个 empty slab,表示 slab 此刻变为了一个完全空闲的 slab。如果超过了 slab cache 中规定的 empty slab 的阈值,slab cache 就会将这些空闲的 empty slab 重新释放回伙伴系统中。

如果一个 slab 中的对象部分被分配出去使用,部分却未被分配仍然在 slab 中缓存,那么内核就会将该 slab 视为一个 partial slab。

这些不同状态的 slab,会在 slab cache 中被不同的链表所管理,同时 slab cache 会控制管理链表中 slab 的个数以及链表中所缓存的空闲对象个数,防止它们无限制的增长。

slab cache 中除了需要管理众多的 slab 之外,还包括了很多 slab 的基础信息。比如:

slab 对象内存布局相关信息。

slab 中的对象需要按照什么方式进行内存对齐。

一个 slab 具体到底需要多少个物理内存页 page,一个 slab 中具体能够容纳多少个 object (内存块)。

对象描述符

每个对象都有一个类型为 kmem_bufctl_t 的描述符,该描述符定义于 include/asm-xx/types.h (xx 代表相应体系结构),是一个无符号整型。

slab 的对象描述符也可以用两种方式存放:

外部对象描述符:存放在 slab 外部,位于高速缓存描述符 slabp_cache 字段指向的一个普通高速缓存中,内存区大小取决于在 slab 中所存放的对象个数(高速缓存描述符的 num 字段)。
内部对象描述符:存放在 slab 内部,正好位于描述符所描述的对象之前。

各描述符之间的关系

高速缓存描述符与 slab 描述符的关系如下:

slab 描述符与对象描述符的关系如下:

普通高速缓存和专用高速缓存

高速缓存被分为普通和专用两种,普通高速缓存只由 slab 分配器用于自己的目的,而专用高速缓存由内核的其余部分使用。

普通高速缓存
第一个高速缓存叫 kmem_cache,包含在 cache_cache 中。

slab 着色

简单概括,slab 着色就是对 slab 使用不同的颜色(不同的偏移量),尽量使得不同的对象的映射到不同的硬件高速缓存行上。着色相关的内容最终被摒弃了(slub 分配器),所以没必要细看了。

分配和释放内存

分配 slab 对象

位于mm/slab.c:

static inline void ** ac_entry(struct array_cache *ac)
{return (void**)(ac+1);
}static inline struct array_cache *ac_data(kmem_cache_t *cachep)
{return cachep->array[smp_processor_id()];
}/*** kmem_cache_alloc - Allocate an object* @cachep: The cache to allocate from.* @flags: See kmalloc().** Allocate an object from this cache.  The flags are only relevant* if the cache has no available objects.*/
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{return __cache_alloc(cachep, flags);
}static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{unsigned long save_flags;void* objp;struct array_cache *ac;cache_alloc_debugcheck_before(cachep, flags);local_irq_save(save_flags);ac = ac_data(cachep);if (likely(ac->avail)) {STATS_INC_ALLOCHIT(cachep);ac->touched = 1;objp = ac_entry(ac)[--ac->avail];} else {STATS_INC_ALLOCMISS(cachep);objp = cache_alloc_refill(cachep, flags);}local_irq_restore(save_flags);objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));return objp;
}

首先试图从本地高速缓存获得一个空闲对象,如果没有,则调用 cache_alloc_refill() 函数重新填充本地高速缓存并获得一个空闲对象。cache_alloc_refill() 函数比较复杂,这里不展开。

释放 slab 对象

位于mm/slab.c:

/*** kmem_cache_free - Deallocate an object* @cachep: The cache the allocation was from.* @objp: The previously allocated object.** Free an object which was previously allocated from this* cache.*/
void kmem_cache_free (kmem_cache_t *cachep, void *objp)
{unsigned long flags;local_irq_save(flags);__cache_free(cachep, objp);local_irq_restore(flags);
}/** __cache_free* Release an obj back to its cache. If the obj has a constructed* state, it must be in this state _before_ it is released.** Called with disabled ints.*/
static inline void __cache_free (kmem_cache_t *cachep, void* objp)
{struct array_cache *ac = ac_data(cachep);check_irq_off();objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));if (likely(ac->avail < ac->limit)) {STATS_INC_FREEHIT(cachep);ac_entry(ac)[ac->avail++] = objp;return;} else {STATS_INC_FREEMISS(cachep);cache_flusharray(cachep, ac);ac_entry(ac)[ac->avail++] = objp;}
}

首先检查本地高速缓存是否有空间给指向一个空闲对象的额外指针,如果有,该指针被加到本地高速缓存然后返回。否则,调用 cache_flusharray() 函数来清空本地高速缓存,再将指针加到本地高速缓存。 同样,cache_alloc_refill() 函数比较复杂,这里不展开。

分配通用对象

位于include/linux/slab.h:

static inline void *kmalloc(size_t size, int flags)
{if (__builtin_constant_p(size)) {int i = 0;
#define CACHE(x) \if (size <= x) \goto found; \else \i++;
#include "kmalloc_sizes.h"
#undef CACHE{extern void __you_cannot_kmalloc_that_much(void);__you_cannot_kmalloc_that_much();}
found:return kmem_cache_alloc((flags & GFP_DMA) ?malloc_sizes[i].cs_dmacachep :malloc_sizes[i].cs_cachep, flags);}return __kmalloc(size, flags);
}

mm/slab.c:

/*** kmalloc - allocate memory* @size: how many bytes of memory are required.* @flags: the type of memory to allocate.** kmalloc is the normal method of allocating memory* in the kernel.** The @flags argument may be one of:** %GFP_USER - Allocate memory on behalf of user.  May sleep.** %GFP_KERNEL - Allocate normal kernel ram.  May sleep.** %GFP_ATOMIC - Allocation will not sleep.  Use inside interrupt handlers.** Additionally, the %GFP_DMA flag may be set to indicate the memory* must be suitable for DMA.  This can mean different things on different* platforms.  For example, on i386, it means that the memory must come* from the first 16MB.*/
void * __kmalloc (size_t size, int flags)
{struct cache_sizes *csizep = malloc_sizes;for (; csizep->cs_size; csizep++) {if (size > csizep->cs_size)continue;
#if DEBUG/* This happens if someone tries to call* kmem_cache_create(), or kmalloc(), before* the generic caches are initialized.*/BUG_ON(csizep->cs_cachep == NULL);
#endifreturn __cache_alloc(flags & GFP_DMA ?csizep->cs_dmacachep : csizep->cs_cachep, flags);}return NULL;
}EXPORT_SYMBOL(__kmalloc);

释放通用对象

mm/slab.c:

/*** kfree - free previously allocated memory* @objp: pointer returned by kmalloc.** Don't free memory not originally allocated by kmalloc()* or you will run into trouble.*/
void kfree (const void *objp)
{kmem_cache_t *c;unsigned long flags;if (!objp)return;local_irq_save(flags);kfree_debugcheck(objp);c = GET_PAGE_CACHE(virt_to_page(objp));__cache_free(c, (void*)objp);local_irq_restore(flags);
}

总结

深入理解linux内核这本书实在是过老了,里面linux2.6.11内核的许多代码很明显已经存在缺陷并被后续高得多版本的内核进行了优化,就slab而言,还有许多机制如内存池、多cpu等本书没有讲明白或者属于后面slub的优化内容,以及一些内存的分配机制,本书也未说的很清楚。因而也可以看到本文实际上有很多内容并不是非常详细和逻辑相关联,其实根源是为了针对本书的阅读笔记,因为此书确实很多也写作并不逻辑相关。
本人强烈推荐可以继续阅读(https://www.cnblogs.com/binlovetech/p/17288990.html) 的内容来更清楚的了解slab。

参考:
https://www.cnblogs.com/binlovetech/p/17288990.html
https://www.dingmos.com/index.php/archives/23/#
https://freeflyingsheep.github.io/posts/kernel/memory/slab/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/731710.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

由本签名控制的文档修订版次尚未被更改,但在其后,文档已被更改

原文链接:https://blog.csdn.net/weixin_45303938/article/details/108007791 签章后修改文档如果把一个有签过名的PDF文档进行修改,再验证时可能会有以上的提示:“由本签名控制的文档修订版次尚未被更改,但在其后,文档已被更改”。出现这种情况的原因来自于PDF文档独特的…

yolov5-v7.0 目录结构

一、一级目录下各文件功能模型架构(位于 /models): 如果希望改变YOLOv5的架构,需要修改通常位于 models 目录中的模型定义文件。这可能包括改变网络的深度和宽度,更改层类型或添加新层。 训练数据(位于 /data): 为了提高模型在特定任务上的表现,需要更新位于 data 目…

从0开始搭建seldom-platform平台

一、前言 seldom-platform平台虫师已经出挺久了,但是之前因为没有linux环境,导致一直无法尝试搭建,这次自己创建个虚拟机linux环境,从0开始搭建,因为虫师的文档有些没咋搞懂,边参考边自己找资料。 二、linux环境搭建 1、下载VMware虚拟机,现在免费了,直接下载即可免费试…

【YashanDB知识库】YAS-00103 no free block in dictionary cache

【问题分类】功能使用 【关键字】YAS-00103,no free block in dictionary cache 【问题描述】执行union all 太多子查询导致报错,例子如下:【问题原因分析】选择增大DICTIONARY_CACHE_SIZE 或 SHARE_POOL_SIZE 或 两者都增大 【解决/规避方法】优先考虑增大SHARE_POOL_SIZE …

记录--vue3中使用Swiper组件

🧑‍💻 写在开头 点赞 + 收藏 === 学会🤣🤣🤣 一,安装npm i swiper二,使用 swiper/vue 导出 2 个组件:Swiper 和 SwiperSlide<template><swiper:slides-per-view="3":space-between="50"@swiper="onSwiper"@slideChange=&…

2024.6.26 CTF MISC任务清单

题目目录[HBNIS2018]caesar[SUCTF2018]single dog [HBNIS2018]caesar BUUCTF在线评测 (buuoj.cn) 打卡一看,是个密文 根据题目英文“caesar” 可知,是凯撒密码 于是我们上链接!凯撒密码在线加密解密 - 千千秀字 (qqxiuzi.cn) 即可得到 flag [SUCTF2018]single dog打开一看,…

IDEA创建Java项目的初始配置

第一步,新建项目打开settings第二步,设置Java Compiler第三步,设置项目文件和编码第四步,设置Maven仓库位置

R语言SVM支持向量机用大学生行为数据对助学金精准资助预测ROC可视化

全文链接:https://tecdat.cn/?p=34607 原文出处:拓端数据部落公众号 大数据时代的来临,为创新资助工作方式提供了新的理念和技术支持,也为高校利用大数据推进快速、便捷、高效精准资助工作带来了新的机遇。基于学生每天产生的一卡通实时数据,利用大数据挖掘与分析技术、数…

面对中国新能源车出海 韩国2024年汽车出口额仍增长5.4%?

2024年6月23日,韩国汽车制造商协会(KAMA)发布消息称,得益于环保车型和SUV车型的需求增加,韩国今年的汽车出口额有望刷新历史记录。据KAMA的预测,2024年,韩国汽车出口额将比去年同期增长5.4%,达到747亿美元,创下历史新高。如果加上汽车零部件的出口额,总额预计将达到9…

【专题】2024年中国AIGC行业应用价值研究报告合集PDF分享(附原数据表)

原文链接 :https://tecdat.cn/?p=36570 原文出处:拓端数据部落公众号 大模型的发展标志着AIGC时代的来临,没有大模型支撑的AI已成为旧时代产物,缺乏竞争力。技术的突破始终是AI发展的关键,而商业应用则是推动其迅速发展的加速器。AI的持久繁荣依赖于其商业化的成功。展望2…

别再用Excel做数据可视化了!这款免费可视化工具能够帮你大幅提高效率

现代数据分析和展示的需求已经远远超出了传统工具的能力,尤其是在需要快速、直观和高效地处理复杂数据的情况下。山海鲸可视化通过其强大的功能和易用性,成为了设计师以及各类新手用户的理想选择。下面我就以一个可视化设计师的角度,和大家简单聊聊这款可视化工具在做数据可…