linux内存管理之slab机制

一、内核内存分配

在linux内核中伙伴系统用来管理物理内存,其分配的单位是页,但是向用户程序一样,内核也需要动态分配内存,而伙伴系统分配的粒度又太大。由于内核无法借助标准的C库,因而需要别的手段来实现内核中动态内存的分配管理,linux采用的是slab分配器。slab分配器不仅可以提供动态内存的管理功能,而且可以作为经常分配并释放的内存的缓存。通过slab缓存,内核能够储备一些对象,供后续使用。需要注意的是slab分配器只管理内核的常规地址空间(准确的说是直接被映射到内核地址空间的那部分内存包括 ZONE_NORMAL和ZONE_DMA )。
采用了slab分配器后,在释放内存时,slab分配器将释放的内存块保存在一个列表中,而不是返回给伙伴系统。在下一次内核申请同样类型的对象时,会使用该列表中的内存开。slab分配器分配的优点:
  • 可以提供小块内存的分配支持
  • 不必每次申请释放都和伙伴系统打交道,提供了分配释放效率
  • 如果在slab缓存的话,其在CPU高速缓存的概率也会较高。
  • 伙伴系统的操作队系统的数据和指令高速缓存有影响,slab分配器降低了这种副作用
  • 伙伴系统分配的页地址都页的倍数,这对CPU的高速缓存的利用有负面影响,页首地址对齐在页面大小上使得如果每次都将数据存放到从伙伴系统分配的页开始的位置会使得高速缓存的有的行被过度使用,而有的行几乎从不被使用。slab分配器通过着色使得slab对象能够均匀的使用高速缓存,提高高速缓存的利用率
在引入了slab分配器后,内核的内存管理方案如图所示:


slab分配器也不是万能的,它也有缺陷:

  • 对于微型嵌入式系统,它显得比较复杂,这是可以使用经过优化的slob分配器,它使用内存块链表,并使用最先适配算法
  • 对于具有大量内存的大型系统,仅仅建立slab分配器的数据结构就需要大量内存,这时候可以使用经过优化的slub分配器
无论是slab分配器家族的这三个中的那个一,它们提供的接口都是相同的:
kmalloc,__kmalloc和kmalloc_node用于普通内存的分配
kmem_cache_alloc,kmem_cache_alloc_node用于申请特定类型的内存
内核中普通内存的申请使用kmalloc(size,flags),size是申请的大小,flags告诉分配器分配什么样的内存,如何分配等等。
内核中普通内存的释放使用kfree(*ptr);释放ptr所指向的内存区。
可以通过/proc/slabinfo查看活动的缓存列表。

二、slab分配器的原理

slab算法是1994年开发出来的并首先用于sun microsystem solaris 2.4操作系统。这种算法的使用基于以下几个前提:

  1. 所存放数据的类型可以影响存储器取区的分配方式。
  2. 内核函数倾向于反复请求同一类型的存储器区。
  3. 对存储器区的请求可以根据它们发生的频率来分类。
  4. 所引入的对象大小不是几何分布的。
  5. 硬件高速缓存的高性能。在这种情况下,伙伴函数的每次调用都增加了对内存的平均访问时间。
slab分配器把对象分组放进高速缓存。每个高速缓存都是同种类型对象的一种“储备”。一个cache管理一组大小固定的内存块(也称为对象实体),每个内存块都可用作一种数据结构。cache中的内存块来自一到多个slab。一个slab来自物理内存管理器的一到多个物理页,该slab被分成一组固定大小的块,被称为slab对象(object),一个slab属于一个cache,其中的对象就是该cache所管理的固定大小的内存块。所以一个cache可以有一到多个slab。下图给出了slab分配器的各个部分及其相互关系:

在基于slab的内核内存管理器中,基本的概念是保存管理型数据的缓存(即slab cache,slab缓存)和保存被管理对象的各个slab。每个缓存都负责一种对象类型,比如kmalloc-128会负责管理65-128字节的内存的kmalloc分配。系统中的所有缓存类型都保存在一个链表slab_caches中。

1.slab缓存

slab缓存的详细结构 如图所示:


