尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Chapter-1 Memory Management (section 1.1-1.5)

Chapter-1 Memory Management (section 1.1-1.5)
📅 发布时间:2026/6/23 15:48:30
简单介绍了页表及 Linux 建立完整的物理地址映射到虚拟地址的内存管理之前 memblock 的一些动作

参考了 《打通 Linux 操作系统和芯片开发》 书籍的内容,实际也可以说是完全参照加上了个人的拙见或者是读书记录。
和我上一篇说的一样,我依然还是一个初学者,记录这些是自己梳理,以及想让文字发挥一些作用和意义。

涉及到代码的部分实在是非常非常的枯燥无味和无聊,并且由于 Linux 中函数的分层很多,call stack 特别深,函数名称特别相似,
非常容易头晕眼花了,所以还是应该采取从宏观角度的,抓住关键的函数调用链来进行分析和理解,不应该要求细节到某一行代码的程度,
否则陷入难解的困境了。

目录
  • 操作系统为什么需要内存管理?
  • 一级、二级页表映射过程
  • memblock 物理内存的初始化
    • memblock 的作用
    • memblock 的数据结构及代码分析

操作系统为什么需要内存管理?

这应该是一个很经典的问题,内存池 (Memory Pool) 也可以认为是一种内存管理的方式,所以关于内存管理四个字有点像谜底就在谜面上,更多的只是你如何管理的方式。
比如 FreeRTOS 中的好几种分配方式,常用的只是 heap_4.c 的方式,这种用在 MCU 上的方式可以比较简单,而对于现代的 2025 年的 MCU 可能依然还是比较小的内存,
至少没有上升到 4GB ,至少我还没接触到。并且芯片性能可能不强,无法负责和管理这么多的内存(后面出现了一个东西专门辅助此工作),所以操作系统采取了其他的方式来管理。

MCU 跑的都是在很小的内存中,大部分直接都是访问了物理地址,
所以简单的说为什么的原因,就是为了更好的利用和使用内存 这个相对比较快速的可以存数据的东西,才出现了内存管理的各种方式,一般在操作系统课程中都会提到在演进中出现的:

  • 分段机制(Segmentation)
  • 分页机制(Paging)

两种经典的方式,也可能听到 段页式 就是两种合并在一起说的。

但 Linux 采取的是分页的机制,同时根据书中描述是 四级页表 的形式,关于分段、分页的一些说明和概述及原理性这里就不详细说明,可以询问 ChatGPT 或是查看其他的文章,
这里仅添加一些可能重要的名词:

名词 翻译
换入 Swapping In
换出 Swapping Out
页面 Page
物理页面 Physical Page
页帧 Page Frame
页帧号 Page Frame Number (PFN)
虚拟页帧号 Virtual Page Frame Number (VPN)
物理页帧号 Physical Page Frame Number (PFN)
虚拟页面 Virtual Page
页表 Page Table (PT)
页表项 Page Table Entry (PTE)
内存管理单元 Memory Management Unit (MMU)
虚拟地址 Virtual Address
物理地址 Physical Address
转译后备缓冲器(快表) Translation Lookaside Buffer (TLB)
页全局目录 Page Global Direcotry (PGD)
页上级目录 Page Upper Directory (PUD)
页中间目录 Page Middle Direcotry (PMD)
页表 Page Table (PTE)
上面出现了但含义不一样
这里主要指Linux中多级页表的
页内偏移 Page offset

上面提到了一个专门辅助 Linux 做内存管理的东西就是 MMU 了(用来将虚拟地址转换成物理地址),现代的芯片一般都是将 MMU 内置在芯片中了,这是 Linux 运行的必备条件,所以 区别一个芯片能不能运行 Linux 系统,就是芯片有没有 MMU 这个模块了。

关于 MMU 如何寻址,如何管理,以及页表的映射和使用的过程,这里不多赘述,感兴趣的可以找操作系统相关课程学习,或者找 408 相关的学习视频参考。
另外重点是 Linux 一个页面的大小为 4KB 至于为什么是 4KB 的大小,AI 给出的答案是历史原因;还有就是想要运行 Linux 就需要 MMU 这个模块;

Linux 内存架构和模型略过,对理解关系不大不太重要

一级、二级页表映射过程

感觉实在是有必要的画一个一级、二级的页表映射的过程

二级页表只是在一级的基础上做了修改,添加了一个对一级页表的索引表,这样能对应的一级页表项就会更多了,而 Linux 用了四级来管理,数量就不计算了,同时这只是大致的示意,不代表就是这样的寻址。

