内存管理 - 物理内存

简介: 内存管理 - 物理内存

本篇从我自己的角度来写对物理内存管理的理解。由于 Linux 引入了虚拟内存的概念,应用程序对物理内存的访问都是由内核模块来接管的,因此带着以下问题,逐步揭开相关的细节:

  1. 内核是使用什么地址访问物理内存的?
  2. 物理内存为何需要分区?
  3. 伙伴系统和 SLAB 系统 有何区别?

页框管理

想要管理内存,首先要知道有哪些内存,并且把内存状态记录下来。物理内存默认以 4k 分割为一个个的单元,每个单元被称为页框(page frame)。内核使用 struct page 数组跟踪内存中每个页框的当前状态。数组的每个元素对应于物理内存中的一个页框,数组定义如下:

// `struct page` 定义在 `linux/mm_types.h`
struct page *mem_map;

例如,mem_map[0] 包含内存中第一个页框的信息

名词说明:

  • 页框: 存储数据的内存块
  • 页:存放在页框内的数据块

如此,内核就通过页框数组把所有的内存使用索引了起来,并且知道每个页的情况,例如:是否空闲、拥有者是谁。

为什么分区?

然而对于内核来说仅有分页是不够的,内核也没办法 完全 直接访问内存,是什么原因呢?

具体还是要从内存分配过程聊起来。进程申请内存的时候,会调用 malloc() 和 mmap() 等内存分配函数,最终会发起系统调用陷入内核态进行内存分配。但是,内存分配过程分配的只是虚拟地址空间,并没有给虚拟内存分配对应的物理内存。当进程访问没有建立映射关系的虚拟内存时,将触发一个缺页中断。当一个进程发生缺页中断的时候,进程会再次陷入内核态,查找/分配一个页框,建立映射关系(虚拟地址到物理地址)

可以看到进程在分配内的时候两次进入内核态,然而两次却完全不同。要理解这一点首先要熟悉两个概念 “进程上下文” vs “中断上下文”

在 Linux 实现中,处理器在执行过程中总是处于以下三种状态:

(1)内核态,运行于进程上下文,内核代表进程运行于内核空间。

(2)内核态,运行于中断上下文,内核代表硬件运行于内核空间。

(3)用户态,运行于用户空间。

内核的地址空间不仅仅要支持硬件访问,同时还需要映射到进程的虚拟地址空间,成为进程上下文的一部分。

当然,单独从实现来看,对于(1)、(2)两种情况,内核的上下文如果完完全全从进程上下文独立开也是可行的,甚至更为简单。但是从性能来看,当前的方案才是更优的。详情参考:《User Space on Top of Kernel Space Versus Separated Address Spaces》

分区地址映射

32位系统中,内核模块的地址空间只有1G。但是,内核又要访问所有的 4G 内存。但内核访问物理内存与进程访问虚拟内存不同,虚实映射既消耗空间也消耗性能(详见:地址映射),且在内核场景下,内存移动与内存换出的需求并不高,也没有多进程隔离的需求(详见:内存共享),映射的收益不大。

因此,内核把页框分组,划分为不同的区(ZONE)。内核空间的前 896MB(不仅是内核代码,还有它的数据)被“直接”映射到物理内存。虚拟内核空间的最后 128MB 部分被映射到物理“高内存”(> 896MB)的一些部分。物理内存的直接映射允许物理页面分配器的直接访问获得的页面,而无需任何映射操作。获取物理页的虚拟地址所需的唯一操作是添加固定偏移量。

通过以上方式,既实现 4G 内存的访问,也保证了内核访问的性能。最终,物理内存的页框就被组织成了以下的形式

从内核地址空间虚实转换的视角来看,如下:

内存分配器

对于空闲内存的分配管理是交给内存分配器进行的。内核中有两种内存分配器,即伙伴系统分配器 和 SLAB 分配器。前者是页框分配器,后者是对象分配器。

伙伴系统的引入为内核提供了一种用于分配一组连续的页而建立的一种高效的分配策略。避免因频繁地申请和释放不同大小的连续页框,导致在已分配页框的内存块中分散了许多小块的空闲页框,而其他需要分配连续页框的请求无法得到满足。

SLAB 工作是针对一些经常分配并释放的对象,如进程描述符等内核中常见的小对象。如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而 SLAB 分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符),每当要申请这样一个对象,就从一个 SLAB 列表中分配同样大小的内存,而当要释放时,将其重新保存在该列表中。

伙伴系统解决了内存外部碎片问题,而 SLAB 解决了内存的内部碎片问题。所谓外部碎片是指由于频繁地申请和释放页框而导致的某些小的连续页框,而内部碎片就是指被分配出去但是不能被利用的内存。

两个系统的细节暂时按下,后续详聊。

本文作者 : cyningsun

本文地址https://www.cyningsun.com/06-15-2021/memory-management-physical-memory.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Linux

  1. 内存管理基础概念总述
  2. 从 lsof 开始,深入理解虚拟文件系统
目录
相关文章
|
22天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
35 8
|
1月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
49 6
|
2月前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
2月前
|
存储 缓存 监控
深入了解MySQL内存管理:如何查看MySQL使用的内存
深入了解MySQL内存管理:如何查看MySQL使用的内存
441 1
|
2月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
100 3
|
3月前
|
Java
在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
【9月更文挑战第25天】在ArkTS中,有效进行内存管理并避免内存泄漏的方法包括:及时释放不再使用的资源,如关闭监听器和清理定时器;避免循环引用,通过弱引用打破循环;合理使用单例模式,确保单例对象正确释放;及时处理不再使用的页面和组件,在卸载时清理相关资源。
135 9
|
3月前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
2月前
|
Java C语言 iOS开发
MacOS环境-手写操作系统-16-内存管理 解析内存状态
MacOS环境-手写操作系统-16-内存管理 解析内存状态
51 0
|
3月前
|
存储 并行计算 算法
CUDA统一内存:简化GPU编程的内存管理
在GPU编程中,内存管理是关键挑战之一。NVIDIA CUDA 6.0引入了统一内存,简化了CPU与GPU之间的数据传输。统一内存允许在单个地址空间内分配可被两者访问的内存,自动迁移数据,从而简化内存管理、提高性能并增强代码可扩展性。本文将详细介绍统一内存的工作原理、优势及其使用方法,帮助开发者更高效地开发CUDA应用程序。
|
4月前
|
存储 Java 程序员
JVM自动内存管理之运行时内存区
这篇文章详细解释了JVM运行时数据区的各个组成部分及其作用,有助于理解Java程序运行时的内存布局和管理机制。
JVM自动内存管理之运行时内存区