每个缓存结构都包括了两个重要的成员:

  • struct kmem_list3 **nodelists:kmem_list3结构中包含了三个链表头,分别对应于完全用尽的slab链表,部分用尽的slab链,空闲的slab链表,其中部分空闲的在最开始
  • struct array_cache *array[NR_CPUS + MAX_NUMNODES]:array是一个数组,系统中的每一个CPU,每一个内存节点都对应该数组中的一个元素。array_cache结构包含了一些特定于该CPU/节点的管理数据以及一个数组,每个数组元素都指向一个该CPU/节点刚释放的内存对象。该数组有助于提高高速缓存的利用率。
    • 当释放内存对象时,首先将内存对象释放到该数组中对应的元素中
    • 申请内存时,内核假定刚释放的内存对象仍然处于CPU高速缓存中,因而会先从该数组的对应数组元素中查找,看是否可以申请。
    • 当特定于CPU/节点的缓存数组是空时,会用slab缓存中的空闲对象填充它
因此,对象分配的次序为:
  1. 特定于CPU/节点的缓存列表中的对象
  2. 当前已经存在于slab缓存中中的未用对象
  3. 从伙伴系统获得内存,然后创建的对象

2.slab对象

对象在slab中不是连续排列的,其排列如图所示:


slab对象的长度并不代表其确切的长度,因为需要对长度进行调整以满足对齐要求。对齐要求可能是:
  • 创建slab时指定了SLAB_HWCACHE_ALIGN标志,则会按照cache_line_size的返回值对齐,即对齐的硬件缓存行上。如果对象小于硬件缓存行的一半,则将多个对象放入一个缓存行。
  • 如果没有指定对齐标记,则对齐到BYTES_PER_WORD,即对齐到void指针所需字节数目。
为了使得slab满足对齐要求,会在slab对象中添加填充字节以满足对齐要求,使用对齐的地址可以会加速内存访问。每个slab都对应一个管理结构,它可能位于slab内部也可能位于slab外部专门为它申请的内存中,它保存了所有的管理数据,也包括一个链表域用于将slab连接起来,还包括一个指针指向它所属的cache。
大多数情况下,slab内存区的长度是不能被对象长度整除的,因而就有了一些多余的内存,这些内存可以被用来以偏移量的形式给slab“着色”,着色后,缓存的各个slab成员会指定到不同的偏移量,进而可以将数据定位到不同的缓存行。
内核通过对象自身即可找到它对应的slab,过程是:对象的物理地址->物理地址对应的page结构。然后由page找到对应的slab以及cache(包含在page结构中)。

三、slab分配器的实现

1.使用的数据结构

linux使用struct kmem_cache表示slab缓存,使用struct kmem_list3管理缓存所对应的slab链表的链表头,使用struct array_cache管理特定于CPU的slab对象的缓存(注意不是slab缓存是slab对象的缓存)。

2.内核采用的其它保护机制

为了检测错误,内核采用了一些机制来对内存进行保护,主要的方法有:
危险区:在每个对象的开始和结束处增加一个额外的内存区,其中会填充一些特殊的字段。如果这个区域被修改了,可能就是某些代码访问了不该访问的内存区域
对象毒化:在建立和释放slab时,将对象用预定义的模式填充。如果在对象分配时发现该模式已经改变,就可能是发生了内存越界。

3.初始化

slab分配器的初始化涉及到一个鸡与蛋的问题。为了初始化slab数据结构,内核需要很多远小于一页的内存区,很显然由kmalloc分配这种内存最合适,但是kmalloc只有在slab分配器初始化完才能使用。内核借助一些技巧来解决该问题。
kmem_cache_init函数被内核用来初始化slab分配器。它在伙伴系统启用后调用。在SMP系统中,启动CPU正在运行,其它CPU还未初始化,它要在smp_init之前调用。slab采用多步逐步初始化slab分配器,其工作过程:
创建第一个名为kmem_cache的slab缓存,此时该缓存的管理数据结构使用的是静态分配的内存。在slab分配器初始化完成后,会将这里使用的静态数据结构替换为动态分配的内存。
初始化其它的slab缓存,由于已经初始化了第一个slab缓存,因此这一步是可行。
将初始化过程由于“鸡与蛋”的问题而使用的静态数据结构替换为动态分配的。

4.API

1.创建缓存

slab分配器使用kmem_cache_create创建一个新的slab缓存。该函数的基本工作过程为:

  1. 参数检查
  2. 计算对齐
  3. 分配缓存的管理结构所需的内存
  4. 计算slab所需的物理内存大小以及每个slab中slab对象的个数
  5. 计算slab管理部分应该放在哪里,并存储在缓存的flags域中
  6. 计算slab的颜色,颜色数目存在color中,颜色偏移量存在color_off中
  7. 建立每CPU的缓存
  8. 将新创建的缓存添加到全局slab缓存链表slab_caches中

