当前位置: 首页 > news >正文

Linux 内核内存管理:从伙伴系统到 Slab 分配器的分层设计

Linux 内核内存管理:从伙伴系统到 Slab 分配器的分层设计

一、内存管理的工程挑战:碎片化与分配效率的矛盾

Linux 内核需要管理从几 KB 到几 TB 的内存分配请求,这些请求的粒度差异巨大:进程的页表需要整页(4KB)分配,而文件系统的 inode 只需几十字节。如果所有分配都按页对齐,小对象的内存浪费高达 99%。如果所有分配都按字节对齐,外部碎片化会导致大块连续内存无法分配。

Linux 的解决方案是分层设计:伙伴系统(Buddy System)管理物理页帧,解决外部碎片化;Slab 分配器在伙伴系统之上管理小对象,解决内部碎片化。两层协作,各司其职。

二、内存管理的分层架构

flowchart TB APP[应用层 kmalloc/vmalloc] --> SLAB[Slab 分配器 小对象] APP --> BUDDY[伙伴系统 页帧分配] SLAB --> BUDDY BUDDY --> ZONE[内存区域 ZONE_DMA/NORMAL/HIGHMEM] ZONE --> PHYS[物理内存] subgraph Slab 层 SLAB CACHE[Slab Cache 对象池] end subgraph 伙伴系统层 BUDDY FREE_AREA[空闲区域链表 2^n 页] end

三、伙伴系统与 Slab 的核心实现

/* ========== 伙伴系统核心逻辑 ========== */ /* 空闲区域:每个 order 维护一个链表 */ struct free_area { struct list_head free_list; /* 空闲页块链表 */ unsigned long nr_free; /* 空闲页块数量 */ }; /* 伙伴系统分配:从 order 链表中取出 2^order 个连续页 */ static struct page *alloc_pages(unsigned int order) { struct free_area *area; unsigned int current_order; /* 从请求的 order 开始,向上查找空闲块 */ for (current_order = order; current_order < MAX_ORDER; current_order++) { area = &zone->free_area[current_order]; if (!list_empty(&area->free_list)) goto found; } return NULL; /* 没有足够大的连续页块 */ found: /* 从链表中取出一个空闲块 */ struct page *page = list_entry(area->free_list.next, struct page, lru); list_del(&page->lru); area->nr_free--; /* 如果取出的块比请求的大,逐级分裂 */ while (current_order > order) { current_order--; area = &zone->free_area[current_order]; /* 将后半部分挂到低一级的链表上 */ struct page *buddy = page + (1 << current_order); list_add(&buddy->lru, &area->free_list); area->nr_free++; } return page; } /* 伙伴系统释放:合并相邻的空闲块 */ static void free_pages(struct page *page, unsigned int order) { unsigned long page_idx = page_to_pfn(page); /* 尝试与伙伴合并,直到无法合并或达到最大 order */ while (order < MAX_ORDER - 1) { unsigned long buddy_idx = page_idx ^ (1 << order); struct page *buddy = pfn_to_page(buddy_idx); /* 检查伙伴是否空闲且大小相同 */ if (!page_is_buddy(buddy, order)) break; /* 从链表中移除伙伴,合并 */ list_del(&buddy->lru); zone->free_area[order].nr_free--; /* 合并后页索引取较小值 */ page_idx = min(page_idx, buddy_idx); order++; } /* 将合并后的块挂到对应 order 的链表 */ list_add(&pfn_to_page(page_idx)->lru, &zone->free_area[order].free_list); zone->free_area[order].nr_free++; } /* ========== Slab 分配器核心逻辑 ========== */ /* Slab Cache:管理同一类型的小对象 */ struct kmem_cache { const char *name; /* Cache 名称 */ unsigned int object_size; /* 对象大小 */ unsigned int objs_per_slab; /* 每个 Slab 中的对象数 */ struct list_head slabs_full; /* 已满 Slab 链表 */ struct list_head slabs_partial; /* 部分 Slab 链表 */ struct list_head slabs_free; /* 空 Slab 链表 */ }; /* Slab 分配:从 partial 链表取对象 */ static void *slab_alloc(struct kmem_cache *cachep) { struct slab *slabp; /* 优先从 partial 链表分配 */ if (!list_empty(&cachep->slabs_partial)) { slabp = list_entry(cachep->slabs_partial.next, struct slab, list); } else if (!list_empty(&cachep->slabs_free)) { /* partial 为空,从 free 链表取 */ slabp = list_entry(cachep->slabs_free.next, struct slab, list); list_move(&slabp->list, &cachep->slabs_partial); } else { /* 需要新建 Slab:向伙伴系统申请一页 */ slabp = new_slab(cachep); if (!slabp) return NULL; list_add(&slabp->list, &cachep->slabs_partial); } /* 从 Slab 中取出一个空闲对象 */ void *objp = slabp->freelist; slabp->freelist = *(void **)objp; /* freelist 是隐式链表 */ slabp->inuse++; /* Slab 满了,移到 full 链表 */ if (slabp->inuse == cachep->objs_per_slab) list_move(&slabp->list, &cachep->slabs_full); return objp; } /* Slab 释放:将对象放回 freelist */ static void slab_free(struct kmem_cache *cachep, void *objp) { struct slab *slabp = virt_to_slab(objp); /* 将对象插入 freelist 头部 */ *(void **)objp = slabp->freelist; slabp->freelist = objp; slabp->inuse--; /* 根据使用率调整 Slab 所在链表 */ if (slabp->inuse == 0) { list_move(&slabp->list, &cachep->slabs_free); } else if (slabp->inuse == cachep->objs_per_slab - 1) { list_move(&slabp->list, &cachep->slabs_partial); } }

