当前位置: 首页 > 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.gsyq.cn/news/1525532.html

相关文章:

  • 3分钟搭建专属动漫场景搜索引擎:trace.moe全攻略
  • Vector Store:FAISS、Chroma、Milvus、Qdrant、ES 怎么选?
  • 3个颠覆性应用场景:LSPatch如何让Android免Root模块化成为现实
  • 如何将普通鼠标变成macOS上的生产力神器:Mac Mouse Fix完全指南
  • 从BERT到GPT:预训练模型两大流派怎么选?项目实战中的避坑指南
  • 2026 连南县室内除异味、新房除甲醛怎么选?专业对比 + 案例解析,优先推荐清远佰家环保 - 专注室内空气检测治理
  • 解锁学术壁垒:caj2pdf-qt跨平台转换实战探索
  • MTKClient终极指南:轻松解锁和刷机联发科设备的完整教程
  • 实测避坑:用GPT-4All离线跑代码和文案,8G和13B模型到底哪个更靠谱?
  • 欧米茄官方售后服务中心全攻略:全国网点、服务流程与联系方式(2026年6月最新) - 资讯速览
  • 2026年6月14日合肥黄金铂金K金钻石回收哪家靠谱 五大正规实体店排行榜实测推荐典典金奢无套路当面结款 - 资讯速览
  • 2026金华GEO优化哪家强?技术实力+客户效果双维度深度解析 - 936品牌测评网
  • 别再傻傻分不清了!一文搞懂RTK和CORS在无人机测绘、自动驾驶里的真实用法
  • 在 Oracle EBS 中设置权益法(Equity Method)调整规则,是一个结合了系统配置与会计准则的复杂过程。这主要依赖于 全球合并系统(GCS) 或 财务合并中心(FCH),并深度结合 子
  • Skills实战:从0到1设计一个“数据驱动”Skill,一行配置跑10组参数
  • 洛雪音乐音源完整指南:3步免费获取全网无损音乐
  • 昆明配眼镜去哪好?一份给实在人的选购参考 - 配眼镜新资讯
  • 杭州配眼镜去哪配?功能性镜片选购一篇说清 - 配眼镜新资讯
  • Audiveris光学乐谱识别引擎:跨平台安装与高效使用指南
  • 暗黑破坏神2存档编辑器:免费开源的角色修改神器终极指南
  • AI 辅助数据标注质量检测与主动学习采样:从“人工苦力“到“智能协作“
  • 厦门市誉金合抛磨材料有限公司:厦门本地抛磨耗材与加工设备综合服务商 - 资讯速览
  • 5分钟将图片转为3D打印模型的终极指南:ImageToSTL完全教程
  • 5个实战场景:深度解析Edge-TTS在Python项目中的高级应用
  • ScintillaNET技术选型深度分析:构建企业级代码编辑器的架构决策指南
  • 曝光泸州黄金回收套路!实测 4 家靠谱商家,无隐形扣费 - 资讯速览
  • Rusted PackFile Manager:5步打造专业级《全面战争》模组的终极指南
  • MPC8260 DMA控制器原理与配置实战:缓存一致性与链式传输详解
  • 论文写作用哪个AI模型?精选3款学术专用大模型 - 掌桥科研-AI论文写作
  • 5分钟快速上手Bilibili视频批量下载工具:开源免费跨平台解决方案