对于 Linux 的多级页表管理不准备画图了,只不过是更多级,更复杂,更多控制位,更长的地址长度,但基本的方式是一样的。
简单说明就是首先将一个包含了虚拟页帧号的虚拟地址(线性地址)通过一系列查表的方式(查表的这个动作也可以是 MMU 在执行)转换成一个包含了物理页帧号的物理地址,(这里假设用到了 TLB ) 然后 MMU 通过查 TLB 快速的知道了物理页帧号与内存的某一块位置的对应关系,
然后就使用一下偏移量,在这一页中的偏移多少,就知道了这个虚拟地址的数据内容是多少了。
另外在这个过程中可能会产生一个 缺页中断 (Page Fault) ,简单说明即是在代码中使用 malloc 申请内存空间是在虚拟地址空间中,此时随便申请,实际上物理的内存条上对应映射的空间并不存在,或者说并没有数据内容,
或者当访问的页不在任何一个页表中,这个时候出现了缺页中断,此时才会从存储中加载到内存中,或者是正式的分配内存,这时候内存条上就有了空间和数据内容了,那么这正好也有页的换入(Swaping In)和换出(Swaping Out)两个动作。

memblock 物理内存的初始化

这部分有比较多的图和代码,太麻烦了,尝试通过文字来简单的叙述看看

memblock 的作用

Linux 中通过 Buddy 伙伴系统和 slab 分配器来分配和管理内存的,但是在此之前不可用的阶段,就由 memblock 来承担了初始化和管理的工作,所以自然的就想到这个阶段的 memblock 直接就是访问和管理的物理地址,
memblock 是唯一能做早期启动阶段管理内存的内存分配器,由此出现了 early boot memory 阶段的名称,是系统启动中间阶段的内存管理,这里涉及的内存模型不多赘述。

memblock 的数据结构及代码分析

书籍解析了代码的结构,只是简单解析了各部分的字段含义,详细可以直接让 AI 生成,注释内容 Gemini 2.5 Flash 生成:

include/linux/memblock.hstruct memblock {bool bottom_up;					/* 是否是自底向上? */phys_addr_t current_limit;		/* 当前限制地址 */struct memblock_type memory;	/* 可用内存区域 */struct memblock_type reserved;	/* 保留内存区域 */
};struct memblock_type {unsigned long cnt; 				 /* 区域计数 */unsigned long max; 				 /* 最大区域数 */phys_addr_t total_size; 		 /* 总大小 */struct memblock_region *regions; /* 区域数组 */char *name;						 /* 类型名称 */
};struct memblock_region {phys_addr_t base;		 	/* 区域基地址 */phys_addr_t size; 			/* 区域大小 */enum memblock_flags flags; 	/* 区域标志 */
#ifdef CONFIG_NUMAint nid; /* NUMA节点ID */
#endif
};enum memblock_flags {MEMBLOCK_NONE			= 0x0,	/* 无特殊请求 */MEMBLOCK_HOTPLUG		= 0x1,	/* 可热插拔区域 */MEMBLOCK_MIRROR			= 0x2,	/* 镜像区域 */MEMBLOCK_NOMAP			= 0x4,	/* 不添加到内核直接映射 */MEMBLOCK_DRIVER_MANAGED = 0x8,	/* 总是通过驱动检测 */MEMBLOCK_RSRV_NOINIT	= 0x10,	/* 不初始化struct pages */
};

接着从 stark_kernel 入手,主要关注了 setup_arch 函数,参数是 command_line,贴出函数:

// 只给出相对重要的函数调用
arch/arm64/kernel/setup.cvoid __init __no_sanitize_address setup_arch(char **cmdline_p)
{setup_initial_init_mm(_stext, _etext, _edata, _end);*cmdline_p = boot_command_line;... ...early_fixmap_init();early_ioremap_init();setup_machine_fdt(__fdt_pointer);... ...arm64_memblock_init();paging_init();... ...bootmem_init();... ...
}