四、内存管理的 Trade-offs 分析

伙伴系统的内部碎片:请求 3 页时分配 4 页(2^2),浪费 25%。这是页对齐的代价。缓解方案是 Slab 分配器在小对象层面复用浪费的空间。

Slab 的对象对齐开销:每个 Slab 需要维护 freelist 和元数据,这些开销对小对象(如 16 字节的 dentry)占比显著。Linux 使用"隐式 freelist"(在空闲对象内部存储 next 指针)减少元数据开销。

NUMA 架构的复杂性:多 CPU 系统中,每个 CPU 有本地内存节点,跨节点访问延迟高。伙伴系统和 Slab 都需要按 NUMA 节点分区,增加了管理复杂度。

内存压缩的开销:当外部碎片化严重时,内核需要做内存压缩(memory compaction)——移动已分配的页以合并空闲块。压缩本身消耗 CPU,需要在碎片率和压缩频率之间权衡。

五、总结

Linux 内存管理的分层设计通过伙伴系统解决外部碎片化、Slab 分配器解决内部碎片化。伙伴系统按 2 的幂次管理页帧,分裂和合并操作保证 O(log n) 的分配效率。Slab 分配器在伙伴系统之上管理小对象,通过对象池复用减少分配开销。落地时需要关注内部碎片率、Slab 元数据开销、NUMA 分区和内存压缩策略。理解这两层机制是排查 Linux 内存问题的基础。

http://www.jsqmd.com/news/1013715/

相关文章:

  • MPC8280 PCI桥配置、地址转换与错误处理实战解析
  • 3分钟搭建专属动漫场景搜索引擎:trace.moe全攻略
  • GEO 服务商如何选?2026 年 6 月五家优质 GEO 平台评测分享 - 资讯速览
  • 重庆配眼镜一般多少钱?一份按需求选镜片的价格透明指南 - 配眼镜新资讯
  • Fast-GitHub:国内开发者必备的GitHub加速深度解析与实战指南
  • 2026最新:从化除甲醛公司 5 大排名|基于全民票选与真实口碑|高温高湿气候适配性专项测评 - 专注室内空气检测治理
  • Vector Store:FAISS、Chroma、Milvus、Qdrant、ES 怎么选?
  • Mi-Create终极指南:快速打造个性化小米智能手表表盘
  • macOS Unlocker for VMware ESXi:虚拟化平台兼容性突破技术深度解析
  • Late Chunking:语义驱动的长文本嵌入动态分块技术
  • 3个颠覆性应用场景:LSPatch如何让Android免Root模块化成为现实
  • 2026年6月最新版|百达翡丽全国官方售后服务体系全解析 - 资讯速览
  • 2026郑州汽车租赁推荐:三大热门租车深度对比测评 - 资讯速览
  • RK3566 NPU实战:对比YOLOv5在rknn-toolkit2 v1.4与v1.6版本上的部署差异与性能实测
  • 如何将普通鼠标变成macOS上的生产力神器:Mac Mouse Fix完全指南
  • 从热力图到流向图:数据可视化新手如何用对7种专题地图,让你的图表不再‘翻车’
  • LangChain 系列:从 0 搭一个企业知识库问答系统
  • 5步上手Ryujinx:在电脑上完美运行Switch游戏的终极免费教程
  • Cursor Free VIP:简单三步永久激活Cursor Pro,告别试用限制的终极解决方案
  • MTKClient终极指南:轻松解锁和刷机联发科设备的完整教程
  • 从BERT到GPT:预训练模型两大流派怎么选?项目实战中的避坑指南
  • 解锁学术壁垒:caj2pdf-qt跨平台转换实战探索
  • 2026 连南县室内除异味、新房除甲醛怎么选?专业对比 + 案例解析,优先推荐清远佰家环保 - 专注室内空气检测治理
  • 严守原厂标准:2026年欧米茄官方售后的配件保障与服务体系解析 - 资讯速览
  • Potree vs Cesium 加载点云,到底怎么选?从项目需求到技术细节的深度对比
  • 系统调用与设备驱动:从用户态到内核态的跨越机制
  • 2026年京东云简易方法:OpenClaw怎么部署?Token Plan配置及大模型Skill配置
  • 深度解析ok-ww:如何用图像识别技术实现《鸣潮》智能自动化
  • 【力扣100题】96.跳跃游戏 II
  • 实测避坑:用GPT-4All离线跑代码和文案,8G和13B模型到底哪个更靠谱?