Linux内存管理

掌握Linux内存管理,以下概念都需要理解:

bash 复制代码
虚拟地址
物理地址
Page
页表
MMU
TLB
Buddy System
SLAB / SLUB
malloc
brk / mmap
Page Fault
Page Cache

整个Linux内存管理体系可以先简单理解成下面几层:

复制代码
应用程序
    ↓
虚拟地址空间
    ↓
页表 / MMU / TLB
    ↓
物理页 Page
    ↓
伙伴系统
    ↓
物理内存 DDR

一、虚拟内存:为什么进程看到的不是物理地址?

1.1为什么不直接使用物理地址而引入虚拟地址?

主要还是基于安全性考虑:

安全性:使用虚拟地址可以使进程无法直接接触物理内存,各个进程拥有独立的地址空间。若出现野指针或越界访问,只影响当前进程(触发 Segmentation Fault),不会破坏其他进程或内核的数据。

解决物理碎片:连续的虚拟地址可以映射到散落、不连续的物理页框上,无需为了寻找连续的大块物理内存而频繁搬移数据。

1.2虚拟地址如何变为物理地址?

虚拟地址转换为物理地址的核心机制,是利用 MMU(内存管理单元) 硬件,结合多级页表(Page Table) 和 TLB 硬件缓存。

页是什么?

页是内存分割和分配的最小单位,一般是4KB;

页表是什么?

页表是记录"虚拟页与物理页映射关系"的表,页表除了表明虚拟页(虚拟地址)和物理页(物理地址)的映射关系,还包括一些控制位:

  • R/W Bit(读写位): 标记该页是只读还是可读写。

  • User/Supervisor Bit(权限位): 标记该页允许用户态访问还是仅内核态可访问。

  • Dirty Bit(脏位): 标记该物理页中的数据是否被修改过(用于换页刷盘)。

TLB是什么?

TLB 是置于 CPU 内部、用来加速查找这份通讯录的硬件高速缓存

由于多级页表保存在物理内存中,如果在 4 级页表下,CPU 每发起一次内存读写,都需要先访问 4 次内存查页表,才能得到真实的物理地址,最后再读写 1 次数据------这会导致内存访问速度降低 80%。为了解决这个性能瓶颈,CPU 内部的 MMU(内存管理单元)引入了 TLB。

总的来说虚拟地址转换为物理地址的过程如下图所示:

bash 复制代码
# 查看进程 PID=1234 的地址空间映射
cat /proc/1234/maps

二、物理内存:如何管理物理内存?

理解了虚拟地址之后,下一个问题就是:

页表最终映射到的物理页,是谁负责分配的?

Linux 最主要的物理页分配器之一就是:

复制代码
Buddy System
伙伴系统

2.1伙伴系统

伙伴系统(Buddy System)是 Linux 内核用于管理大块连续物理内存的核心分配算法,其主要目标是在高效响应内存申请的同时,最大程度地解决物理内存的外部碎片(External Fragmentation)问题。

2.1.1伙伴系统组织结构

伙伴系统将物理内存中的空闲页框按 (n称为 Order ,通常范围为 )的页数大小进行分组,维护在不同阶(Order)的双向空闲链表中:

  • Order 0 :单个页框( 页,即 )

  • Order 1: 个连续页框()

  • Order 2 : 个连续页框()

  • ...

  • Order 10 : 个连续页框()

所以伙伴系统是维护了11个链表。

2.1.2伙伴系统分配与回收内存

分配内存过程:

例如申请 3 页内存,系统会计算出需要分配 页(Order 2)。

  • 检索对应链表:查看 Order 2 的空闲链表:

    • 若链表非空,直接摘下一块分配给调用者。

    • 若链表为空,向上检索更高阶的链表(如 Order 3,即 8页的链表)。

  • 对半分裂(Split) :若在 Order 3 找到一个空闲块,系统将其对半劈开,拆分为两个各占 4 页(Order 2)的内存块。这两个大小相等、物理地址相邻的内存块互称为"伙伴(Buddy)"。

  • 交付与留存:将其中一个 Order 2 块分配给申请者,另一个块挂入 Order 2 的空闲链表中备用。

