
深入 uCore 内存管理First-Fit 算法实现与伙伴系统/SLUB 扩展挑战全解析在操作系统内核开发中内存管理是最基础也是最关键的模块之一。uCore作为教学用操作系统其内存管理实现从简入繁为学习者提供了循序渐进的理解路径。本文将带领已完成uCore Lab 2基础练习的读者深入First-Fit算法的实现细节并挑战实现更高级的伙伴系统(Buddy System)和SLUB分配器。1. First-Fit算法深度解析First-Fit作为最简单的连续内存分配算法其核心思想是维护一个按地址排序的空闲块链表分配时从链表头部开始查找第一个足够大的空闲块。在uCore中的实现涉及几个关键数据结构struct Page { int ref; // 页帧引用计数 uint32_t flags; // 状态标志位 unsigned int property; // 连续空闲页数(仅头页有效) list_entry_t page_link; // 空闲链表指针 }; typedef struct { list_entry_t free_list; // 空闲页链表头 unsigned int nr_free; // 空闲页总数 } free_area_t;算法时间复杂度分析分配操作O(n)需要遍历链表找到合适块释放操作O(n)需要查找相邻块进行合并关键实现细节内存初始化时default_init_memmap将连续空闲页组织成块仅设置头页的property标志分配时default_alloc_pages找到合适块后若剩余空间足够大(n)会将剩余部分作为新块重新插入链表释放时default_free_pages会检查前后相邻块是否空闲进行合并操作性能优化空间使用平衡二叉树替代链表可将查找复杂度降至O(logn)实现延迟合并策略减少频繁合并带来的开销引入块大小分类对不同规模请求采用不同策略提示在实现First-Fit时特别要注意边界条件的处理如分配最后一页或释放首尾页的情况。2. 伙伴系统(Buddy System)设计与实现伙伴系统通过将内存划分为2的幂次方大小的块来解决外部碎片问题。其核心特性包括数据结构设计#define MAX_ORDER 10 // 最大管理1101024页 struct buddy { unsigned size; // 管理的内存总页数 unsigned *bitmap; // 各阶空闲块位图 struct Page* base; // 物理页数组 };关键操作分配流程将请求大小向上取整到最近的2的幂从对应阶的链表开始查找空闲块若当前阶无空闲向更高阶查找并分裂释放流程释放块后立即检查伙伴块是否空闲若伙伴空闲则合并递归检查更高阶uCore集成要点修改pmm_manager接口实现buddy_alloc/buddy_free维护11个空闲链表(0~10阶)实现伙伴查找算法static inline unsigned find_buddy(unsigned page_idx, unsigned order) { return page_idx ^ (1 order); }测试用例设计测试场景预期结果验证方法分配4页后释放能正确合并回原块检查bitmap状态交替分配不同大小无外部碎片统计总可用内存极限压力测试不出现死锁多线程并发操作3. SLUB分配器原理与实现SLUB作为Linux默认的 slab分配器实现其核心思想是通过对象缓存提高小内存分配效率。在uCore中的简化实现可包含以下组件核心数据结构struct kmem_cache { char name[16]; // 缓存名称 unsigned size; // 对象大小 unsigned objs_per_slab; // 每个slab的对象数 struct list_head partial; // 部分空闲slab列表 struct list_head full; // 全满slab列表 }; struct slab { struct list_head list; // 缓存链表指针 void *freelist; // 空闲对象链表 unsigned inuse; // 已用对象数 void *mem; // 内存起始地址 };关键操作实现初始化缓存void kmem_cache_init(struct kmem_cache *cache, const char *name, size_t size) { strncpy(cache-name, name, 16); cache-size ALIGN(size); cache-objs_per_slab (PAGE_SIZE 2) / cache-size; INIT_LIST_HEAD(cache-partial); INIT_LIST_HEAD(cache-full); }对象分配void *kmem_cache_alloc(struct kmem_cache *cache) { struct slab *slab; void *object; // 优先从partial slab分配 if (!list_empty(cache-partial)) { slab list_first_entry(cache-partial, struct slab, list); } else { slab new_slab(cache); // 申请新slab } object slab-freelist; slab-freelist *(void **)object; slab-inuse; if (slab-inuse cache-objs_per_slab) { list_move(slab-list, cache-full); } return object; }性能优化技巧实现每CPU缓存减少锁争用不同大小对象使用不同缓存对齐处理避免假共享4. 三种算法的对比与实践建议特性对比表特性First-FitBuddy SystemSLUB时间复杂度O(n)O(logn)O(1)内存利用率中等较低(内碎片)高适用场景大块连续分配中等块分配小块频繁分配实现复杂度简单中等复杂实践建议在uCore中可以先实现Buddy System作为底层分配器再基于它构建SLUB调试时重点关注边界条件处理并发安全性内存泄漏检测性能测试指标分配/释放延迟内存利用率多线程扩展性集成到uCore的步骤创建新的pmm_manager实现修改初始化流程选择分配器添加测试用例到check函数验证与其他模块(如进程管理)的兼容性在实际项目中这三种算法往往需要配合使用。例如Linux内核就同时使用了Buddy System管理大块内存和SLUB处理小块对象分配。理解它们的优劣能帮助我们在不同场景做出合适选择。