本文主要分析linux内核内存分配方法kmalloc的实现逻辑,并且在CONFIG_SLUB条件下。
先看下kmalloc函数定义:
如果申请的大小超过KMALLOC_MAX_SIZE最大值,则返回NULL表示失败;如果申请大小小于192,且不为0,将通过size_index_elem宏转换为下标后,经size_index全局数组取得索引值,否则将直接通过fls()取得索引值;最后如果开启了DMA内存配置且设置了GFP_DMA标志,将结合索引值通过kmalloc_dma_caches返回kmem_cache管理结构信息,否则将通过kmalloc_caches返回该结构。
slab_alloc 函数通过slab_alloc_node 函数实现,后者定义在 mm/slub.c 中 slab_alloc_node()函数逻辑是两个分支语句:一种情况是当前CPU的freelist为空,或者分配slab的节点和当前CPU的内存节点不匹配,执行较慢的分配路径 __slab_alloc ;否则直接从freelist中获取新分配的对象,通过预取指令将对象放到cache中
slab_alloc()函数再调用_slab_alloc()函数执行具体的内存分配。
执行慢路径___slab_alloc()函数时,如果当前CPU的slab cache的partiallist也不可用,就通过 new_slab_objects()函数,先从属于当前节点的slab中通过 get_partial() 尝试从当前的节点获取一个partial slab,如果失败再通过 get_any_partial 从其他的节点获取一个partial slab,如果都失败则通过伙伴系统分配新的内存页作为slab使用。
new_slab_objects()函数在尝试创建新的slab前,将先通过get_partial()获取存在空闲对象的slab并将对象返回;否则继而通过new_slab()创建slab,如果创建好slab后,将空闲对象链表摘下并返回。
get_partial()函数的主要功能通过 get_partial_node 从指定的节点获取partial slab,然后保存到当前CPU的slab cache的partiallist中。
get_any_partial()函数按照距离当前节点由近到远的顺序在其他的节点上寻找可用的partial slab,主要功能同样通过 get_partial_node()实现。
以上是申请内存小于8KB的情况。
接下来看下申请内存超过8KB的情况:
如果内核编译时启用NUMA,则调用alloc_pages_current()函数。
alloc_pages_current()函数最终调用__alloc_pages_nodemask()函数,__alloc_pages_nodemask()函数在之前的文章内核收包已经分析过来,这里不再赘述。
总结:
kmalloc 函数主要是两个分支:一个是分配小于等于8kb的内存 __kmalloc ,一个是分配大于8kb的内存 kmalloc_large
分配大于8KB内存时:通过kmalloc_large()进行内存分配,将会经kmalloc_large()->kmalloc_order()->__get_free_pages(),最终通过Buddy伙伴算法申请所需内存。
分配小于8KB内存时__kmalloc()函数和slub有关系。从当前CPU的freelist中获取可用对象,获取成功直接返回对象,更新freelist,通过预取指令放到cache,即快路径。
如果freelist没有对象可用,执行慢路径,从CPU的partial slab中取出第一个元素,作为当前正在使用的slab,并尝试从中分配对象。
如果CPU的partial list也为空,就从当前节点中获取一个partial slab,分配给当前CPU使用。
如果当前节点中也没有partial slab可用,就按照距离当前节点的从近到远的顺序,分配partial slab使用。
所以__slab_alloc 函数的注释中所说的“lockless freelist”和“regular freelist”,指的应该是 struct kmem_cache_cpu 的 freelist 成员;“the rest of the freelist”的freelist指的应该是第一个partial slab分配完请求的对象后剩余的freelist。