回收内存过程:

  • 检查伙伴状态:

    • 若伙伴正在被使用或处于其他 Order:无法合并,直接将当前释放块挂入对应 Order 的空闲链表。

    • 若伙伴也处于空闲状态 :将伙伴从当前 Order 链表中摘除,与当前释放块合并 为一个更大的 内存块(进入更高一阶的 Order)。

  • 递归向上合并:合并生成新的大块后,系统会继续检查新大块的伙伴是否空闲,触发连锁合并,直到伙伴不空闲或达到最大 Order 限制(Order 10)为止。

2.2SLAB

SLAB 分配器(Slab Allocator)是建立在伙伴系统(Buddy System)之上的内核小内存分配机制,其核心作用是高效管理小于 1 页(4KB)的小内存对象,解决伙伴系统的内部碎片问题,并实现内核数据结构的快速缓存与复用。

2.1SLAB的作用

1. 消除内部碎片(Internal Fragmentation)
  • 问题 :伙伴系统的最小分配粒度是 1 个物理页(通常为 4KB)。若内核需要创建一个几十字节的结构体(如 128 字节的 struct inode),直接找伙伴系统要 1 页,就会浪费掉 90% 以上的物理内存。

  • 解决:SLAB 先向伙伴系统"批发"一页或多页连续物理内存,然后像切蛋糕一样将其划分为成百上千个固定大小的小对象(Objects)。当内核申请小内存时,SLAB 直接从对象池中划拨一个小块,做到精细化按需分配。

2. 内核对象的缓存与高效复用(Object Caching)
  • 原理 :内核中很多核心数据结构(如进程描述符 task_struct、文件节点 inode、目录项 dentry)会被极其频繁地创建和销毁。

  • 优化 :SLAB 引入了"对象池"的思想。当一个对象被销毁时,SLAB 并不把物理内存归还给伙伴系统,也不清理其内部结构,而是将其标记为"空闲"并挂入链表缓存起来。下次内核再申请同类对象时,直接取出使用,省去了重新申请内存和初始化结构体的巨大 CPU 开销。

消除内部碎片的意思是,比如说我需要10字节空间,我kmalloc,但分配内存至少是4KB,所以哪怕我申请10字节,也会分配4KB,虽然只使用10字节,但剩余的空间别人也用不了。

kmalloc一般是通过slab申请物理内存的。

三、用户空间:malloc

前面介绍的是 Linux 内核如何管理物理内存。

但普通应用程序使用内存时,通常写的是:

复制代码
malloc()

malloc 是C库函数,并不是系统调用,malloc的执行过程如下:

malloc 的执行过程可以精简概括为以下 4 个关键步骤:

  1. 用户态缓存查找 (ptmalloc)

    优先在用户态虚拟地址的堆中寻找符合大小的空闲内存块。如果找到直接返回指针,无需陷入内核(极快)。

  2. 按申请尺寸选择系统调用

    若缓存池不足,分配器向内核申请扩展虚拟内存(注意:此时仍未分配真正的物理内存):

    • 小内存(< 128KB) :调用 **brk()**抬高堆顶指针扩大堆空间。

    • 大内存(≥ 128KB) :调用 **mmap()**在内存映射区创建独立的匿名映射。

  3. 内核建立虚拟映射 (VMA)

    内核验证申请合法后,在进程的虚拟地址空间中创建/扩展 VMA(虚拟内存区域) 节点,并将对应的虚拟起始地址直接返回给程序,内核此时都没有划拨任何实际物理内存(RAM)。

  4. 首次读写与物理分配 (缺页中断)

    程序首次访问该虚拟地址时,CPU 发现未建立物理映射,触发缺页中断 (Page Fault) 。内核捕获中断,调用伙伴系统分配真实的物理页框,更新页表与 TLB 后,恢复程序正常运行。

问:malloc 分配的内存一定在堆上吗?

