news 2026/10/26 2:43:17

Linux内存管理:Buddy系统初始化与物理内存分配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内存管理:Buddy系统初始化与物理内存分配

1. Buddy系统在Linux内存管理中的核心地位

当Linux内核启动时,物理内存的管理是系统初始化的关键环节之一。Buddy(伙伴)系统作为Linux物理内存管理的基础设施,其重要性不亚于城市交通系统中的红绿灯和道路规划。想象一下,如果没有合理的内存分配机制,内核就像一座没有交通规则的城市——各个子系统争抢内存资源,最终导致系统崩溃。

Buddy系统的设计初衷是解决内存分配的外部碎片问题。在早期的操作系统中,频繁的内存分配和释放会导致内存被分割成许多小块,虽然总空闲内存足够,但无法满足稍大的连续内存请求。Buddy系统通过将物理内存划分为不同大小的块(通常为2的幂次方),并采用特定的合并策略,有效减少了外部碎片。

关键点:Buddy系统管理的不是虚拟内存,而是物理页帧(page frame)。每个页帧对应物理内存的一个4KB(默认)区域。

在x86架构中,Buddy系统管理的最小单位是struct page结构体。这个结构体包含了页帧的所有元信息,比如引用计数、标志位等。内核通过mem_map数组管理所有物理页帧,而Buddy系统则在此基础上构建了更高效的内存分配机制。

2. 内核启动早期的内存布局探测

Buddy系统的初始化不是一蹴而就的,它建立在系统对物理内存布局的准确认知基础上。在x86架构中,这个过程始于BIOS或UEFI提供的e820内存映射表。

2.1 从BIOS获取原始内存信息

内核启动时,arch/x86/kernel/e820.c中的代码会调用detect_memory_e820()函数,通过int 0x15中断获取物理内存布局。典型的输出如下:

