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

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

log4j2相关配置说明以及${sys:catalina.home}应用

${sys:catalina.home} 等价于 System.getProperty("catalina.home") 就是Tomcat的根目录:  C:\apache-tomcat-7.0.77 <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" /> 2017-08-10

Node Linux相关安装

下载经编译好的文件cd /optwget https://nodejs.org/dist/v10.15.3/node-v10.15.3-linux-x64.tar.gztar -xvf node-v10.15.3-linux-x64.tar.gzln -s /opt/node-v10.15.3-linux-x64/bin/npm /usr/local/bin/ln -s /opt/nod

git ssh key相关

step1、进入.ssh文件夹   (windows下 下载git客户端)   cd ~/.ssh(windows mkdir ~/.ssh) step2、配置name和email git config --global user.name "你的名称"git config --global user.email "你的邮箱" step3、生成key ssh-keygen

zookeeper相关面试题

zk的数据同步原理?zk的集群会出现脑裂的问题吗?zk的watch机制实现原理?zk是如何保证一致性的?zk的快速选举leader原理?zk的典型应用场景zk中一个客户端修改了数据之后,其他客户端能够马上获取到最新的数据吗?zk对事物的支持? 1. zk的数据同步原理? zk的数据同步过程中,通过以下三个参数来选择对应的数据同步方式 peerLastZxid:Learner服务器(Follo