不一定。申请小内存使用brk()在堆上,大内存使用mmap不在堆上。

传统意义上的 heap 一般通过 brk()向上扩展,所以对于比较小的内存申请,malloc 通常会通过brk()从已有 heap 中切分。

但是较大的内存申请可能直接使用 mmap()建立一块新的匿名映射,就不在堆上。

问:malloc 100 MB 会立即占用 100 MB 物理内存吗?

不会。

只有在使用阶段才会划分物理内存:

  • 第一次读写内存: 当程序拿到了 malloc 返回的指针,并第一次对其发起读写操作(例如写入数据 ptr0 = 'A' )时,CPU 的 MMU 在翻译虚拟地址时发现页表项为空(未绑定物理内存)。

  • 缺页中断处理: CPU 立即触发 Page Fault(缺页中断) 陷入内核态。内核接管后,确认该访问属于合法的 VMA,才调用伙伴系统(Buddy System)划拨一个真实的物理页(通常为 4 KB),填入页表并刷新 TLB,随后恢复程序运行。

四、其他

4.1文件页缓存Page Cache

4.1.1什么是文件页缓存?

文件页缓存(Page Cache)是 Linux 内核利用空闲物理内存来暂存磁盘文件数据的一种机制。它的核心目的是弥合极速的内存与极其缓慢的磁盘之间的巨大性能鸿沟。

注意: Page Cache与CPU的Cache不一样,CPU Cache是CPU内部的SRAM,而Page Cache 本质上就是物理内存DDR(RAM)的一部分。它是 Linux 内核将"暂时未被进程真正占用的物理内存"利用起来,用来缓存磁盘数据的动态内存池。

4.1.2为什么需要文件页缓存?

因为DDR访问速度远高于磁盘/SSD/eMMC等

假设第一次读取:

复制代码
test.txt

Linux 将文件内容读入Page Cache,如果之后再次读取同样的数据:

复制代码
read(test.txt)

Linux 发现:

复制代码
Page Cache Hit

就可以直接从内存读取。

无需再次访问磁盘。

因此 Page Cache 的核心作用就是:

利用空闲内存缓存文件内容,减少慢速存储设备访问。

4.2 Dirty Page 和 sync

如果程序执行:

cpp 复制代码
write(fd, buf, size);

很多情况下数据不会立刻写入磁盘,而是先写入Page Cache或DDR,此时内存中的文件内容已经发生变化,但是磁盘中的文件还没有同步更新。

这样的 Page 称为:

cpp 复制代码
Dirty Page 脏页

Linux 会在合适的时候把这些数据:

复制代码
Page Cache
    ↓
SSD / eMMC / Disk

写回存储设备,执行:

复制代码
sync

就是要求 Linux 尽快把脏数据写回存储设备。

sync 的核心作用是强制将内存中尚未物理写入磁盘的"脏页"(Dirty Pages)数据,立刻全局同步到物理存储设备上。

相关推荐
吴声子夜歌1 小时前
Nginx应用与运维——Nginx Web服务应用实战(静态文件服务器的搭建)
运维·前端·nginx
Lucis__1 小时前
编译链接全链路解析:ELF形成到可执行加载的发展历程
linux·程序·elf文件
海宇服务1 小时前
零信任架构实战:基于海宇身份证OCR构建自动化自助终端核验网关
运维·人工智能·架构·自动化
开开心心就好1 小时前
图片白底怎么去掉?抠图工具抠完背景透明
java·服务器·开发语言·pdf·ocr·散列表·启发式算法
ken22322 小时前
ubuntu 24.04, audacity 4 AppImage 加载 libavdevice.so.62 /.61 /.63 失败:依赖缺失?
linux·运维·ubuntu
NightReader2 小时前
vCenter8部署
linux
未完成的歌~2 小时前
Windows 系统镜像备份还原 操作手册
运维
高山有多高2 小时前
【Linux笔记】线程概念与控制
linux
阳光九叶草LXGZXJ2 小时前
达梦数据库-学习-69-DM9主备集群部署
linux·运维·服务器·数据库·sql·学习