kernel heap
内核堆概述
前述
linux kernel对堆的内存管理器有两种,buddy system和slab allocater,前者以内存页为操作单位,后者则是将内存页划分为多个较小的对象进行粒度颗粒管理
linux kernel将内存分为页-区-节点三级结构,下面是一张十分经典的 Overview ,自顶向下是
- 节点(node,对应结构体 pgdata_list)
- 区(zone,对应结构体 zone,图上展示了三种类型的 zone)
- 页(page,对应结构体 page)

页
区
将节点的不同用途的内存区域划分为不同的zone
节点
一个MC对应一个节点;对于同一个内存控制器下的CPU而言其对应的节点称为本地内存
Buddy system
以内存页为粒度管理所有的物理内存,其存在于区这一级别
每个 zone 结构体中都有一个 free_area 结构体数组,用以存储 buddy system 按照 order 管理的页面:
1 | struct zone { |
order指的是连续的空闲页面的大小 2^order (参照区的那张图片)
分配:
- 首先会将请求的内存大小向 2 的幂次方张内存页大小对齐,之后从对应的下标取出连续内存页。
- 若对应下标链表为空,则会从下一个 order 中取出内存页,一分为二,装载到当前下标对应链表中,之后再返还给上层调用,若下一个 order 也为空则会继续向更高的 order 进行该请求过程。
释放:
- 将对应的连续内存页释放到对应的链表上。
- 检索是否有可以合并的内存页,若有,则进行合成,放入更高 order 的链表中。
Slub allocator
只涉及目前通用版本slub
buddy system 请求单张或多张连续内存页(slub)后再分割成同等大小的对象用以更小粒度调用
slub结构图:
page 结构体与物理内存间存在线性对应关系,我们可以直接通过 object 地址找到其对应的 page 结构体。
一个 kmem_cache 主要由两个模块组成:
- kemem_cache_cpu:一个percpu变量(每一个cpu都有一个独立的副本),用以表示当前正在使用的slub,因此当前 CPU 在从 kmem_cache_cpu 上取 object 时不需要加锁
- kmem_cache_node:存放两个slub链表;partial:有一定数量的空闲object;full:该slub上object都分配出去了
分配:
- 首先从
kmem_cache_cpu上取对象,若有则直接返回。 - 若
kmem_cache_cpu上的 slub 已经无空闲对象了,对应 slub 会被从kmem_cache_cpu上取下,并尝试从 partial 链表上取一个 slub 挂载到kmem_cache_cpu上,然后再取出空闲对象返回。 - 若
kmem_cache_node的 partial 链表也空了,那就向 buddy system 请求分配新的内存页,划分为多个 object 之后再给到kmem_cache_cpu,取空闲对象返回上层调用。
释放:
- 若被释放 object 属于
kmem_cache_cpu的 slub,直接使用头插法插入当前 CPU slub 的 freelist。 - 若被释放 object 属于
kmem_cache_node的 partial 链表上的 slub,直接使用头插法插入对应 slub 的 freelist。 - 若被释放 object 为 full slub,则其会成为对应 slub 的 freelist 头节点,且该 slub 会被放置到 partial 链表。
slub allocator
内核堆利用与绑核
成因:
slub allocator在申请分配时会优先从kmem_cache_cpu上分配,这也就导致所利用的object可能来自不同的kmem_cache_cpu,不好触发漏洞
解决:
将当前进程绑定到某个cpu核心上,从而辨别object
1 | #include <sched.h> |
通用kmalloc flag
kmalloc函数,size是分配的内核对象的大小,gfp_t flags是 Get-Free-Page flag ,相当于“怎么分配”
1 | static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t flags) |
不同bit所对应的flag:
GFP_KERNEL:0xCC0GFP_KERNEL | __GFP_ZERO: 0xDC0GFP_KERNEL_ACCOUNT:0x400CC0GFP_KERNEL_ACCOUNT | __GFP_ZERO: 0x400DC0GFP_KERNEL_ACCOUNT比GFP_KERNEL多启用了一个 bit___GFP_ACCOUNT_BIT,表示 使用 MEMCG 机制进行数据记录 ,用于追踪,在内核启用了CONFIG_MEMCG_KMEM=y时生效 ,没有则二者相同;__GFP_ZERO字面意思,表示清除内存在申请之前
当 ___GFP_KSWAPD_RECLAIM_BIT 启用时意味着在内存不足时唤醒 kswapd 进行内存回收。
kmalloc编译优化
kmalloc的size如果是固定的,就能知道从哪一个kmem_cache进行分配, 因此内核会将对 kmalloc() 的调用优化为对 kmem_cache_alloc_noprof() 的调用
1 | void *kmem_cache_alloc_noprof(struct kmem_cache *cachep, |
1 | #define INIT_KMALLOC_INFO(__size, __short_size) \ |
在内核源码中对 kmalloc_caches 的使用形式为 kmalloc_caches[type][index] , 但实际上的使用是一维指针数组 ,每个 type 占据一段连续的 index, 且大于 8k 的大小通常不启用 ,因此每个 type 一般会有 14 个 kmem_cache ,根据这个规则我们便能计算 kmalloc_caches[N] 对应的大小,以下是两个例子:
kmalloc_caches[12]: 大小范围位于KMALLOC_NORMAL对应的 index0~13,取kmalloc_info[12 - 0],得到大小 4k,对应kmem_cache为kmalloc-4k,对应 flag 为GFP_KERNEL。kmalloc_caches[54]: 大小范围位于KMALLOC_CGROUP对应的 index42~55,取kmalloc_info[54 - 42],得到大小 4k,对应kmem_cache为kmalloc-cg-4k,对应 flag 为GFP_KERNEL_ACCOUNT
参考(由源码分析):
KMALLOC_NORMAL→ 缓存名称为"kmalloc-*"→ 对应GFP_KERNELKMALLOC_CGROUP→ 缓存名称为"kmalloc-cg-*"→ 对应GFP_KERNEL_ACCOUNTKMALLOC_RECLAIM→ 缓存名称为"kmalloc-rcl-*"→ 对应GFP_KERNEL | __GFP_RECLAIMKMALLOC_DMA→ 缓存名称为"kmalloc-dma-*"→ 对应GFP_KERNEL | __GFP_DMA
slub 合并 & 隔离
slab alias 机制是一种对同等 / 相近大小 object 的 kmem_cache 进行复用的一种机制:
- 当一个
kmem_cache在创建时,若已经存在能分配相等 / 近似大小的 object 的kmem_cache,则不会创建新的 kmem_cache,而是为原有的 kmem_cache 起一个 alias,作为 “新的” kmem_cache 返回。 - 对于初始化时设置了
SLAB_ACCOUNT这一 flag 的kmem_cache而言,则会新建一个新的kmem_cache而非为原有的建立 alias