内存管理专题05 slab分配器

在这里插入图片描述

一.slab核心思想

块分配器是为了解决小块内存的分配问题,linux提供块分配器,最早实现块分配器是slab分配器,slab的作用不仅仅是分配小块内存,是针对经常分配和释放的对象充当缓存的作用。
为每种对象类型创建一个内存缓存,每个内存缓存由多个大块组成,一个大块是一个或多个连续的物理页,每个大块包含多个对象。slab不仅仅可以解决小力度内存分配问题,还提供面向对象的分配机制。linux内核经常高频率分配和使用一些数据结构,由于分配过于频繁,如果此时分配的速率太慢,可能会影响系统的性能,所以slab面向这些数据结构设计出高速缓存,内核可以从这些高速缓存上面快速获得需要的内存,当内核使用完毕之后,可以将这些内存归还给高速缓存,以便其他使用者再次使用,很大程度提高分配速率和系统速率,一定程度上减少了系统内存的浪费,由于系统中cache的存在,高速缓存当中内存很容易命中,也是一定程度上加速系统运行的效率。
采用面向对象的思想,基于对象类型管理内存,每种对象被划分为一个类,比如进程描述符(task_struct)是一个类,每个进程描述符实现是一个对象。内存缓存组成结构如下:
在这里插入图片描述

二、编程接口

内存分配和释放的这些函数,主要是为了方便使用,块分配器在初始化的时候就会创建一些通用的内存缓存,对象的长度大多数是2^n个字节。如:从普通区域分配页的内存缓存,通过kmalloc进行使用。
1)分配内存:void * kmalloc (size_t size,gfp_t flags);
//size_t size是需要分配内存的长度是多少;gfp_t flags是我们需要分配多少,需要把它传给页分配器的分配标志位,当内存缓存没有空闲对象的时候,就向页分配器请求分配页的时候就是用分配标志位。块分配器找到一个适合通用内存缓存,对象的长度刚好大于或者等于请求内存长度,然后就从内存缓存进行分配对象。如果分配成功,就返回此对象的地址,如果分配失败,就返回空地址

2)重新分配内存:void *krealloc(const void *p, size_t new_size, gfp_t flags)
//const void *p是用指针指向需要我们重新分配内存的对象,size_t new_size需要分配内存的长度是多少,gfp_t flags是传给页分配器的标志位,这个函数是根据新的长度为对象重新分配内存,如果分配成功,就返回此对象的地址,如果分配失败,就返回空指针

3)释放内存:void kfree ( const void * objp);
//释放内存, * objp是我们用kmalloc所返回的地址对象。
kfree这个函数怎么知道这个对象属于哪一个通用内存的缓存?
我们使用通用的内存缓存有一个缺点,就是这个块分配器需要找到一个对象长度刚好大于或者等于他自己的长度才进行分配,如果请求长度和内存缓存相差很远,空间就会浪费

1)创建内存缓存:struct kmem_cache *kmem_cache_create(const char *, size_t, size_t,unsigned long, void ()(void *));
//const char * name, size_t size, size_t align,unsigned long flags, void (*strc)(void *)
name:名称
size:创建对象的场地
align:常见对象需要对齐的数值
flags:slab的标志位
strc:对象的构造函数

2)指定的内存缓存分配对象:void *kmem_cache_alloc(struct kmem_cache *, gfp_t);
//struct kmem_cache *cachep, gfp_t flags
cachep:从指定的内存缓存分配
flags:传给页分配器的分配标志位,当内存缓存没有空闲对象,向页分配器请求分配页的时候使用这个分配标志位

3)释放对象:void kmem_cache_free(struct kmem_cache *, void *);
//struct kmem_cache * cachep, void * objp
cachep:对象所属的内存缓存
objp:对象的地址

4)销毁内存缓存:void kmem_cache_destroy(struct kmem_cache *);
//struct kmem_cache * s
s:内存缓存

在这里插入图片描述
在这里插入图片描述

三、slab数据结构

