0%

Linux内核内存管理 - 内核职责

本系列是本人对Linux内核内存管理的学习持续总结。

内存系统是操作系统最复杂的子系统之一,内存管理穿插着内核的方方面面。做驱动开发有2年多了,之前写过Linux内核内存管理的博客。现在回头看,之前的理解并不到位,也不完整。希望用本系列对Linux内核内存管理的知识做重新梳理,增强自己的理解,也能给对这个复杂功能一头雾水的朋友提供一些思路。

平台

为了阐述方便,本文(系列)会基于Intel 64位平台做讨论。所涉及内核代码主要位于如下目录:

  • mm
  • arch/x86

内核职责

内存是系统得以运行的最基本保证。为了将内存进行有效管理,内核需要做如下考虑:

物理内存管理

  • 非一致性内存访问(NUMA): 多处理器系统中有多个内存节点。每个处理器和每个内存节点距离并不相同,因此访问不同内存节点的距离和开销并不相同。OS需要考虑如何有效管理处理器对内存的访问,使该处理器更多地距其更近的处理器(一般称为Local)节点。 当然实际内核NUMA内存管理策略并非所述这么简单,Linux有一系列配置NUMA访问策略的方法,详见NUMA Policy Guide
  • 内存热拔插: 顾名思义,系统运行时内存热拔插的处理。

内核物理地址的规划

在Boot Loader加载内核后,如何分别摆放16 Bit, 32 Bit代码区域。压缩内核,以及如何解压内核,解压还要考虑KASLR(内核地址随机化)等因素。同时,每个启动阶段使用的堆和栈如何划分。

虚拟内存管理

  • 内核初始化前:可大致分为32 Bit页表映射,64 Bit内存页表映射(解压内核前),64 Bit内存页表映射(解压内核后)
  • 内核初始化后: 各个进程独立页表的控制,同时需要保证内核空间态地址对所有进程是一致的。同时需要考虑用户态和内核态对不同内存区域访问权限的控制。

外设访问

通过虚拟地址访问外设IO端口或者MMIO端口,这需要内核为其建立对应的页表项,同时为了保证特定IO区域只能有一个主体来管理,内核需要以树状结构来管理IO区域。
同时,外设要访问的内存空间,需要考虑CPU和外设访问内存一致性问题(DMA一致性)。

内存分配

内存分配和释放是操作系统内最为频繁的操作。保证内存分配和释放的同时,也需要考虑避免系统内存的碎片化,避免系统运行到一段时间后,程序需要一块大内存的的时候无法分配到。其中:

  • 伙伴系统(Buddy System): 按页管理内存分配和释放
  • SLAB系列: 页内小内存分配的管理和释放

内存回收和换页

当系统物理内存紧张时,系统会将一些内存换出到硬盘上。而当系统访问该内存页产生Page Fault时,操作系统需要负责将该内存页的内容换回到内存上。

内存使用检测和Debug

除了上述职责,内核也需要提供方法对内存使用进行检测和调试。例:

  • 程序可能会对内存做不当使用,当这种不当使用发生在内核态,往往会产生严重的后果。因此Linux提供了KASAN、Kmemleak等工具方便开发人员进行检查。
  • 同时内核内存管理子系统也提供了一系列接口和方法供系统运维管理人员对内存使用进行调试和检测。

结语

以上为本人对内核内存管理功能的梳理。因为内核内存管理功能复杂,以上理解并不一定准确,因此本文也需要持续更新。系列文章后续也将会对本文提及的内容进行具体的分析和介绍。