2.分配对象

kmem_cache_alloc用于从指定的slab缓存分配对象。与kmalloc相比,它多了一个缓存指针的参数,用于指向所要从其中分配内存的缓存。
其工作过程如图:

在NUMA系统中,如果在本节点分配失败,还会尝试其它节点。

cache_grow用于缓存的增长,它会从伙伴系统获取内存。其流程如图所示:


3.释放对象

kmem_cache_free用于将对象归还给指定的slab缓存,类似于kmem_cache_free,它比kfree多了一个指向所归还到的slab缓存指针参数。其流程如图:

free_block会将缓存中前batchcount个对象移动到slab链表中,并且将缓存中剩余的对象向数组的头部移动。根据slab对象所属的slab的状态(inuse域),slab对象可能被归给给部分空闲链表(如果该slab中有些slab对象正在被使用)或者空闲链表(该slab中没有其它对象正在被使用),同时如果加入到空闲slab链表中的slab对象数目超过了free_limit的限制(在kmem_list3结构中),则会调用slab_destroy销毁slab。

4.缓存收缩

可以使用kmem_cache_shrink来回收一个slab缓存所管理的内存。它会释放尽可能多的slab。它会尝试回收用于每CPU缓存的内存空间(调用free_block),以及用于空闲链表的slab内存空间,slab的释放最终都由slab_destroy完成。

5.通用缓存