Gemini 2.5 Flash:

  1. setup_initial_init_mm

    • 初始化内核的第一个内存描述符 init_mm。
    • 主要用于设置内核代码 (_stext, _etext) 和数据段 (_edata, _end) 的内存范围。
  2. *cmdline_p = boot_command_line

    • 将系统启动时传递的命令行参数 (boot_command_line) 赋值给 cmdline_p 指针。
    • 这使得后续的内核组件能够访问和解析启动参数。
  3. early_fixmap_init

    • 初始化早期固定映射(fixmap)区域。
    • 用于在启动初期为特定的、固定地址的内存区域建立虚拟地址映射,通常用于访问一些关键的硬件寄存器或数据结构。
  4. early_ioremap_init

    • 初始化早期 I/O 内存重映射机制。
    • 允许内核在启动初期安全地访问和映射设备 I/O 空间,例如外设控制器的寄存器。
  5. setup_machine_fdt(__fdt_pointer)

    • 根据设备树(Flattened Device Tree, FDT)设置机器相关的参数和配置。
    • 解析由 __fdt_pointer 指向的设备树,从中获取硬件信息、设备配置等,以初始化系统。
  6. arm64_memblock_init

    • 初始化 ARM64 架构的内存块(memblock)管理机制。
    • 用于在内核启动早期跟踪和管理物理内存区域,包括可用内存和保留内存。
  7. paging_init

    • 初始化页表和内存分页机制。
    • 建立将物理内存映射到虚拟地址空间的页表结构,这是现代操作系统内存管理的基础。
  8. bootmem_init

    • 初始化 bootmem 分配器。
    • 这是一个简单的物理内存分配器,在内核启动的早期阶段(分页机制刚建立,但更复杂的内存管理系统尚未完全初始化时)用于分配物理内存。

接着继续分析了 setup_machine_fdt 函数:

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{int size;void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);const char *name;if (dt_virt)memblock_reserve(dt_phys, size);if (!early_init_dt_scan(dt_virt, dt_phys)) {pr_crit("\n""Error: invalid device tree blob at physical address %pa (virtual address 0x%px)\n""The dtb must be 8-byte aligned and must not exceed 2 MB in size\n""\nPlease check your bootloader.",&dt_phys, dt_virt);while (true)cpu_relax();}/* Early fixups are done, map the FDT as read-only now */fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);name = of_flat_dt_get_machine_name();if (!name)return;pr_info("Machine model: %s\n", name);dump_stack_set_arch_desc("%s (DT)", name);
}

该函数主要功能是:

  • 拿到 DTB 的物理地址后,会通过 fixmap_remap_fdt() 进行映射,其中包括 pgd、pud、pte 等映射(书中这部分是否漏了 pmd ?),当映射完成后会返回 dt_virt,并通过 memblock_reserve() 添加到 memblock.reserved 中。
  • early_init_dt_scan() 通过解析 DTB 文件的 memory 节点获得可用物理内存的起始地址和大小,并通过类 memblock_add 的 API 向 memory.regions 数组添加一个 memblock.region 实例,用于管理这个物理内存的区域。

接着是 arm64_memblock_init 函数,其主要工作是将物理内存进行整理,将一些特殊区域添加到 reserved 内存中,主要是设备树中的:chosen, chosen(cma), reserved-memory, /memreserve, chosen(initrd) 节点。

这部分的代码工作大体将物理内存进行了分区和简单的管理,后续需要进行重要的 内存页表映射 完成物理地址到虚拟地址的映射,书中说系统完成初始化之后,所有的工作会移交给 Buddy 系统来进行内存管理。

—— juezhong 乙巳年丙戌月戊辰日 戌时

「夫人神好清,而心擾之,人心好靜,而慾牽之。」

本文来自博客园,作者:纵然似梦,转载请注明原文链接:https://www.cnblogs.com/juezhong/p/19167298

相关新闻

  • 2025年摩托车/机车厂家权威推荐榜:专业制造工艺与骑行性能深度解析,精选实力品牌及选购指南
  • 妙题合集
  • 2025 年 10 月门窗十大品牌榜单揭晓,技术创新实力与市场口碑双重透视

最新新闻

  • Gatsby入门:从Node.js环境搭建到首个可运行网站
  • 垂直图表与数据驱动可视化:植物生态数据交互界面设计实践
  • 混合系统不变集计算:理论与机器人应用
  • IPCCF算法:基于意图解耦与对比学习的可解释推荐系统实践
  • Zoro智能编码代理:规则引擎与LLM融合,提升代码质量与开发效率
  • 医学AI模型可解释性实战:13种XAI方法在头颈癌预后预测中的横向评测与选型指南

日新闻

  • Arduino-ESP32项目深度解析:解锁隐藏芯片支持与架构演进
  • 2026年 系统窗厂家/品牌推荐榜单:隔音系统窗+高端系统门窗的核心优势与选购指南 - 品牌发掘
  • NVBench:首个双语非言语发声语音合成评测基准详解与实践

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号