内核中与驱动相关的内存操作之七(slab)

2024-03-15 07:48

本文主要是介绍内核中与驱动相关的内存操作之七(slab),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

    slab分配器,是内核为了达到高效利用内存的一种管理算法,它以牺牲一些内存空间的代价,收获了代码在时间上的利益.

 

1.slab的动机:

    在操作系统动作过程中,经常会涉及到大量对象的重复生成、使用与释放.LINUX系统中所用到的对象,比较典型的例子是inodetask_struct.这些大量的常用的对象如果每次都要从无到有生成、投入使用、使用完再释放,类似这样的操作频率很高.而且,内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间.如果遵循传统的流程"生成->使用->释放",将是一个很耗时耗力的过程,而且很容易产生内存碎片.内核为优化这种情况,提供了"内存池"这样的一种策略.大量的常用的对象的基本的核心的骨架存放在内存池,当有需要用到这些对象的时候,直接从内存池上摘取,稍加初始化就可以了,这种策略的结果就是大大提高了效率,减少内存碎片的产生.其代价就是要浪费掉一些内存空间.slab分配器就是其中比较典型的策略.

 

2.slab的策略:

    slab出现的目的就是为了实现内存的高效利用.那么,进一步的策略细则又是怎么样的呢?slab内存管理也是以树状形式去进行管理的.

cache_chain:

    slab高速缓存树的根.它下一级就是slab高速缓存.即cache_chain每一个元素都是一个kmem_cache;

kmem_cache:

    slab高速缓存.它下一级就是一定数量slab的集合.即kmem_cache所管理的slab集合有三种状态,分别是满、部分满、空,对应内核术语就是slabs_full、slabs_partial、slabs_empty;

slab:

    每一个slab都是一段连续的内存区域.它才是用来承载数据的实体内存区域.它是slab分配器管理的最小单元.上述的cache_chain、kmem_cache只是一种组织方式,对实际的数据交互有任何的影响.因此,每一个slab根据其内部对象分配的多寡归属于其所属于的kmem_cache哪个区间(slabs_full、slabs_partial和slabs_empty).因此,slab分配器用一定的算法,根据slab内部对象分配出去的多寡,把当前slab分别移到slabs_full、slabs_partial还是slabs_empty.就像我们定义了一个指针数组(假设为pa),数组里面每个元素都是一个指针(假设为p),每个指针都指向一段分配出来的内存(假设为pm).pa就相当于cache_chain,p相当于kmem_cache,pm相当于slab.

page:

    slab是一段连续的内存区域,这些内存区域就是由一个或多个page组成的.它只是标识此slab所占用的内存空间的大小,不是其成员对象;

object:

    每一个slab内部存放的元素就是对象.也就是我们目标程序所需要到的目标数据结构.比如,struct niode.
    因此,整个slab分配器的树状结构如下:

    以内核中最常见的inode的操作为例.inode对应的slab高速缓存(kmem_cache)是inode_cachep,inode_cachep包含了多个slab,并根据slab内部对象分配的多寡进行归类(满、部分满、空闲三类).每一个slab里面又包含了多个我们要操作的目标数据结构.我们以后要用到struct inode的时候,直接从inode_cachep高速缓存里面摘取就可以了,这是一个很迅速的过程.

    因此,我们要使用slab分配器,主要有下面几步:

申请高速缓存-->模块化高速缓存(存放我们自定义的目标对象数据并实现共性初始化)-->目标对象数据的操作-->目标对象的释放-->释放高速缓存

   

3.slab相关的API:

    3-1.申请一个高速缓存:

    函数原型:

struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,unsigned long flags,void (*ctor)(void*, struct kmem_cache *, unsigned long),void (*dtor)(void*, struct kmem_cache *, unsigned long))

    函数功能:

获取一块内存区域(高速缓存).

    参数说明:

name:

    高速缓存的名字.通过命令cat /proc/slabinfo可以看查当前系统的高速缓存信息.如下:

[root@seven ~]# cat /proc/slabinfo 
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>UDPLITEv6              0      0    704   11    2 : tunables    0    0    0 : slabdata      0      0      0
UDPv6                 11     11    704   11    2 : tunables    0    0    0 : slabdata      1      1      0TCPv6                 12     12   1344   12    4 : tunables    0    0    0 : slabdata      1      1      0isofs_inode_cache      0      0    376   21    2 : tunables    0    0    0 : slabdata      0      0      0ext4_inode_cache    2075   2408    576   14    2 : tunables    0    0    0 : slabdata    172    17

    可以看到ext4文件系统的节点i的高速缓存.

 

size:

    高速缓存里面最基本元素(对象)所占内存空间的大小.
[注:]不是高速缓存的大小,而是里面目标对象的大小.这个目标对象就是我们以后用来操作的目标数据结构.

 

align:

    页面第一个对象的偏移量,它可以用来确保已分配的对象进行某种特殊的对齐,最常用的就是0;
 

flags:

    位掩码.控制高速缓存分配的行为.比如SLAB_HWCACHE_ALIGN标志要求所有对象跟高速缓存行对齐;

 

ctor:

    高速缓存里面对象的"构造函数".
 

dtor:

    高速缓存里面对象的"析构函数".

    返回值:

成功则返回一个高速缓存区间,失败返回NULL.

 

    3-2.获取一个目标数据结构:

    通过函数kmem_cache_create()创建了一块高速缓存区,并把这个区规划成了大小相同的slab区域.每个slab区域存放的是具体对象数据的必须的基本骨架.相当于一池的水,已经被分割成一碗碗的量,我们需要的时候直接去端水就可以了,而不是用的时候再去手动勺."勺水"这个动作已经被kmem_cache_craete()处理了,我们后续的工作只是直接去端水--这个动作需要借助下面的内核函数kmem_cache_alloc():

    函数原型:

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

    函数功能:

从既知的高速缓存区域里面获取一个目标数据结构对象的骨架.

    参数说明:

cachep:

    既知的高速缓存.

flags:

    获取目标数据结构对象的方式,和kmalloc()函数的flags一样的意义.

 

    3-3.释放一个目标数据结构:

    和上述3-2动作相反,使用完一个目标数据结构需要把此目标数据结构返回给高速缓存,通过函数kmem_cache_free()实现.

    函数原型:

void kmem_cache_free(struct kmem_cache *cachep, void *objp)

    函数功能:

    把使用完的目标数据结构归还给其所属的高速缓存.

    参数说明:

cachep:

    目标数据结构对象所属的高速缓存. 

objp:

    指向目标数据结构对象的类型指针.

    3-4.

    如果整个高速存在对于我们来说已经没有存在意义的话,那么,可以通过函数kmem_cache_destroy()来实现.

    函数原型:

void kmem_cache_destroy(struct kmem_cache *cachep)

    函数功能:

释放一个高速缓存.

    参数说明:

cachep:

    通过函数kmem_cache_create()生成的相应的高速缓存.

 

4.slab的应用场景:

    对于slab适配器多应用于文件系统、网络协议、scsi数据包和DMA.尤其是文件系统,因为在LINUX平台,在用户空间看来,"一切皆文件".对于文件节点的生成、操作、销毁是很频繁的,几乎每个文件系统都涉及了slab内存池,如ubi、ext4、yaffs等.

 

5.实例:

    下面通过文件系统nfs为例看一下是如何使用slab内存池的.

    回到上述2,我们使用slab内存池大致包括下面几大步:

申请高速缓存-->模块化高速缓存(存放我们自定义的目标对象数据并实现共性初始化)-->目标对象数据的操作-->目标对象的释放-->释放高速缓存

    结合nfs实例fs/nfs/inode.c:

    创建一个高速缓存:

 
static int __init nfs_init_inodecache(void)
{nfs_inode_cachep = kmem_cache_create("nfs_inode_cache",sizeof(struct nfs_inode),0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD),init_once, NULL);if (nfs_inode_cachep == NULL)return -ENOMEM;return 0;
}

    我们创建了一个名为nfs_inode_cache的高速缓存.它在文件系统加载的时候被调用:

/** Initialize NFS*/
static int __init init_nfs_fs(void)
{int err;err = nfs_fs_proc_init();if (err)goto out5;err = nfs_init_nfspagecache();if (err)goto out4;err = nfs_init_inodecache();if (err)goto out3;err = nfs_init_readpagecache();if (err)goto out2;err = nfs_init_writepagecache();if (err)goto out1;err = nfs_init_directcache();if (err)goto out0;#ifdef CONFIG_PROC_FSrpc_proc_register(&nfs_rpcstat);
#endifif ((err = register_nfs_fs()) != 0)goto out;return 0;
out:
#ifdef CONFIG_PROC_FSrpc_proc_unregister("nfs");
#endifnfs_destroy_directcache();
out0:nfs_destroy_writepagecache();
out1:nfs_destroy_readpagecache();
out2:nfs_destroy_inodecache();
out3:nfs_destroy_nfspagecache();
out4:nfs_fs_proc_exit();
out5:return err;
}

 

    获取一个目标数据结构:

struct inode *nfs_alloc_inode(struct super_block *sb)
{struct nfs_inode *nfsi;nfsi = (struct nfs_inode *)kmem_cache_alloc(nfs_inode_cachep, GFP_KERNEL);if (!nfsi)return NULL;nfsi->flags = 0UL;nfsi->cache_validity = 0UL;
#ifdef CONFIG_NFS_V3_ACLnfsi->acl_access = ERR_PTR(-EAGAIN);nfsi->acl_default = ERR_PTR(-EAGAIN);
#endif
#ifdef CONFIG_NFS_V4nfsi->nfs4_acl = NULL;
#endif /* CONFIG_NFS_V4 */return &nfsi->vfs_inode;
}

    当我们需要操作一个nfs文件系统的nfs_inode时,直接从上述分配的nfs_inode_cachep摘取,然后再根据一些个性的需要实现初始化.如上述的nfsi.
[注:]所有的文件系统对上统一为vfs,从nfs的返回值也可见一斑!

 

    释放一个目标数据结构:

void nfs_destroy_inode(struct inode *inode)
{kmem_cache_free(nfs_inode_cachep, NFS_I(inode));
}

 

    释放一个高速缓存:

static void nfs_destroy_inodecache(void)
{kmem_cache_destroy(nfs_inode_cachep);
}

    它在文件系统卸载的时候被调用:

static void __exit exit_nfs_fs(void)
{nfs_destroy_directcache();nfs_destroy_writepagecache();nfs_destroy_readpagecache();nfs_destroy_inodecache();nfs_destroy_nfspagecache();
#ifdef CONFIG_PROC_FSrpc_proc_unregister("nfs");
#endifunregister_nfs_fs();nfs_fs_proc_exit();
}


 

这篇关于内核中与驱动相关的内存操作之七(slab)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/811268

相关文章

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

sysmain服务可以禁用吗? 电脑sysmain服务关闭后的影响与操作指南

《sysmain服务可以禁用吗?电脑sysmain服务关闭后的影响与操作指南》在Windows系统中,SysMain服务(原名Superfetch)作为一个旨在提升系统性能的关键组件,一直备受用户关... 在使用 Windows 系统时,有时候真有点像在「开盲盒」。全新安装系统后的「默认设置」,往往并不尽编

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

Python自动化处理PDF文档的操作完整指南

《Python自动化处理PDF文档的操作完整指南》在办公自动化中,PDF文档处理是一项常见需求,本文将介绍如何使用Python实现PDF文档的自动化处理,感兴趣的小伙伴可以跟随小编一起学习一下... 目录使用pymupdf读写PDF文件基本概念安装pymupdf提取文本内容提取图像添加水印使用pdfplum

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

使用Python的requests库来发送HTTP请求的操作指南

《使用Python的requests库来发送HTTP请求的操作指南》使用Python的requests库发送HTTP请求是非常简单和直观的,requests库提供了丰富的API,可以发送各种类型的HT... 目录前言1. 安装 requests 库2. 发送 GET 请求3. 发送 POST 请求4. 发送

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看