每个内存缓存对应一个kmem_cache实例。
//比如有一个成员gfp_order(是slab的阶数),成员member是每一个slab所包含对象的数量(对象的原始长度是多少,需要填充对象的长度)

每个内存节点对应一个kmem_cache_node实例。

kmem_cache实例的成员cpu_slab指向array_cache实例
//每个处理器对应一个array_cach实例,称为数组缓存,用来缓存刚刚释放的对象,分配时首先从当前处理器的数据缓存分配,避免每次都要从slab分配,减少链表操作的锁操作,提高分配的速度。

slab分配器数据结构源码分析如下:

在这里插入图片描述
struct list_head list;//将slab链接到各个slab链表上面
unsigned log colouroff;//slab中第一个对象偏移
void *s_mem;//slab中的第一个对象地址
unsigned int inuse;//有多少对象正在被使用
kmem_bufctl_t free;//下一个空闲对象的下标
unsigned short nodeid;//用于寻址在高速缓存中kmem_list3的下标

四、高速缓存描述符数据结构:struct kmem_cache。

一个高速缓存中可以含有多个kmem_cache对应的高速缓存,就拿L1高速缓存来举例,一个L1高速缓存对应一个kmem_cache链表,这个链表中的任何一个kmem_cache类型的结构体均描述一个高速缓存,而这些高速缓存在L1 cache中各自占用着不同的区域。

kmem_cache常用成员:
在这里插入图片描述
在这里插入图片描述

本地高速缓存array cache成员:
在这里插入图片描述

kmem_cache_node 数据结构(高速缓存描述符的内存节点)
在这里插入图片描述

每个slab由一个或者多个连续的物理页组成,页的阶数是kmem_cache.gfporder,如果阶数大于0,组成一个复合页。
slab被划分位多个对象,大多数情况下slab长度不是对象长度的整数倍。
在这里插入图片描述
空闲对象链表:每个slab需要一个空闲对象链表,从而把所有的空闲对象连接起来,空闲对象链表是用数组来实现的,数组的元素个数是slab对象数量,数组存放空闲对象的索引。

五、每处理器数组缓存

内存缓存为每个处理器创建一个数组缓存(结构体array_cache)。释放对象时,把对象存放到当前处理器对应的数组缓存中;分配对象的时候,先从当前处理器的数组缓存分配对象,采用后进先出(LIFO)原则,可以提高性能。
刚释放的对象很可能还在处理器的缓存中,可以更好地利用处理器的缓存;
减少对链表的操作;
避免处理器之间互斥,减少自旋锁操作。

1.分配对象的时候,先从当前处理器的数组缓存分配对象,如果数组缓存是空的,那么批量分配对象以重新填充数组缓存,批量值就是数组缓存的成员batchcount。
2.释放对象的时候,如果数组缓存是满的,那么先把数组缓存中的对象地址批量归还给slab,批量值就是数组缓存的成员batchcount,然后把正在释放的对象存到数组缓存中。
在这里插入图片描述

六、回收内存

对于所有对象空间的slab,没有立即释放,而是放在空闲slab链表中。只有内存节点上
空闲对象的数量超过限制,才开始回收空闲slab,直到空闲对象的数量小于或等于限制。
节点n的空闲对象数量限制=(1+节点的处理器数量)*kmem_cache.batchcount+kmem_cache.num。
Slab分配器定期回收对象和空闲slab,实现方法是再每个处理器上向全局功罪队列添加一个延迟工作项,工作项的处理函数是cache_reap。
1.每个处理器每隔两秒钟对每个内存缓存执行回收节点n对应的远程节点数组缓存中的对象;如果过两面没有从当前处理器的数组缓存分配对象,那么回收数组缓存中的对象。
2.每个处理器每隔4秒钟对每个内存缓存执行,如果过两面没有从当共享数组缓存分配对象,那么回收共享数组缓存中的对象;如果过去4秒没有从空闲slab分配对象,那么回收空闲slab。
在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值