内核中与驱动相关的内存操作之七(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

相关文章

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

Python的time模块一些常用功能(各种与时间相关的函数)

《Python的time模块一些常用功能(各种与时间相关的函数)》Python的time模块提供了各种与时间相关的函数,包括获取当前时间、处理时间间隔、执行时间测量等,:本文主要介绍Python的... 目录1. 获取当前时间2. 时间格式化3. 延时执行4. 时间戳运算5. 计算代码执行时间6. 转换为指

Python ZIP文件操作技巧详解

《PythonZIP文件操作技巧详解》在数据处理和系统开发中,ZIP文件操作是开发者必须掌握的核心技能,Python标准库提供的zipfile模块以简洁的API和跨平台特性,成为处理ZIP文件的首选... 目录一、ZIP文件操作基础三板斧1.1 创建压缩包1.2 解压操作1.3 文件遍历与信息获取二、进阶技

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效