[ 0.000000] e820: BIOS-provided physical RAM map: [ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable [ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved [ 0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved [ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007ffd8fff] usable

这些信息会被转换为struct e820_entry数组,记录哪些内存区域可用,哪些被保留给硬件设备使用。

2.2 内存区域的分类与处理

内核将物理内存划分为几个关键区域:

  1. ZONE_DMA:0-16MB,用于老式ISA设备的DMA操作
  2. ZONE_DMA32:在64位系统上,用于只能寻址32位地址空间的设备
  3. ZONE_NORMAL:直接映射到内核虚拟地址空间的常规内存
  4. ZONE_HIGHMEM:在32位系统上,超过内核直接映射范围的内存

这种分区管理是Buddy系统初始化的前提条件。每个zone都有自己的Buddy分配器实例,处理不同特性的内存请求。

3. Buddy系统的数据结构初始化

3.1 核心数据结构解析

Buddy系统的核心是以下几个数据结构:

  1. struct zone:描述一个内存区域,包含该区域Buddy系统的所有状态
  2. free_area:管理不同阶(order)的空闲块链表
  3. struct page:每个物理页帧的元数据

关键初始化过程发生在mm/page_alloc.c中的init_currently_empty_zone()函数:

static void __init init_currently_empty_zone(struct zone *zone, unsigned long zone_start_pfn, unsigned long size) { struct pglist_data *pgdat = zone->zone_pgdat; unsigned long nr_pages = size; unsigned long pfn = zone_start_pfn; for (; pfn < zone_start_pfn + nr_pages; pfn++) init_reserved_page(pfn_to_page(pfn)); }

3.2 空闲列表的构建

Buddy系统为每个zone维护一组空闲链表,每个链表对应不同大小的内存块(2^0, 2^1,..., 2^MAX_ORDER-1页)。初始化时,系统需要:

  1. 计算zone的起始和结束页帧号
  2. 将所有可用页帧标记为保留状态
  3. 按照MAX_ORDER的最大值初始化free_area数组

典型的MAX_ORDER值为11,意味着Buddy系统能处理的最大连续块是2^10=1024个页(4MB)。

4. 物理页帧的初始化流程

4.1 mem_map数组的建立

mem_map是struct page的数组,每个物理页帧对应一个元素。其初始化过程包括:

  1. 计算所需mem_map数组的大小
  2. 为mem_map本身分配内存(有趣的是,这发生在Buddy系统完全初始化之前)
  3. 初始化每个struct page的字段:
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone, unsigned long start_pfn, enum memmap_context context) { struct page *page; unsigned long end_pfn = start_pfn + size; unsigned long pfn; for (pfn = start_pfn; pfn < end_pfn; pfn++) { page = pfn_to_page(pfn); __init_single_page(page, pfn, zone, nid); set_page_links(page, zone, nid, pfn); } }

4.2 页帧标志位的初始设置

每个struct page包含多个标志位,影响Buddy系统的行为:

  • PG_reserved:标记页帧是否被保留
  • PG_buddy:标记页帧是否属于Buddy系统
  • PG_slab:标记页帧是否被slab分配器使用

初始化时,所有可用页帧的PG_reserved位被清除,准备加入Buddy系统的空闲列表。

5. 空闲内存的最终整合

5.1 将空闲内存加入Buddy系统

在mem_init()函数中,内核会扫描所有可用的内存区域,将空闲页帧释放到Buddy系统中:

void __init mem_init(void) { free_all_bootmem(); /* 更新内存统计信息 */ mem_init_print_info(); }

free_all_bootmem()是关键函数,它:

  1. 遍历所有bootmem(启动时临时内存分配器)管理的空闲区域
  2. 调用__free_pages()将这些页帧释放到Buddy系统
  3. 更新zone的空闲页计数

5.2 伙伴合并机制的启用

Buddy系统的核心特性是能够合并相邻的空闲块。初始化完成后,系统会:

  1. 检查所有空闲块是否有"伙伴"(相同大小、物理地址连续的块)
  2. 如果找到可合并的伙伴,将它们合并为更高阶的块
  3. 递归执行此过程,直到无法继续合并

这个机制确保了系统总是能提供尽可能大的连续内存块。

6. 多核环境下的特殊处理

在现代多核系统中,Buddy系统的初始化还需要考虑NUMA(非统一内存访问)架构的影响。

6.1 NUMA节点的初始化

每个NUMA节点有自己的pg_data_t结构,包含该节点的内存zone信息。初始化流程:

  1. 通过ACPI或硬件特定方法检测NUMA拓扑
  2. 为每个节点分配pg_data_t结构
  3. 初始化节点内的各个zone

6.2 每CPU页帧缓存

为了优化性能,Linux为每个CPU维护了一个页帧缓存(per-CPU pageset)。初始化时需要:

  1. 为每个CPU分配缓存结构
  2. 设置热/冷页的阈值
  3. 将缓存与对应的zone关联

这种设计减少了多核竞争,提高了内存分配效率。

7. 调试与验证机制

内核提供了多种机制来验证Buddy系统初始化的正确性。

7.1 内存信息输出

通过/proc/buddyinfo可以查看各zone的Buddy系统状态:

Node 0, zone Normal 3 4 5 4 3 2 1 1 1 1 0

这表示在Normal zone中:

  • 有3个2^0=1页大小的块
  • 4个2^1=2页大小的块
  • 以此类推

7.2 完整性检查

CONFIG_DEBUG_PAGEALLOC选项启用额外的检查:

  • 验证空闲列表的完整性
  • 检测双重释放等错误
  • 验证页标志位的一致性

这些检查在开发阶段能及时发现Buddy系统初始化的问题。

8. 实际应用中的性能考量

Buddy系统初始化完成后,其性能直接影响整个系统的内存分配效率。以下是几个关键优化点:

  1. MAX_ORDER的选择:较大的值支持更大的连续分配,但增加管理开销
  2. zone的划分:合理的zone边界减少跨zone分配
  3. 预分配策略:为关键子系统预留内存,避免碎片化

在嵌入式系统中,开发者可能需要调整这些参数以适应特定的内存约束。

Buddy系统初始化的最后一步是释放启动期间使用的临时内存分配器(bootmem),将系统完全切换到Buddy内存管理模式。至此,Linux内核拥有了完整的内存管理能力,为后续子系统的初始化奠定了基础。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 2:41:22

T_MATS安装包配置与单轴涡喷Simulink建模实战

简介&#xff1a;面向航空发动机建模与仿真方向的学习者与研究人员&#xff0c;提供 MATLAB 环境下的 T_MATS 热力学系统建模工具箱安装包&#xff0c;并以 NASA 标准化 JT9D 涡扇发动机模型为实例&#xff0c;可用于控制策略验证、性能评估与故障诊断等研究&#xff0c;适合具…

作者头像 李华
网站建设 2026/10/26 2:41:17

Docker 部署 Lidarr 音乐库自动化管理:配置、整理与运维实战

1. 先把需求理清楚&#xff1a;为什么我要用 Docker 搭 LidarrLidarr 这个工具&#xff0c;第一次听名字可能没什么感觉&#xff0c;但用过之后&#xff0c;你会发现它是那种能让人省下几百个小时的工具。简单说&#xff0c;Lidarr 是一个面向音乐库的自动化管理工具&#xff0…

作者头像 李华
网站建设 2026/10/26 2:41:14

TVHeadend容器化部署:电视流媒体、硬件转码与录像调优

1. 为什么把 TVHeadend 塞进容器&#xff1a;先把选型逻辑理清楚TVHeadend 本质上是一个电视信号接收与流媒体分发服务&#xff0c;它把从调谐器、网络播放列表拿到的原始流&#xff0c;统一转成标准协议分发给局域网里的各种播放客户端&#xff0c;同时负责电子节目单抓取、定…

作者头像 李华
网站建设 2026/10/26 2:41:05

Google Workspace插件开发实战指南

1. Google Workspace插件开发概述Google Workspace作为企业级生产力套件&#xff0c;其插件系统为开发者提供了强大的扩展能力。通过Apps Script这一云端JavaScript平台&#xff0c;开发者可以创建与Gmail、Docs、Sheets等核心应用深度集成的定制化功能。我曾在多个企业协作项目…

作者头像 李华
网站建设 2026/10/26 2:40:52

Web端PDF预览技术解析与pdf.js实战优化

1. 项目概述&#xff1a;Web平台PDF预览的核心需求在Web开发中实现PDF文件预览是个看似简单却暗藏玄机的需求。我最近接手的一个企业文档管理系统项目就遇到了这个经典问题——客户要求在H5页面无缝预览各种规格的PDF文档&#xff0c;还要支持标注、缩放等基础功能。这个需求背…

作者头像 李华
网站建设 2026/10/26 2:39:56

短信提醒App开发:违停与工单通知的技术实现

1. 短信提醒类App的另类应用场景最近发现一个有趣的现象&#xff1a;市面上出现了一批专门针对"违停短信"和"工单短信"的短信提醒App。这类应用看似简单&#xff0c;却在特定场景下解决了用户的痛点需求。作为一个长期关注移动应用领域的从业者&#xff0c…

作者头像 李华