如果不涉及到特定类型的内存,而只是普通类型的内存,可以使用kmalloc和kfree来申请和释放缓存。内核会找到并使用适用于所申请的大小的通用slab缓存来进行分配和释放。
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 第一章 Linux底层分段分页机制 5 1.1 基于x86的Linux分段机制 5 1.2 基于x86的Linux分页机制 7 1.2.1 页全局目录和页表 8 1.2.2 线性地址到物理地址 10 1.2.3 线性地址字段处理 13 1.2.4 页表处理 15 1.3 扩展分页与联想存储器 20 1.4 Linux内存布局 21 1.5 内核空间和用户空间 23 1.5.1 初始化临时内核页表 24 1.5.2 永久内核页表的初始化 32 1.5.3 第一次进入用户空间 41 1.5.4 内核映射机制实例 44 1.6 固定映射的线性地址 48 1.7 高端内存内核映射 50 1.8.1 永久内存映射 50 1.8.2 临时内核映射 55 第二章 内核级内存管理系统 58 2.1 Linux页面管理 58 2.1.1 NUMA架构 61 2.1.2 内存管理区 62 2.2 伙伴系统算法 65 2.2.1 数据结构 66 2.2.2 块分配 67 2.2.3 块释放 69 2.3 Linux页面级内存管理 72 2.3.1 分配一组页面 73 2.3.2 释放一组页面 80 2.4 每CPU页面高速缓存 81 2.4.1 数据结构 81 2.4.2 通过每CPU 页高速缓存分配页面 82 2.4.3 释放页面到每CPU 页面高速缓存 83 2.5 slab分配器 85 2.5.1 数据结构 86 2.5.2 分配/释放slab页面 92 2.5.3 增加slab数据结构 93 2.5.4 高速缓存内存布局 94 2.5.5 slab着色 95 2.5.6 分配slab对象 96 2.5.7 释放Slab对象 100 2.5.8 通用对象 102 2.5.9 内存池 103 2.6 非连续内存区 104 2.6.1 高端内存区回顾 105 2.6.2 非连续内存区的描述符 106 2.6.3 分配非连续内存区 109 2.6.4 释放非连续内存区 113 第三章 进程的地址空间 117 3.1 用户态内存分配 117 3.1.1 mm_struct数据结构 118 3.1.2 内核线程的内存描述符 122 3.2 线性区的数据结构 123 3.2.1 线性区数据结构 123 3.2.2 红-黑树算法 126 3.2.3 线性区访问权限 128 3.3 线性区的底层处理 130 3.3.1 查找给定地址的最邻近区 131 3.3.2 查找一个与给定的地址区间相重叠的线性区 135 3.3.3 查找一个空闲的地址区间 135 3.3.4 向内存描述符链表中插入一个线性区 137 3.4 分配线性地址区间 141 3.5 释放线性地址区间 151 3.5.1 do_munmap()函数 151 3.5.2 split_vma()函数 153 3.5.3 unmap_region()函数 155 3.6 创建和删除进程的地址空间 156 3.6.1 创建进程的地址空间 156 3.6.2 删除进程的地址空间 175 3.6.3 内核线程1号的地址空间 176 3.7 堆的管理 178 第四章 磁盘文件内存映射 182 4.1 内存映射的数据结构 182 4.2 内存映射的创建 184 4.3 内存映射的请求调页 194 4.4 刷新内存映射的脏页 203 4.5 非线性内存映射 210 第五章 页面的回收 215 5.1 页框回收概念 215 5.1.1 选择目标页 216 5.1.2 PFRA设计 217 5.2 反向映射技术 218 5.2.1 匿名页的反向映射 220 5.2.2 优先搜索树 226 5.2.3 映射页的反向映射 231 5.3 PFRA实现 235 5.3.1 最近最少使用(LRU)链表 236 5.3.2 内存紧缺回收 242 5.3.3 回收磁盘高速缓存的页 267 5.3.4 周期回收 273 5.3.5 内存不足删除程序 283 第六章 交换机制 289 6.1 交换区数据结构 289 6.1.1 创建交换区 290 6.1.2 交换区描述符 291 6.1.3 换出页标识符 293 6.2 激活和禁用交换区 295 6.2.1 sys_swapon()系统调用 296 6.2.2 sys_swapoff()系统调用 304 6.2.3 try_to_unuse()函数 308 6.3 分配和释放页槽 313 6.3.1 scan_swap_map()函数 313 6.3.2 get_swap_page()函数 316 6.3.3 swap_free()函数 318 6.4 页面的换入换出 320 6.4.1 交换高速缓存 320 6.4.2 换出页 323 6.4.3 换入页 329 第七章 缺页异常处理程序 335 7.1 总体流程 335 7.2 vma以外的错误地址 341 7.3 vma内的错误地址 346 7.3.1 handle_mm_fault()函数 348 7.3.2 请求调页 352 7.3.3 写时复制 358 7.4 处理非连续内存区访问 364
通过把nginx slab的精简,把需要的头文件单独整理出来,增加了main方法,可以单独运行,代码包含了大量的中文注释,方便了了解和学习slab的运行机制 int main(int argc, char **argv) { ngx_log_t log; ngx_shm_t shm; ngx_slab_pool_t *shpool; char *ssl_session[10]; int n; ngx_pid = ngx_getpid(); memset(&log;, 0, sizeof(ngx_log_t)); memset(&shm;, 0, sizeof(ngx_shm_t)); shm.size = 512000; ngx_pagesize_shift = 0; ngx_pagesize = getpagesize(); for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ } printf("--%d\n", 1 << ngx_pagesize_shift); if (ngx_shm_alloc(&shm;) != NGX_OK) { return 1; } if (ngx_init_zone_pool(&log;, &shm;)) { return 1; } shpool = (ngx_slab_pool_t *) shm.addr; ssl_session[0] = (char *) ngx_slab_alloc_locked(shpool, 12); ssl_session[0] = (char *) ngx_slab_calloc(shpool, 56); ssl_session[1] = (char *) ngx_slab_alloc_locked(shpool, 14); ssl_session[2] = (char *) ngx_slab_alloc_locked(shpool, 11); ngx_slab_free(shpool, ssl_session[2]); ngx_slab_free(shpool, ssl_session[0]); ssl_session[2] = (char *) ngx_slab_alloc_locked(shpool, 65); return 0; } nginx的slab内存管理方式,这种方式的内存管理在nginx中,主要是与nginx 的共享内存协同使用的。nginx的slab管理与linux的slab管理相同的地方在于均是利用了内存 的缓存与对齐机制,slab内存管理中一些设计相当巧妙的地方,也有一些地方个人感觉设计 不是很完美,或许是作为nginx设计综合考虑的结果。 nginx slab实现中的一大特色就是大量的位操作,这部分操作很多是与slot分级数组相关的。 为方便描述下面做一些说明: 1.将整个slab的管理结构称slab pool. 2.将slab pool前部的ngx_slab_pool_t结构称slab header. 3.将管理内存分级的ngx_slab_page_t结构称slot分级数组. 4.将管理page页使用的ngx_slab_page_t结构称slab page管理结构. 5.将具体page页的存放位置称pages数组. 6.将把page分成的各个小块称为chunk. 7.将标记chunk使用的位图结构称为bitmap.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值