1. Buddy分配器概述Buddy分配器是一种经典的内存管理算法最早由Harry Markowitz在1964年提出。它通过将物理内存划分为大小相等的块并采用伙伴系统的方式管理这些内存块从而高效地处理内存分配与释放请求。这种分配器在操作系统内核中广泛应用比如Linux内核就使用Buddy系统来管理物理页面。Buddy分配器的核心思想是将可用内存划分为2的幂次方大小的块。当请求分配内存时系统会寻找最适合请求大小的块。如果找不到正好大小的块就会将一个较大的块一分为二这两个子块就称为伙伴。这种分裂过程会递归进行直到得到合适大小的块为止。关键特性Buddy分配器特别适合处理频繁的小内存分配请求因为它能有效减少外部碎片同时保持较高的分配效率。2. Buddy分配器的工作原理2.1 内存块的组织结构Buddy分配器将可用内存视为一系列大小不同的块每个块的大小都是2的幂次方如4KB、8KB、16KB等。这些块被组织成多个链表每个链表对应一个特定大小的块4KB块链表8KB块链表16KB块链表...最大块链表当系统启动时所有可用内存最初都在最大块的链表中。随着分配请求的到来这些大块会被不断分割成更小的块。2.2 分配算法详解当收到一个内存分配请求时Buddy分配器的工作流程如下计算请求大小的最小2的幂次方上界。例如请求5KB内存会向上取整到8KB。检查对应大小的空闲链表如果有可用块直接分配如果没有则尝试从更大的块链表获取如果更大的块也没有继续向上查找直到找到可用的块将找到的大块不断二分直到得到所需大小的块将分割后剩余的块放入相应大小的空闲链表例如假设系统当前只有1个16KB的块而请求分配5KB内存计算得到需要8KB块16KB块被分割为两个8KB伙伴其中一个8KB块被分配另一个8KB块保留在空闲链表2.3 释放与合并机制当内存被释放时Buddy分配器会尝试将释放的块与其伙伴块合并检查释放块的伙伴块是否也在空闲状态如果是则将两个伙伴块合并为一个大块递归检查新合并的块是否可以继续与它的伙伴合并最终将合并后的大块放入相应大小的空闲链表这种合并机制能有效减少内存碎片保持内存的连续性。3. Buddy分配器的实现细节3.1 数据结构设计典型的Buddy分配器实现需要以下核心数据结构struct free_area { struct list_head free_list; // 空闲块链表 unsigned long nr_free; // 空闲块数量 }; #define MAX_ORDER 11 // 最大块大小(2^10 * PAGE_SIZE) struct zone { // ...其他字段 struct free_area free_area[MAX_ORDER]; // 各阶空闲块 };每个free_area对应一个特定大小的块链表。MAX_ORDER定义了系统支持的最大块大小。3.2 关键操作函数分配函数实现struct page *alloc_pages(gfp_t gfp_mask, unsigned int order) { struct page *page NULL; struct zone *zone; // 遍历所有内存区域 for_each_zone(zone) { // 从请求的order开始查找 for (current_order order; current_order MAX_ORDER; current_order) { if (!list_empty(zone-free_area[current_order].free_list)) { page list_entry(zone-free_area[current_order].free_list.next, struct page, lru); list_del(page-lru); zone-free_area[current_order].nr_free--; // 分割大块 while (current_order order) { current_order--; buddy page (1 current_order); list_add(buddy-lru, zone-free_area[current_order].free_list); zone-free_area[current_order].nr_free; } return page; } } } return NULL; }释放函数实现void free_pages(struct page *page, unsigned int order) { struct zone *zone page_zone(page); unsigned long page_idx; struct page *buddy; while (order MAX_ORDER - 1) { page_idx page_to_pfn(page) ^ (1 order); buddy pfn_to_page(page_idx); // 检查伙伴是否空闲且同阶 if (!page_is_buddy(buddy, order)) break; // 合并伙伴块 list_del(buddy-lru); zone-free_area[order].nr_free--; if (page_idx page_to_pfn(page)) page buddy; order; } // 将合并后的块加入空闲链表 list_add(page-lru, zone-free_area[order].free_list); zone-free_area[order].nr_free; }3.3 伙伴块的查找算法伙伴块的地址可以通过简单的位运算得到伙伴地址 块地址 ^ (1 order)其中order表示块的大小阶数。这个特性使得伙伴查找非常高效不需要复杂的计算或查找操作。4. Buddy分配器的性能优化4.1 分配策略优化在实际系统中Buddy分配器可以采用多种分配策略来提高性能首次适应从最低阶开始查找找到第一个可用的块最佳适应查找最接近请求大小的可用块最差适应总是从最大可用块开始分配Linux内核默认采用首次适应策略因为它实现简单且效率较高。4.2 内存预取优化为了减少分配延迟可以预先分割一些大块void prep_new_page(struct page *page, unsigned int order) { // 预分割高阶内存块 if (order 0) { unsigned long size 1 (order - 1); struct page *buddy page size; list_add(buddy-lru, zone-free_area[order-1].free_list); zone-free_area[order-1].nr_free; } }4.3 缓存对齐优化对于需要缓存对齐的内存请求可以调整分配策略struct page *alloc_pages_aligned(gfp_t gfp_mask, unsigned int order, unsigned int align_order) { struct page *page alloc_pages(gfp_mask, order align_order); if (page) { unsigned long align_mask (1 align_order) - 1; unsigned long aligned_addr (page_to_pfn(page) align_mask) ~align_mask; struct page *aligned_page pfn_to_page(aligned_addr); // 释放不需要的前后部分 if (aligned_page ! page) free_pages(page, align_order); if (aligned_page (1 order) ! page (1 (order align_order))) free_pages(aligned_page (1 order), align_order); return aligned_page; } return NULL; }5. Buddy分配器的实际应用5.1 Linux内核中的实现Linux内核的Buddy分配器主要管理物理页面关键实现位于mm/page_alloc.c中。主要特点包括支持多种内存区域DMA、NORMAL、HIGHMEM与每CPU页框缓存配合使用支持内存压缩和迁移实现了水位线机制防止内存耗尽5.2 与其他内存分配器的配合Buddy分配器通常不直接面向应用层而是作为底层分配器为其他高级分配器提供支持Slab分配器用于小对象分配建立在Buddy分配的页面上kmalloc内核通用内存分配接口底层使用Slab或Buddyvmalloc分配虚拟连续但物理不连续的内存5.3 性能调优参数Linux内核提供了一些sysctl参数用于调优Buddy分配器# 查看当前内存信息 cat /proc/buddyinfo # 调整水位线参数 sysctl -w vm.min_free_kbytes65536 # 调整页面回收策略 sysctl -w vm.swappiness606. 常见问题与解决方案6.1 内存碎片问题虽然Buddy系统能减少外部碎片但仍可能产生内部碎片。解决方案包括内存压缩定期移动已分配页面以合并空闲块页面迁移将页面移动到其他NUMA节点或区域内存规整内核的kswapd和kcompactd线程6.2 分配失败排查当alloc_pages返回NULL时可以采取以下步骤检查/proc/buddyinfo确认内存碎片情况使用vmstat -s查看内存统计检查dmesg是否有OOM killer活动调整vm.min_free_kbytes增加保留内存6.3 NUMA架构下的优化在NUMA系统中Buddy分配器需要特别处理为每个NUMA节点维护独立的Buddy系统实现节点间的页面迁移优化分配策略优先从本地节点分配struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order) { // 优先从指定节点分配 if (node_online(nid) node_present_pages(nid)) return __alloc_pages_node(nid, gfp_mask, order); // 回退到其他节点 return alloc_pages(gfp_mask, order); }7. 高级话题与扩展7.1 大页支持现代Buddy分配器支持大页(Huge Page)分配struct page *alloc_huge_page(int order) { // 尝试从高阶空闲链表直接分配 for (current_order order; current_order MAX_ORDER; current_order) { if (!list_empty(zone-free_area[current_order].free_list)) { page list_entry(zone-free_area[current_order].free_list.next, struct page, lru); list_del(page-lru); zone-free_area[current_order].nr_free--; return page; } } return NULL; }7.2 内存热插拔支持运行时内存添加/移除内存上线将新内存区域加入Buddy系统内存下线将内存区域从Buddy系统移除隔离区域将内存标记为不可分配7.3 虚拟化环境优化在虚拟化环境中Buddy分配器需要支持balloon驱动调整客户机内存优化页面回收策略实现内存超配(overcommit)8. 实现注意事项在实际实现Buddy分配器时需要注意以下关键点并发控制分配和释放操作需要适当的锁保护错误处理处理内存不足等边界条件性能监控实现统计信息收集调试支持添加调试信息和检查机制一个健壮的实现应该包含以下安全检查static inline bool page_is_buddy(struct page *page, unsigned int order) { // 检查页面标志 if (PageBuddy(page) page_order(page) order) { // 检查页面是否在正确的空闲链表中 struct zone *zone page_zone(page); struct free_area *area zone-free_area[order]; struct page *tmp; list_for_each_entry(tmp, area-free_list, lru) { if (tmp page) return true; } } return false; }Buddy分配器作为操作系统内存管理的基石其设计和实现直接影响整个系统的性能和稳定性。理解其工作原理和优化技巧对于系统级开发人员和内核开发者都至关重要。在实际项目中应根据具体需求调整分配策略和参数以达到最佳的性能和资源利用率。