1.作用
- 用于在kernel中为object申请小块的内存(主要是小于4K的内存,大于4K的也有),底层就是调用了slab(也会用buddy system)。
- 虚拟连续,物理也连续。
2. 函数使用说明
参数:
- size:要申请的内存的大小(2^N对齐)
- flags:是GFP flag中的一个,表示应该分配什么样的内存。
kmalloc会根据申请的内存大小来决定来决定使用slub还是buddy system进行内存分配。 控制kmalloc分配行为的主要有如下三个宏:
macro | desc | size |
KMALLOC_MAX_SIZE | kmalloc可以分配的最大内存,超过此大小时返回NULL。 kmalloc最大可以分配1024个页面,就是buddy system所管理的最大内存块 | 2 ^ (MAX_ORDER + PAGE_SHIFT - 1) 一般:4K * 1024页 = 4M |
KMALLOC_MAX_CACHE_SIZE | kmalloc使用slab分配器分配的最大内存,超过此大小后会使用buddy system | 一般:2页* 4K = 8K |
KMALLOC_MIN_SIZE | kmalloc可以分配的最小内存,小于此大小时,kmalloc内部会按此大小分配 | 8 byte |
kmalloc分配内存的策略
- 大于8K,从buddy system分配。
- 小于8K,从slab分配。
3. kmalloc中VA和PA间的映射
kmalloc 分配内存是从直接映射区域(Direct Mapping Area)中拿的,Direct Mapping Area中的内存在内核启动阶段就做好了PA -> VA的线性映射。
PA=VA−Fixed Offset
内核在启动阶段使用kernel_physical_mapping_init函数建立DMA区域的线性映射。
setup_arch
init_mem_mapping
memory_map_top_down
init_range_memory_mapping
init_memory_mapping
kernel_physical_mapping_init
4. 代码实现
1. kmalloc
定义在include/linux/slab.h中
主要是实现在kmalloc_noprof中:
(1)判断size是否是编译时常量,是的话可以减少一些判断,来优化性能。
PS:
编译时常量:在编译时就能确定它的值的常量,直接硬编码到二进制。
运行时常量:在运行时才能知道它的值的,占用内存,适用于动态场景。
(2)如果是编译时常量,且size> KMALLOC_MAX_CACHE_SIZE(PAGE_SIZE*2),走__kmalloc_large_node_noprof(buddy system)。
(3)否则(size>PAGE_SIZE*2)走__kmalloc_cache_node_noprof(Slub)。
(4)如果是非编译常量:走__kmalloc_node_noprof。
调用的流程总结为:
总结:不管是走编译常量分支,还是非编译常量分支,最后调用的函数是一样的。
- 大于PAGE_SIZE*2:调用___kmalloc_large_node。
- 小于PAGE_SIZE*2:调用slab_alloc_node。
所以我们分别研究这两个函数。
2. Buddy申请:___kmalloc_large_node
为了避免不必要的开销,大内存分配请求直接通过页分配器(page allocator)处理。
使用了 __GFP_COMP ,因为在 kfree 时需要知道分配的内存页的阶数(order),以便正确释放内存页。
PS:我理解compound page(复合页)就是,物理上连续的页,把N个连续的页视为一个大页,一个整体。
(1)真正的分配内存函数。从指定的node上分配内存,这里传的node值是NUMA_NO_NODE,没有指定任何node。
alloc_pages_node_noprof接下来的调用栈如下,最后调用到的__alloc_frozen_pages_noprof就是buddy system分配器的核心实现了,具体解析见Buddy System。
(2)通过folio_address获取分配的内存页的起始地址,并更新LRU统计信息,标记分配的内存也为不可回收的slab内存(这部分不懂,LRU待学)。
(3)使用KASAN和KMSAN检查大内存分配(这部分不懂,待学)。
3. slab申请:slab_alloc_node
3.1 kmalloc slab cache定义:kmalloc_info[]
内核为kmalloc创建了不同尺寸的通用slab cache(kmem_cache),可以通过 cat /proc/slabinfo 命令来查看:
这些不同尺寸的slab cache信息都存储在称为kmalloc_info[]的struct kmalloc_info_struct结构体中。
struct kmalloc_info_struct定义在mm/slab.h中:
- name:该slab cache的名称,kmalloc-xxx
- size:该slab cache的内存块的大小。
kmalloc_info[]的初始化定义:
所以kmalloc_info[]的内容如下图:
kmalloc_info[] 数组中的 index 有一个特点,从 index = 3 开始一直到数组的最后一个 index,index是该slab cache中object的size的order:
object size = 2^index
内核中,对于内存块的申请需求大部分情况下都在 96 字节或者 192 字节附近,所以把这两个size特殊的放在index 1和2的位置。
3.2 kmalloc_caches
内核启动初始化的时候会根据kmalloc_info中的内容,通过create_kmalloc_caches函数,挨个建立kmem_cache,并把所有的kmem_cache结构体使用kmalloc_caches变量统一管理。
kmalloc_caches定义在mm/slab_common.c中:
- kmem_buckets表示一个长度为KMALLOC_SHIFT_HIGH + 1的kmem_cache指针数组。
- 所以其实等于struct kmem_cache *kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1]
type:
kmalloc_cache_type定义在include/linux/slab.h中:
- KMALLOC_NORMAL: kmalloc 需要从 ZONE_NORMAL 物理内存区域中分配内存。
- KMALLOC_DMA: kmalloc 需要从 ZONE_DMA 物理内存区域中分配内存。
- KMALLOC_RECLAIM:需要分配可以被回收的内存
- etc.
index:
kmalloc从slub分配内存的内存大小范围由 KMALLOC_SHIFT_LOW 和 KMALLOC_SHIFT_HIGH 决定,它们被定义在 /include/linux/slab.h中:
kmalloc支持的最小内存块为:2^KMALLOC_SHIFT_LOW = 2^3 = 8 byte。
kmalloc支持的最大内存块为:2^KMALLOC_SHIFT_HIGH = 2^(12+1) = 8K = PAGE_SIZE * 2
create_kmalloc_caches:
轮询kmalloc的type和index,挨个调用new_kmalloc_cache。
new_kmalloc_cache:
根据输入的idx和type,从预先写好的kmalloc_info中拿name和size,调用create_kmalloc_cache建立kmem_cache并把指针放入kmalloc_caches。
总结:
kmalloc能从slub分配的内存块大小在8 byte ~ 8K 之间:
3.3 计算分配的大小:kmalloc_slab
创建了 不同大小的kmalloc slab cache,如何计算取最佳尺寸的 slab cache呢?
用kmalloc_slab函数。
总结:
kmalloc_slab定义在mm/slab.h中:
(1)根据输入参数,确定kmalloc的type,大多数情况下是KMALLOC_NORMAL。调用kmalloc时,通过gfp flag指定kmalloc的type。
(2)如果size<=192,就通过kmalloc_size_index获得index。
(3)如果size>192,就通过fls计算index
kmalloc_size_index:
就是一个数组,快速的检索。定义在mm/slab_common.c:
fls:
"Find Last Set" 函数,用于返回一个整数中最高有效位的位置(从1开始计数)
eg:fls(8) = fls(1000) = 4
3.4 slab分配函数:slab_alloc_node
知道了从哪个kmalloc kmem cache中分配内存后,就是分配内存操作了。
通过调用slab_alloc_node。这个是slab实现的分配函数,详情见Slab笔记。
kfree
内核提供了 kfree 函数来释放由 kmalloc 内存池分配的内存块,参数 x 表示释放内存块的虚拟内存地址。
定义在mm/slub.c中:
(1)把虚拟地址转换为物理地址,并且返回一个folio结构体(方便管理复合页)
(2)如果内存当时不是从slab分配的,调用free_large_kmalloc释放回buddy system。
(3)如果是从slab分配的,调用slab_free释放回slab中。