1. Slab 分配器概述Slab 分配器是 Linux 内核的对象级内存分配器专门用于分配小对象如 task_struct、inode 等。它基于 Buddy 系统构建但针对小对象进行了优化。1.1 为什么需要 SlabBuddy 系统分配最小单位是 4KB 页面对于小对象如 100 字节的结构体会造成严重浪费使用 Buddy 分配 100 字节: - 实际分配: 1 页 4096 字节 - 浪费: 3996 字节 (97.5%)Slab 分配器通过以下方式解决在一个页面中存放多个小对象缓存常用对象避免重复初始化减少内存碎片1.2 Slab 变体Linux 提供了三种 Slab 分配器分配器描述特点Slab原始实现功能完整性能一般Slub优化实现默认使用性能最好SLOB简化实现适合嵌入式系统2. Slab 原理2.1 缓存结构// mm/slab.hstructkmem_cache{// 缓存名称charname[KMALLOC_NAME_LEN];// 对象大小size_tobject_size;// 对齐大小size_talign;// 每个 slab 的对象数unsignedintnum;// 页面大小size_tsize;// 构造函数和析构函数void(*ctor)(void*);void(*dtor)(void*);// 着色 (减少缓存冲突)unsignedintcolour;unsignedintcolour_off;// 链表structkmem_cache_node*node[MAX_NUMNODES];// 标志unsignedlongflags;unsignedlongctor_flags;// 调试intobj_offset;intfree_limit;};2.2 Slab 结构// mm/slab.cstructslab{// 链表structlist_headlist;// 页面unsignedlongcolouroff;void*s_mem;// 对象管理unsignedintinuse;// 已使用对象数unsignedintfree;// 第一个空闲对象// 调试unsignedlongdebug;};2.3 Slab 布局一个 Slab 页面布局: ┌─────────────────────────────────────────────────────────┐ │ Slab 管理结构 │ ├─────────────────────────────────────────────────────────┤ │ 对象1 │ 对象2 │ 对象3 │ ... │ 对象N │ 空闲对象链表 │ ├─────────────────────────────────────────────────────────┤ │ 着色区 (可选) │ └─────────────────────────────────────────────────────────┘3. Slab 分配/释放3.1 创建缓存// mm/slab.c// 创建 kmem_cachestructkmem_cache*kmem_cache_create(constchar*name,size_tsize,size_talign,unsignedlongflags,void(*ctor)(void*)){structkmem_cache*s;// 分配缓存结构skmalloc(sizeof(structkmem_cache),GFP_KERNEL);if(!s)returnNULL;// 初始化strncpy(s-name,name,KMALLOC_NAME_LEN);s-object_sizesize;s-sizesize;s-alignalign;s-flagsflags;s-ctorctor;// 初始化节点for(i0;iMAX_NUMNODES;i){s-node[i]kmalloc_node(sizeof(structkmem_cache_node),GFP_KERNEL,i);if(!s-node[i])gotofail;init_mutex(s-node[i]-list_lock);INIT_LIST_HEAD(s-node[i]-slabs_full);INIT_LIST_HEAD(s-node[i]-slabs_partial);INIT_LIST_HEAD(s-node[i]-slabs_free);}returns;fail:kmem_cache_destroy(s);returnNULL;}3.2 分配对象// mm/slab.c// 从缓存分配对象void*kmem_cache_alloc(structkmem_cache*s,gfp_tgfpflags){void*obj;structslab*slab;structkmem_cache_node*n;// 获取本地节点ns-node[numa_mem_id()];// 快速路径: 从部分空闲 slab 获取spin_lock(n-list_lock);list_for_each_entry(slab,n-slabs_partial,list){if(slab-free!UINT_MAX){// 从 slab 获取对象objslab_get_obj(slab,s);// 更新统计n-nr_partial--;if(slab-freeUINT_MAX)list_move(slab-list,n-slabs_full);elselist_move(slab-list,n-slabs_partial);spin_unlock(n-list_lock);gotodone;}}spin_unlock(n-list_lock);// 慢速路径: 需要分配新的 slabobjcache_grow(s,gfpflags,numa_mem_id());done:// 调用构造函数if(s-ctor)s-ctor(obj);returnobj;}// 从 slab 获取对象staticvoid*slab_get_obj(structslab*slab,structkmem_cache*s){void*obj;intobjnr;// 获取第一个空闲对象objnrslab-free;objslab-s_mem(objnr*s-size);// 更新空闲指针slab-free(int)(long)(objs-size);// 更新使用计数slab-inuse;returnobj;}3.3 释放对象// mm/slab.c// 释放对象voidkmem_cache_free(structkmem_cache*s,void*objp){structslab*slab;structkmem_cache_node*n;// 获取 slabslabvirt_to_slab(objp);// 获取节点ns-node[page_to_nid(virt_to_page(objp))];// 调用析构函数if(s-dtor)s-dtor(objp);// 释放对象回 slabspin_lock(n-list_lock);slab_free_obj(slab,s,objp);// 更新链表if(slab-inuse0){// 完全空闲移动到空闲链表list_move(slab-list,n-slabs_free);n-nr_free;}elseif(slab-inuses-num){// 之前是满的现在变为部分list_move(slab-list,n-slabs_partial);n-nr_partial--;}spin_unlock(n-list_lock);}// 释放对象回 slabstaticvoidslab_free_obj(structslab*slab,structkmem_cache*s,void*objp){// 将对象添加到空闲链表// 实际实现更复杂使用位图管理slab-inuse--;}4. Slub 分配器4.1 Slub 特点Slub 是默认的内存分配器相比 Slab 有以下改进简化设计- 减少数据结构开销更好的缓存局部性- 按 CPU 本地分配更少的锁竞争- Per-CPU 缓存4.2 Slub 结构// mm/slub.cstructkmem_cache{// 对象大小unsignedintobject_size;unsignedintsize;unsignedintalign;// 构造函数void(*ctor)(void*);// 节点structkmem_cache_node*node[MAX_NUMNODES];// CPU 缓存structkmem_cache_cpu*cpu_slab;// 标志unsignedlongflags;unsignedlongmin_partial;intsize_suffix;// 调试constchar*name;intrefcount;void(*dtor)(void*);};// Per-CPU 结构structkmem_cache_cpu{// 当前 slabstructpage*page;// 第一个空闲对象void**freelist;// 锁unsignedlongtid;// 统计intnode;unsignedintoffset;unsignedintinuse;};4.3 Slub 分配流程// mm/slub.c// 分配对象void*kmem_cache_alloc(structkmem_cache*s,gfp_tgfpflags){void*object;structkmem_cache_cpu*c;// 禁用中断获取当前 CPU 的 slabcraw_cpu_ptr(s-cpu_slab);// 快速路径: 本地 slab 有空闲对象objectc-freelist;if(object){c-freelist*(void**)object;c-inuse;// 统计stat(s,ALLOC_FASTPATH);returnobject;}// 慢速路径: 需要从节点获取objectslab_alloc(s,gfpflags,_RET_IP_);returnobject;}// 慢速分配staticvoid*slab_alloc(structkmem_cache*s,gfp_tgfpflags,unsignedlongaddr){void**object;structkmem_cache_cpu*c;structkmem_cache_node*n;// 重试分配cget_cpu_ptr(s-cpu_slab);retry:// 检查当前 slabif(!c-page||c-freelistNULL){// 刷新 CPU slabc-freelistget_freelist(s,c-page);// 如果仍然为空需要从节点获取新 slabif(!c-freelist){c-pageget_new_slab(s,gfpflags);c-freelistget_freelist(s,c-page);}}objectc-freelist;c-freelist*(void**)object;c-inuse;returnobject;}5. kmalloc5.1 kmalloc 概述kmalloc是内核中最常用的内存分配函数基于 Slab 分配器实现#includelinux/slab.h// 分配内存void*kmalloc(size_tsize,gfp_tflags);// 释放内存voidkfree(constvoid*objp);5.2 kmalloc 缓存Linux 为不同大小的请求预创建了多个缓存// mm/slab.c// kmalloc 缓存数组structkmem_cache*kmalloc_caches[KMALLOC_SHIFT_HIGH1];// 大小级别// 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384// 初始化void__initkmem_cache_init(void){// 创建 kmalloc 缓存for(iKMALLOC_SHIFT_LOW;iKMALLOC_SHIFT_HIGH;i){kmalloc_caches[i]create_kmalloc_cache(kmalloc_cache_names[i],1i,SLAB_CACHE_FLAGS);}}5.3 使用示例// 分配内存structtask_struct*task;taskkmalloc(sizeof(structtask_struct),GFP_KERNEL);// 分配并清零structtask_struct*task;taskkzalloc(sizeof(structtask_struct),GFP_KERNEL);// 释放kfree(task);6. 内存分配总结内存分配层次: ┌─────────────────────────────────────────────────────────┐ │ 用户空间 malloc() │ └────────────────────────┬────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ kmalloc() / kmem_cache_alloc() │ │ (Slab/Slub 分配器) │ └────────────────────────┬────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ alloc_pages() / __get_free_pages() │ │ (Buddy 系统) │ └────────────────────────┬────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 物理内存 │ └─────────────────────────────────────────────────────────┘7. 总结Slab 分配器的核心特点对象缓存- 缓存常用对象避免重复初始化着色- 减少缓存冲突Per-CPU- 减少锁竞争 (Slub)碎片少- 在一个页面中存放多个对象理解 Slab/Slub 分配器对于编写高效的内核代码至关重要。内存管理章节完成下一章文件系统