心宇

FDF24DCED8C6738F8313A6D3B073C3F4

首页 管理

Linux 进程内存模型与虚拟内存、物理内存页映射

本笔记从程序的编译、加载、运行开始,系统总结 Linux 进程内存布局,并进一步解释虚拟地址如何通过 MMU、TLB 和多级页表映射到物理内存,以及缺页异常、按需分页、写时复制、Swap 和物理页管理的工作机制。

说明:32 位部分主要采用经典 x86 Linux 教学模型;64 位页表部分以常见 x86-64、4 KiB 页面为例。ARM64 的基本原理相同,但页表级数、页面大小、寄存器和页表项格式存在差异。


第一部分:程序从源代码到进程

一、程序、可执行文件与进程

首先区分三个概念:

  • 程序源代码:程序员编写的 C/C++ 等代码。
  • 可执行文件:编译、汇编、链接后生成的文件,例如 ELF。
  • 进程:程序被操作系统执行时形成的运行实体。

完整过程:

C/C++ 源代码
      |
      v
编译 Compiler
      |
      v
汇编 Assembly
      |
      v
目标文件 Object File
      |
      v
链接 Linker
      |
      v
ELF 可执行文件
      |
      v
execve() 加载程序
      |
      v
建立进程虚拟地址空间
      |
      v
CPU 开始执行

例如:

int global_a = 100;
int global_b;

int main(void)
{
    int a = 10;
    return 0;
}

编译:

gcc test.c -o test

执行:

./test

执行后,操作系统为程序建立运行环境,程序的代码、数据、堆、栈等通过虚拟地址空间组织。

需要注意:可执行文件不等于进程。一个可执行文件可以同时运行多个进程实例。

二、ELF 文件并不是整体复制到物理内存

ELF 文件可能包含:

  • 机器指令
  • 只读数据
  • 已初始化数据
  • 未初始化数据的描述
  • 动态链接信息
  • 符号表
  • 重定位信息

常见 Section:

Section 主要作用
.text 机器指令
.rodata 只读数据
.data 已初始化的可写静态数据
.bss 零初始化或未显式初始化的静态数据
.symtab 符号表
.strtab 字符串表
.dynamic 动态链接信息

ELF 还有 Program Header。

必须区分:

ELF 文件
  |
  +-- Section:描述文件中的逻辑内容
  |
  +-- Segment:描述程序加载时的映射单元
                     |
                     v
                Linux Loader
                     |
                     v
             虚拟内存区域 VMA
                     |
                     v
              按需建立物理页映射

Linux 加载 ELF 时主要依据 Program Header 中的可加载 Segment,而不是简单地为每一个 Section 建立独立内存区域。

例如 PT_LOAD 描述的内容可能映射为可执行、只读或可写的虚拟内存区域。

重点:程序加载并不意味着所有代码和数据立即进入物理 RAM。

很多页面可以在首次访问时才加载。


第二部分:Linux 进程虚拟地址空间

三、虚拟地址、逻辑地址与物理地址

现代 Linux 用户程序通常使用虚拟地址(Virtual Address),而不是直接使用物理地址。

三个概念:

  1. 逻辑地址(Logical Address):程序执行过程中使用的地址概念。
  2. 线性地址(Linear Address):在 x86 架构中,经过分段机制后得到的地址。
  3. 物理地址(Physical Address):最终用于访问物理内存或其他物理地址空间资源的地址。

在现代 x86-64 Linux 的常见平坦内存模型中,逻辑地址经过分段处理后通常与线性地址数值一致,因此日常讨论经常将逻辑地址与虚拟地址近似使用。

但严格来说:

x86 逻辑地址
      |
      v
分段机制 Segmentation
      |
      v
线性地址 Linear Address
      |
      v
分页机制 Paging
      |
      v
物理地址 Physical Address

ARM64 通常不采用传统 x86 式分段机制,其虚拟地址直接经过地址转换机制形成物理地址。

因此,本笔记后续统一使用“虚拟地址”表示程序访问内存时使用的地址。

四、为什么需要虚拟内存

假设计算机有 8 GiB 物理内存。

运行三个进程:

Process A
Process B
Process C

如果所有程序直接使用物理地址,会出现:

  • 不同进程容易发生地址冲突。
  • 一个进程可能破坏另一个进程的数据。
  • 程序必须考虑实际物理内存布局。
  • 内存分配与回收复杂。
  • 难以实现按需加载、共享内存等机制。

引入虚拟内存后:

Process A 虚拟空间 ──┐
                     |
Process B 虚拟空间 ──┼──> 页表映射 ──> 物理 RAM
                     |
Process C 虚拟空间 ──┘

每个进程拥有独立的用户态虚拟地址空间。

例如:

Process A
虚拟地址 0x400000
      |
      v
物理地址 0x120000

Process B
虚拟地址 0x400000
      |
      v
物理地址 0x350000

虽然两个进程使用相同虚拟地址,但可以映射到不同物理页面。

虚拟内存带来的核心能力:

  1. 进程隔离。
  2. 地址空间抽象。
  3. 按需分配物理内存。
  4. 文件映射。
  5. 共享物理页面。
  6. 内存访问权限保护。
  7. 写时复制。
  8. 支持交换和内存回收。

需要强调:虚拟地址空间很大,不代表机器真的有同等容量的 RAM。

五、32 位 Linux 经典地址空间

32 位地址理论上可以表示:

2^32 Bytes = 4 GiB

经典 32 位 x86 Linux 教学模型:

0xFFFFFFFF
+------------------------+
|                        |
| Kernel Virtual Space   |
|                        |
|         1 GiB          |
|                        |
+------------------------+ 0xC0000000
|                        |
| User Virtual Space     |
|                        |
|         3 GiB          |
|                        |
+------------------------+
0x00000000

即:

用户虚拟空间:3 GiB
内核虚拟空间:1 GiB

但是必须注意:

  • 3G/1G 是经典配置,不是所有 Linux 的固定规则。
  • 还存在 2G/2G 等划分方式。
  • 这里划分的是虚拟地址范围,不是物理 RAM。
  • 不代表内核独占 1 GiB 物理内存。
  • 64 位 Linux 不应使用该固定模型解释。

六、64 位 Linux 地址空间

64 位地址理论上可表示:

2^64 Bytes

但 CPU 不一定实现全部 64 位虚拟地址位。

例如 x86-64 常见:

  • 4 级页表:通常使用 48 位规范虚拟地址。
  • 5 级页表:通常使用 57 位规范虚拟地址。

CPU 实现的物理地址位数也可能小于 64 位。

因此:

虚拟地址宽度、物理地址宽度和 CPU 通用寄存器宽度不是同一个概念。

Linux 内核会根据架构、硬件能力及配置管理用户和内核虚拟地址空间。


第三部分:进程内存区域

七、经典进程内存布局

              高虚拟地址
                   |
+--------------------------------+
| argv / env / auxv              |
+--------------------------------+
| Stack                          |
|                                |
| 向低地址增长                   |
|             ↓                  |
+--------------------------------+
| mmap                           |
| Shared Libraries               |
| File Mapping                   |
| Anonymous Mapping              |
+--------------------------------+
|             ↑                  |
| Heap                           |
| 向高地址增长                   |
+--------------------------------+
| .bss                           |
+--------------------------------+
| .data                          |
+--------------------------------+
| .rodata                        |
+--------------------------------+
| .text                          |
+--------------------------------+
                   |
              低虚拟地址

这只是经典示意图。

现代 Linux 还受到以下因素影响:

  • ASLR
  • PIE
  • mmap
  • 动态链接
  • 多线程
  • vDSO
  • vvar
  • CPU 架构
  • 内核配置

真实进程布局并不一定严格按照图中顺序排列。

八、Text / Code

Text 区域主要保存程序机器指令。

例如:

int add(int a, int b)
{
    return a + b;
}

编译后:

C 代码
   |
   v
汇编指令
   |
   v
机器指令
   |
   v
.text

运行时通常具有:

r-x

权限:

  • r:可读。
  • w:可写。
  • x:可执行。

代码区域通常不允许普通写入。

从分页角度看,代码所在虚拟页面可以映射到具有执行权限的物理页框。

多个运行同一程序的进程可以共享相同的只读代码物理页面。

九、CPU 如何执行代码

CPU 使用程序计数器保存指令执行位置。

常见名称:

架构 寄存器
x86 EIP
x86-64 RIP
ARM64 PC

简化执行过程:

PC / RIP
   |
   v
指令虚拟地址
   |
   v
地址转换 MMU / TLB
   |
   v
物理地址
   |
   v
Cache / 内存层次
   |
   v
Fetch
   |
   v
Decode
   |
   v
Execute

程序计数器保存的是程序执行所需的指令地址,不是简单地保存一个固定物理 RAM 地址。

实际 CPU 流水线、缓存和地址转换过程可能并行或重叠执行。

十、rodata:只读数据

.rodata 通常保存:

  • 字符串字面量。
  • 某些 const 对象。
  • 编译器生成的只读表。

例如:

void test(void)
{
    char *str = "hello";
}

概念上:

Stack / Register
+------------------+
| str              |
|   |              |
+---|--------------+
    |
    v
只读映射区域
+------------------+
| h e l l o \0     |
+------------------+

需要区分:

  • str 是指针变量。
  • "hello" 是字符串字面量。

两者是不同对象。

对字符串字面量进行修改属于未定义行为。

十一、Data Segment

.data 通常保存已经显式初始化、需要可写存储的静态存储期对象。

例如:

int global_a = 100;
static int global_b = 200;

void test(void)
{
    static int count = 10;
}

通常:

global_a
global_b
count
    |
    v
.data

这些变量具有静态存储期。

即使 static 局部变量写在函数内部,它也不会随着函数返回而销毁。

十二、BSS

.bss 通常保存:

  • 未显式初始化的全局变量。
  • 零初始化的全局变量。
  • 未显式初始化的 static 变量。
  • 零初始化的 static 变量。

例如:

int global_a;
static int global_b;

void test(void)
{
    static int count;
}

这些对象在程序启动时被零初始化。

为什么需要 BSS

例如:

char buffer[100 * 1024 * 1024];

这是一个 100 MiB 的全局数组。

如果 ELF 文件直接保存 100 MiB 的零,会浪费磁盘空间。

因此:

ELF 文件
  |
  +-- 描述需要 100 MiB 零初始化空间
              |
              v
        运行时建立虚拟映射
              |
              v
        访问时提供零初始化页面

BSS 占用运行时地址空间,但不需要在 ELF 文件中保存同等大小的零数据。

后续分页机制会进一步解释:BSS 对应的物理页面可以按需分配。

十三、const 不等于一定在 rodata

例如:

void test(void)
{
    const int a = 10;
}

编译器可能:

  • 将 a 放入寄存器。
  • 将 a 放入栈。
  • 将 a 直接优化掉。

而:

static const int table[] = {1, 2, 3};

更可能位于只读映射区域。

所以:

const 是语言层面的修改限制,不等于固定的 ELF 存储位置。

十四、Heap

Heap 主要用于动态内存分配。

C 语言:

malloc()
calloc()
realloc()
free()

C++:

new
delete

例如:

int *p = malloc(1024);

概念上:

Stack
+-------------+
| p           |
+------|------+
       |
       v
动态内存区域
+------------------+
| 1024 Bytes       |
+------------------+

p 是指针变量,malloc 返回的是动态分配区域的地址。

二者存储位置不一定相同。

Heap 的增长

传统模型中 Heap 向高虚拟地址增长。

低地址
.text
.data
.bss
Heap
  ↑
  ↑
高地址

Linux 可以通过 brk/sbrk 调整 program break。

但现代 malloc 实现也可能通过 mmap 获取内存。

因此:

malloc 分配的对象不一定全部来自传统 brk Heap。

十五、Memory Mapping Area

mmap 区域可以用于:

  • 共享库映射。
  • 文件映射。
  • 匿名内存映射。
  • 共享内存。
  • 大块动态内存分配。

例如:

Process
  |
  +-- executable
  +-- heap
  +-- libc.so
  +-- libm.so
  +-- anonymous mmap
  +-- stack

mmap 的重要特点是建立虚拟地址区域与文件或匿名内存对象之间的关系。

它不意味着所有页面已经立即装入物理内存。

十六、Stack

Stack 主要与以下内容有关:

  • 函数调用。
  • 部分局部变量。
  • 参数传递。
  • 返回地址。
  • 保存寄存器状态。

经典栈通常向低虚拟地址增长。

高地址
+------------------+
| Stack            |
|       ↓          |
|       ↓          |
+------------------+

        ↑
        ↑
       Heap

低地址

实际栈布局由架构、ABI、编译器及操作系统共同决定。

十七、Stack Frame

函数调用时,程序通常需要保存调用状态。

这部分逻辑结构称为 Stack Frame(栈帧)。

例如:

int add(int a, int b)
{
    int result = a + b;
    return result;
}

int main(void)
{
    int x = add(10, 20);
    return 0;
}

概念上:

高地址
+--------------------+
| main Stack Frame   |
+--------------------+
| add Stack Frame    |
| 参数 / 局部状态    |
| 返回地址等         |
+--------------------+
低地址

add 返回后,其调用帧通常不再需要。

递归函数

func(3)
   |
   v
func(2)
   |
   v
func(1)

同一个函数可以同时存在多个调用帧。

不能理解成“每个函数永远只有一个栈帧”。

编译器优化

开启优化:

gcc -O2 test.c -o test

编译器可能:

  • 将变量放进寄存器。
  • 内联函数。
  • 消除变量。
  • 省略帧指针。
  • 完全消除某些函数调用。

因此普通局部变量不一定实际位于栈中。

十八、参数和环境变量

Linux 程序启动时,初始栈中通常包含:

  • argc
  • argv
  • envp
  • Auxiliary Vector(auxv)

例如:

./test hello world

对应:

argc = 3

argv[0] = "./test"
argv[1] = "hello"
argv[2] = "world"

环境变量例如:

PATH=/usr/bin:/bin
HOME=/home/user
LANG=zh_CN.UTF-8

十九、ASLR

ASLR:

Address Space Layout Randomization。

地址空间布局随机化。

可能随机化:

  • Stack。
  • Heap。
  • mmap。
  • Shared Libraries。
  • PIE Executable。

目的之一是增加内存漏洞利用的难度。

同一个程序多次运行时,部分虚拟地址可能发生变化。

需要区分:

  • ASLR 改变虚拟地址空间的布局。
  • 页表负责虚拟地址到物理地址的映射。

二者属于不同层面的机制。

二十、指针变量与内存位置

例如:

int a = 10;
int *p = &a;

如果 a、p 都是普通局部变量:

p:Stack / Register
a:Stack / Register

再例如:

char *p = "hello";

通常:

p:Stack / Register
"hello":只读映射区域

再例如:

char *p = malloc(100);

通常:

p:Stack / Register
p 指向的数据:Heap / mmap 动态内存

最重要的规律:

指针变量存在哪里,与指针指向的数据存在哪里,是两个不同问题。


第四部分:虚拟内存如何映射物理内存

二十一、前面的内存模型到底描述了什么

前面看到:

.text
.rodata
.data
.bss
Heap
mmap
Stack

这些区域主要描述进程的虚拟地址空间布局。

并不是物理 RAM 中存在固定排列的:

物理 Text 区
物理 Data 区
物理 Heap 区
物理 Stack 区

实际情况是:

进程虚拟地址空间
+------------------+
| Stack            |
+------------------+
| mmap             |
+------------------+
| Heap             |
+------------------+
| BSS              |
+------------------+
| Data             |
+------------------+
| Text             |
+------------------+
        |
        v
     页表映射
        |
        v
物理 RAM
+------------------+
| Page Frame 20    |
+------------------+
| Page Frame 3     |
+------------------+
| Page Frame 91    |
+------------------+
| Page Frame 7     |
+------------------+

虚拟地址连续,并不意味着对应物理地址连续。

这是理解分页机制最关键的前提。

二十二、Page 与 Page Frame

分页系统中有两个核心概念。

Page:虚拟页

Page 是虚拟地址空间按照固定粒度划分的页面。

例如使用 4 KiB 页面:

虚拟地址空间

Page 0:0x0000 ~ 0x0FFF
Page 1:0x1000 ~ 0x1FFF
Page 2:0x2000 ~ 0x2FFF
Page 3:0x3000 ~ 0x3FFF

每页大小:

4 KiB = 4096 Bytes = 2^12 Bytes

Page Frame:物理页框

物理内存也按照页面粒度划分。

例如:

物理 RAM

Frame 0
Frame 1
Frame 2
Frame 3
...

虚拟页可以映射到物理页框。

Virtual Page 0 ---> Physical Frame 7
Virtual Page 1 ---> Physical Frame 2
Virtual Page 2 ---> Physical Frame 9
Virtual Page 3 ---> Physical Frame 1

虚拟页号与物理页框号不必相同。

物理页框也不必连续。

注意:4 KiB 是常见页面大小,不是所有架构和配置的唯一选择。

二十三、为什么分页能解决物理内存碎片问题

假设一个进程需要 12 KiB 内存。

页面大小为 4 KiB。

则需要:

12 KiB / 4 KiB = 3 Pages

虚拟地址空间:

+------------+
| Page 0     |
+------------+
| Page 1     |
+------------+
| Page 2     |
+------------+

物理内存可能是:

Frame 0:其他进程
Frame 1:当前进程 Page 2
Frame 2:空闲
Frame 3:当前进程 Page 0
Frame 4:其他进程
Frame 5:当前进程 Page 1

映射关系:

Page 0 ---> Frame 3
Page 1 ---> Frame 5
Page 2 ---> Frame 1

进程看到连续的 12 KiB 虚拟空间。

物理 RAM 中却不需要连续的 12 KiB 空间。

分页减少了连续物理内存分配的需求,但仍可能存在页内碎片,且大页或某些设备分配仍可能要求连续物理页框。

二十四、页表 Page Table

页表用于描述虚拟页面如何映射到物理页框。

概念上:

虚拟页号 VPN 物理页框号 PFN 状态
0 7 有效
1 2 有效
2 9 有效
3 — 未映射

VPN:

Virtual Page Number。

PFN:

Page Frame Number。

页表项(PTE)不仅包含映射信息,还可能包含:

  • Present:页面是否存在于当前硬件映射中。
  • Read/Write:写权限。
  • User/Supervisor:用户态访问权限。
  • Execute Disable / NX:禁止执行。
  • Accessed:访问标志。
  • Dirty:修改标志。
  • 物理页框地址信息。

具体位定义依 CPU 架构而异。

二十五、MMU

MMU:

Memory Management Unit。

内存管理单元。

负责执行硬件地址转换及权限检查等工作。

简化过程:

CPU
 |
 | 产生虚拟地址
 v
MMU
 |
 | 查询 TLB / 页表
 v
物理地址
 |
 v
Cache / RAM / 内存系统

必须区分:

  • 操作系统负责建立和维护页表等内存管理结构。
  • MMU 根据当前页表配置进行硬件地址转换。
  • TLB 用于缓存地址转换结果。

不是操作系统在每一次普通内存访问时都亲自计算物理地址。

二十六、虚拟地址如何拆分

以 32 位虚拟地址、4 KiB 页面为例。

页面大小:

4 KiB = 2^12 Bytes

因此页内偏移需要 12 位。

剩余:

32 - 12 = 20 位

用于表示虚拟页号。

32-bit Virtual Address

+--------------------+------------+
| Virtual Page Number| Page Offset|
|       20 bits      |  12 bits   |
+--------------------+------------+

地址转换时:

  • 虚拟页号用于查找物理页框。
  • 页内偏移保持不变。

公式:

Virtual Address = VPN × PageSize + Offset

Physical Address = PFN × PageSize + Offset

二十七、地址转换计算实例

假设:

页面大小:4 KiB

虚拟地址:0x00001234

由于:

4 KiB = 0x1000

所以:

VPN = 0x00001234 >> 12
    = 0x1

Offset = 0x234

假设页表中:

VPN 0x1 ---> PFN 0x8

那么:

Physical Address
= 0x8 × 0x1000 + 0x234
= 0x8234

完整过程:

Virtual Address
0x00001234
     |
     +-- VPN = 0x1
     |
     +-- Offset = 0x234
                |
                v
          查询页表
                |
                v
          PFN = 0x8
                |
                v
Physical Address
0x00008234

重点:页表改变的是页框映射,页内偏移保持不变。

二十八、虚拟地址连续,物理地址可以不连续

例如:

虚拟地址空间

0x1000 ~ 0x1FFF ---> Frame 8
0x2000 ~ 0x2FFF ---> Frame 3
0x3000 ~ 0x3FFF ---> Frame 15

虚拟地址:

0x1FFF
0x2000

彼此连续。

但对应物理地址可能是:

0x8FFF
0x3000

完全不连续。

CPU 和程序通过虚拟地址工作,不需要知道实际物理页框的排列顺序。


第五部分:多级页表

二十九、为什么不能简单使用一个巨大页表

假设:

  • 32 位虚拟地址。
  • 4 KiB 页面。
  • 每个页表项 4 Bytes。

虚拟页面数量:

2^32 / 2^12 = 2^20

即:

1,048,576 个虚拟页面

如果采用单级、完整数组式页表:

2^20 × 4 Bytes = 4 MiB

每个进程都可能需要约 4 MiB 的完整页表空间。

大量进程会造成浪费。

因此需要多级页表。

三十、经典 x86 32 位二级页表

在传统 32 位 x86 非 PAE 分页模型中:

32-bit Virtual Address

+----------+----------+------------+
| 10 bits  | 10 bits  | 12 bits    |
+----------+----------+------------+
    |          |            |
    |          |            +-- 页内偏移
    |          +--------------- 页表索引
    +-------------------------- 页目录索引

转换过程:

虚拟地址
   |
   v
Page Directory
   |
   v
Page Table
   |
   v
Physical Frame
   |
   v
加上 Page Offset
   |
   v
物理地址

如果某个虚拟地址范围完全没有映射,可以不创建对应的下级页表。

这就是多级页表节省空间的重要原因。

三十一、x86-64 四级页表

以常见的 48 位规范虚拟地址、4 KiB 页面为例。

地址划分:

48-bit Virtual Address

+-------+-------+-------+-------+------------+
| PML4  | PDPT  | PD    | PT    | Offset     |
| 9 bit | 9 bit | 9 bit | 9 bit | 12 bit     |
+-------+-------+-------+-------+------------+

转换过程:

Virtual Address
       |
       v
PML4
       |
       v
PDPT
       |
       v
Page Directory
       |
       v
Page Table
       |
       v
Page Frame
       |
       v
Physical Address

每级索引 9 位:

2^9 = 512

每个页表通常包含 512 个 8 字节表项:

512 × 8 = 4096 Bytes

正好为 4 KiB。

x86-64 的 48 位规范地址还要求高位满足规范地址的符号扩展规则,不能把任意 64 位数都当作合法虚拟地址。

三十二、x86-64 五级页表

部分 x86-64 CPU 支持 LA57,即五级分页。

概念上:

Virtual Address
       |
       v
PML5
       |
       v
PML4
       |
       v
PDPT
       |
       v
PD
       |
       v
PT
       |
       v
Physical Frame

在 4 KiB 页面配置下,通常支持 57 位规范虚拟地址。

是否启用取决于 CPU、Linux 内核及启动配置。

三十三、页表本身存在哪里

页表不是抽象的数学公式。

页表结构本身也需要占用物理内存。

物理 RAM
+---------------------+
| 普通数据页面        |
+---------------------+
| 代码页面            |
+---------------------+
| 页表页面            |
+---------------------+
| 内核数据页面        |
+---------------------+

在 x86-64 上,CR3 寄存器包含当前地址转换所使用的顶级页表基址等相关控制信息。

CPU 根据该信息进行页表遍历。

ARM64 则使用 TTBR0_EL1、TTBR1_EL1 等寄存器配置不同地址范围的转换表基址。

三十四、进程切换时页表如何变化

假设:

Process A ---> Page Table A
Process B ---> Page Table B

当操作系统进行进程上下文切换时,如果切换了地址空间,就需要让 CPU 使用相应的地址转换上下文。

Process A
   |
   v
Page Table A
   |
   v
CPU 执行 A

发生上下文切换

Process B
   |
   v
Page Table B
   |
   v
CPU 执行 B

现代 CPU 可能使用 PCID、ASID 等机制区分地址空间,避免每次切换都完全清空 TLB。

因此不能简单认为每次进程切换都必须刷新所有 TLB 项。


第六部分:TLB 与地址转换性能

三十五、为什么需要 TLB

如果 CPU 每次访问内存都要进行完整的四级页表遍历,会产生额外开销。

因此 CPU 使用:

TLB(Translation Lookaside Buffer)。

它是一种用于缓存地址转换信息的硬件结构。

CPU 产生虚拟地址
        |
        v
      TLB
        |
   +----+----+
   |         |
  Hit       Miss
   |         |
   v         v
使用缓存   页表遍历
   |         |
   |         v
   |      得到映射
   |         |
   +----+----+
        |
        v
物理地址

TLB Hit

TLB 命中。

可以直接使用缓存的转换结果,并进行相应权限检查。

TLB Miss

TLB 未命中。

硬件通常需要遍历页表。

如果映射有效,获取转换结果并可能填入 TLB。

注意:

TLB Miss 不等于 Page Fault。

TLB Miss 只说明转换缓存未命中。

Page Fault 则表示地址转换或访问权限检查遇到了需要异常处理的情况。

三十六、TLB 与 CPU Cache 的区别

对比 TLB CPU Cache
主要缓存 地址转换信息 指令或数据
解决问题 加快虚拟地址转换 减少较慢内存访问
典型未命中处理 页表遍历 访问下级缓存或内存
是否直接存储普通变量内容 通常不是 是

因此:

TLB:帮助找到地址
Cache:帮助快速取得数据

这是概念性区分,真实 CPU 的缓存层次和地址转换流水线更加复杂。


第七部分:缺页异常与按需分页

三十七、Page Fault 是什么

Page Fault:

缺页异常。

当 CPU 访问某个虚拟地址,而当前页表状态无法直接完成合法访问时,可能产生缺页异常。

原因可能包括:

  • 虚拟页尚未分配物理页框。
  • 文件映射页面尚未载入。
  • 页面被换出。
  • 访问违反页面权限。
  • 写入触发 COW。

因此:

Page Fault 不一定代表程序错误。

它可能是 Linux 正常内存管理流程的一部分。

三十八、按需分页 Demand Paging

假设程序:

#include <stdlib.h>

int main(void)
{
    char *p = malloc(100 * 1024 * 1024);

    if (p == NULL)
        return 1;

    p[0] = 1;

    free(p);
    return 0;
}

申请 100 MiB 动态内存,不意味着立刻消耗 100 MiB 的新物理 RAM。

可能发生:

malloc(100 MiB)
       |
       v
分配器取得或管理虚拟地址区域
       |
       v
尚未为所有页面分配物理页框
       |
       v
访问 p[0]
       |
       v
触发缺页异常
       |
       v
Linux 分配或建立物理页映射
       |
       v
程序继续执行

实际行为还取决于分配器、已有内存、内核配置及页面状态。

三十九、缺页异常处理流程

CPU 访问虚拟地址
        |
        v
页表转换失败或权限异常
        |
        v
Page Fault Exception
        |
        v
进入 Linux 内核
        |
        v
检查地址所属 VMA
        |
        +-- 地址非法
        |      |
        |      v
        |   产生相应错误
        |   用户进程可能收到 SIGSEGV
        |
        +-- 地址合法
               |
               v
        检查缺页原因
               |
               +-- 匿名页面
               |      |
               |      v
               |   分配/映射零页或新页面
               |
               +-- 文件映射
               |      |
               |      v
               |   查找页缓存/读取文件
               |
               +-- COW
               |      |
               |      v
               |   建立私有可写副本
               |
               +-- Swap
                      |
                      v
                   换入页面
               |
               v
          更新相关页表
               |
               v
          重新执行故障指令

上述是典型情况的简化模型。某些错误也可能产生 SIGBUS 等其他结果。

四十、Minor Fault 与 Major Fault

Linux 通常区分:

Minor Page Fault

处理缺页时不需要为该页面执行磁盘 I/O。

例如:

  • 分配新的匿名页面。
  • 页面已经存在于页缓存。
  • 某些写时复制操作。

Major Page Fault

处理缺页时需要执行相应的存储 I/O。

例如需要从文件或 Swap 读取页面。

可以查看:

/usr/bin/time -v ./test

输出中可能包含:

Major page faults
Minor page faults

注意:Major Fault 不等于所有磁盘 I/O;普通文件读写也可能产生 I/O,而不通过缺页异常触发。


第八部分:Heap、Stack、BSS 如何映射物理内存

四十一、BSS 的实际物理页分配

例如:

char buffer[100 * 1024 * 1024];

int main(void)
{
    buffer[0] = 1;
    return 0;
}

逻辑过程:

ELF 描述 BSS 大小
       |
       v
Linux 建立零初始化虚拟映射
       |
       v
程序访问 buffer[0]
       |
       v
可能发生写缺页
       |
       v
分配可写物理页面
       |
       v
写入数据

不需要因为 BSS 声明了 100 MiB,就立即为整个数组分配 100 MiB 独立物理页。

四十二、Heap 的实际物理页分配

例如:

char *p = malloc(4096);

malloc 返回的通常是用户虚拟地址。

malloc()
   |
   v
返回虚拟地址
   |
   v
访问虚拟页面
   |
   v
MMU 查询页表
   |
   v
物理页框

注意:

malloc 是用户态内存分配接口。

它通常依赖分配器管理 brk/mmap 获得的虚拟内存区域,而不是每调用一次 malloc 就必然向内核申请一个物理页。

四十三、Stack 的实际物理页分配

Stack 是虚拟地址空间中的一段映射区域。

它也通过页表映射到物理页框。

例如:

void test(void)
{
    char buffer[4096];
    buffer[0] = 1;
}

如果编译器实际为 buffer 分配栈存储,那么访问相应栈页面时,可能触发物理页的按需分配。

栈增长也受到:

  • VMA 规则。
  • 栈大小限制。
  • Guard Page。
  • 操作系统资源限制。

等因素约束。

不能理解为栈可以无限增长。

四十四、Text 为什么可以共享物理页

假设运行同一个程序两次:

./test &
./test &

两个进程拥有独立虚拟地址空间。

但是它们的只读可执行文件页面可能映射到同一份物理页缓存页面。

Process A
Virtual Text Page
        |
        |
        +------+
               |
               v
        Physical Code Frame
               ^
        +------+
        |
        |
Process B
Virtual Text Page

好处:

  • 节约物理 RAM。
  • 避免重复加载相同代码。
  • 保持进程虚拟地址空间独立。

是否实际共享取决于映射对象、页面状态及系统机制。


第九部分:写时复制 COW

四十五、为什么 fork 不立即复制所有物理内存

Linux 中:

fork();

可以创建子进程。

如果父进程拥有大量内存,fork 时立即复制所有物理页面会非常昂贵。

因此 Linux 通常使用:

Copy-On-Write(COW)。

写时复制。

四十六、COW 工作过程

fork 之后:

Parent Process
Virtual Page A
       |
       |
       +--------+
                |
                v
         Physical Frame X
                ^
       +--------+
       |
       |
Child Process
Virtual Page A

父子进程暂时共享某些物理页面。

相关映射受到写保护。

当子进程修改数据时:

Child 写入页面
       |
       v
触发写保护异常
       |
       v
Linux 判断需要 COW
       |
       v
分配新的物理页框
       |
       v
复制原页面内容
       |
       v
修改子进程页表
       |
       v
子进程写入新页面

结果:

Parent Virtual Page
       |
       v
Physical Frame X


Child Virtual Page
       |
       v
Physical Frame Y

注意:实际实现中,如果页面已经不存在其他需要保留的共享引用,内核可能通过优化避免不必要的数据复制。

四十七、COW 与普通共享内存的区别

COW:

  • 初始可能共享物理页面。
  • 写入时建立私有副本。
  • 修改通常不会反映到另一进程。

共享内存:

  • 多个进程映射同一个共享对象。
  • 写入通常可被其他进程观察。
  • 需要考虑并发同步。

两者虽然都涉及多个虚拟地址映射到同一个物理页框,但语义不同。


第十部分:Swap 与物理内存回收

四十八、Swap 是什么

Swap 是 Linux 用于管理部分可换出内存页面的后备存储机制。

当系统存在内存压力时,内核可能将符合条件的匿名内存页面换出到 Swap。

物理 RAM
    |
    | 页面换出
    v
Swap
    |
    | 页面换入
    v
物理 RAM

Swap 可以是:

  • Swap Partition。
  • Swap File。
  • 其他内核支持的交换后端。

并非所有内存页面都能直接交换。

例如内核中的某些页面、锁定页面和特殊设备映射具有不同管理规则。

四十九、Swap 与虚拟地址的关系

假设某个虚拟页面原本映射到物理页框。

Virtual Page A
       |
       v
Physical Frame 10

系统将该页面换出后:

Virtual Page A
       |
       v
Swap 中的页面数据

此时原来的物理页框可以被回收用于其他用途。

再次访问时:

CPU 访问 Virtual Page A
       |
       v
缺页异常
       |
       v
Linux 定位 Swap 数据
       |
       v
读入物理页面
       |
       v
恢复页表映射
       |
       v
继续执行

Swap 并不是把整个进程虚拟地址空间复制到磁盘。

它主要围绕可交换页面进行管理。

五十、文件页与匿名页

Linux 中常见两类页面:

File-backed Page

有文件作为后备对象。

例如:

  • ELF 代码页面。
  • 共享库页面。
  • mmap 文件映射。

干净的文件页在需要时通常可以直接丢弃,之后再从文件重新加载。

脏文件页在回收前可能需要写回。

Anonymous Page

没有普通文件作为直接后备对象。

例如:

  • Heap 中的匿名内存。
  • Stack 中的匿名内存。
  • 匿名 mmap。

匿名页面在需要保留内容并回收物理内存时,可能依赖 Swap 等机制。

五十一、Page Cache

Page Cache 是 Linux 用于缓存文件内容的重要机制。

磁盘文件
   |
   v
Page Cache
   |
   v
进程读取 / 文件映射

例如多个进程映射同一个共享库:

Process A
      |
      v
共享库文件页缓存
      ^
      |
Process B

Page Cache 可以减少重复磁盘读取。

需要区分:

  • Page Cache 是 Linux 管理的文件页缓存。
  • CPU Cache 是处理器硬件缓存。
  • TLB 是地址转换缓存。

它们不是同一个东西。


第十一部分:Linux 内核如何管理物理内存

五十二、物理内存不是由用户程序直接管理

用户程序通常通过:

malloc()
mmap()

申请或映射虚拟内存。

真正的物理页框由 Linux 内核管理。

概念上:

用户程序
   |
   v
malloc / mmap
   |
   v
虚拟内存管理
   |
   v
缺页处理 / 页面分配
   |
   v
Linux 物理内存分配器
   |
   v
Physical Page Frame

五十三、Buddy Allocator

Linux 使用伙伴分配器管理空闲物理页块。

其基本思想是按照 2 的幂次管理连续页块。

例如:

Order 0:1 Page
Order 1:2 Pages
Order 2:4 Pages
Order 3:8 Pages
...

当需要较小页块时,可以拆分较大页块。

当相邻伙伴页块均空闲时,可以尝试合并。

8 Pages
   |
   v
4 + 4
   |
   v
2 + 2 + 4
   |
   v
1 + 1 + 2 + 4

伙伴分配器主要解决物理页块分配问题。

五十四、Slab / SLUB

内核经常需要分配小对象,例如:

  • 内核数据结构。
  • 文件系统对象。
  • 进程相关结构。
  • 网络对象。

如果每个小对象都直接分配完整物理页,会产生浪费。

因此 Linux 还使用对象分配器,例如 SLUB。

概念上:

Buddy Allocator
       |
       v
分配物理页面
       |
       v
SLUB
       |
       v
管理小型内核对象

Buddy 与 SLUB 解决的问题不同:

  • Buddy:管理物理页块。
  • SLUB:管理内核对象分配。

用户态 malloc 则属于另一层的内存分配机制。

五十五、NUMA

在多 CPU 插槽或复杂服务器中,物理内存可能采用 NUMA 架构。

NUMA:

Non-Uniform Memory Access。

不同 CPU 节点访问不同内存节点的延迟可能不同。

CPU Node 0 ---> Local Memory 0
     |
     +-------> Remote Memory 1

CPU Node 1 ---> Local Memory 1
     |
     +-------> Remote Memory 0

Linux 会根据 NUMA 策略尝试优化物理页分配。

因此即使虚拟地址空间完全相同,实际物理页面放置位置也可能影响程序性能。


第十二部分:大页 Huge Page

五十六、为什么需要大页

普通 4 KiB 页面在管理大容量内存时会产生大量页面和页表项。

例如:

1 GiB / 4 KiB = 262144 Pages

如果采用 2 MiB 大页:

1 GiB / 2 MiB = 512 Pages

页面数量明显减少。

大页的好处可能包括:

  • 减少页表开销。
  • 提高 TLB 覆盖范围。
  • 降低部分地址转换成本。

但大页也可能带来内存浪费、分配困难和管理成本。

五十七、常见页面大小

以常见 x86-64 配置为例:

页面类型 大小
普通页 4 KiB
大页 2 MiB
超大页 1 GiB

Linux 还支持 Transparent Huge Pages(THP)等机制。

ARM64 可以根据硬件和配置采用不同的基础页面粒度及大块映射。

因此不要认为所有 Linux 系统都固定采用 4 KiB 页面。


第十三部分:VMA 与页表的区别

五十八、VMA 是什么

VMA:

Virtual Memory Area。

它描述进程虚拟地址空间中的一段连续区域及其属性。

例如:

VMA 1:代码区域
VMA 2:数据区域
VMA 3:Heap
VMA 4:共享库
VMA 5:Stack

VMA 可以描述:

  • 虚拟地址起止范围。
  • 访问权限。
  • 文件映射关系。
  • 匿名映射属性。
  • 共享或私有映射属性。

五十九、VMA 不等于页表

两者最重要的区别:

对比 VMA Page Table
管理层面 虚拟地址区域 页面级地址转换
主要描述 范围和属性 虚拟页到物理页框的映射
主要维护者 Linux 内核 Linux 内核建立,MMU 使用
是否必须立即拥有物理页 否 有效叶子映射通常指向物理页框
粒度 一段地址范围 页或大页

例如:

VMA:0x100000 ~ 0x10FFFF
          |
          v
     16 个 4 KiB 虚拟页
          |
          v
   各自的页表映射状态
          |
          v
    部分已映射物理页
    部分尚未分配物理页

因此:

VMA 说明某个地址范围应该如何使用;页表描述当前硬件如何转换其中的地址。


第十四部分:从程序启动到物理页映射的完整流程

六十、ELF 加载到执行的完整过程

C/C++ Source
       |
       v
Compiler / Assembler / Linker
       |
       v
ELF Executable
       |
       v
execve()
       |
       v
Linux 加载 ELF
       |
       v
读取 Program Header
       |
       v
建立进程虚拟地址空间
       |
       +-- Text VMA
       +-- Data VMA
       +-- BSS 映射
       +-- Heap
       +-- mmap
       +-- Stack
       |
       v
准备页表和地址转换上下文
       |
       v
设置入口地址和初始栈
       |
       v
CPU 开始执行
       |
       v
访问虚拟地址
       |
       v
TLB 查询
       |
       +-- 命中
       |     |
       |     v
       |   地址转换
       |
       +-- 未命中
             |
             v
          页表遍历
             |
             +-- 有效映射
             |     |
             |     v
             |   得到物理地址
             |
             +-- 缺页/权限异常
                   |
                   v
                Linux 内核
                   |
                   v
             处理缺页或错误
                   |
                   v
                更新映射
                   |
                   v
               重新执行

需要注意:execve 会替换当前进程的程序映像,并不必然创建一个新的 PID。通常 shell 先 fork,再由子进程 execve 执行目标程序。

六十一、一个变量从 C 代码到物理 RAM

例如:

#include <stdio.h>
#include <stdlib.h>

int global_a = 100;
int global_b;

int main(void)
{
    int local_a = 10;
    int *p = malloc(sizeof(int));

    if (p == NULL)
        return 1;

    *p = 20;

    printf("%d %d %d\n", global_a, local_a, *p);

    free(p);
    return 0;
}

首先从程序内存区域理解:

global_a
   |
   v
.data

global_b
   |
   v
.bss

local_a
   |
   v
Stack / Register

p 指针变量
   |
   v
Stack / Register

p 指向的动态对象
   |
   v
Heap / mmap

再从物理映射角度理解:

变量对应的虚拟地址
        |
        v
确定所属虚拟页面
        |
        v
MMU / TLB / Page Table
        |
        v
得到物理页框
        |
        v
通过页内偏移访问对象

注意:编译器可能将部分变量优化到寄存器中,因此并不是每个 C 变量都必然具有实际内存地址。


第十五部分:如何在 Linux 中验证

六十二、查看 ELF 结构

查看 ELF Header:

readelf -h ./test

查看 Section:

readelf -S ./test

查看 Program Header:

readelf -l ./test

重点观察:

.text
.rodata
.data
.bss
PT_LOAD

六十三、查看进程虚拟地址布局

查看当前 Shell:

cat /proc/$$/maps

查看指定进程:

cat /proc/<PID>/maps

可能看到:

[heap]
[stack]
[vdso]
[vvar]
libc.so
ld-linux.so

maps 主要展示虚拟内存映射区域。

它不能直接告诉普通用户每个虚拟页面实际对应哪个物理页框。

六十四、查看进程内存统计

cat /proc/<PID>/status

重点观察:

  • VmSize:虚拟内存大小。
  • VmRSS:常驻物理内存统计。
  • VmData:数据区域相关虚拟内存统计。
  • VmStk:栈相关虚拟内存统计。
  • VmSwap:交换空间相关统计。

还可以:

cat /proc/<PID>/smaps

查看更详细的映射内存信息。

例如:

  • Size
  • Rss
  • Pss
  • Shared_Clean
  • Private_Dirty
  • Anonymous
  • Swap

其中:

RSS 是进程常驻内存统计,并不是进程独占的物理内存总量。

PSS 则按比例分摊共享页面,更适合某些场景下估算进程内存占用。

六十五、查看系统物理内存

free -h

或者:

cat /proc/meminfo

重点观察:

MemTotal
MemFree
MemAvailable
Cached
SwapTotal
SwapFree

MemFree 不等于系统真正可供应用使用的全部内存。

Linux 会利用空闲 RAM 缓存文件等数据,因此 MemAvailable 往往更适合粗略判断可用内存情况。

六十六、查看页面大小

getconf PAGESIZE

可能输出:

4096

表示当前系统普通页面大小为 4096 Bytes。

六十七、查看 Page Fault 统计

/usr/bin/time -v ./test

也可以:

perf stat -e page-faults,minor-faults,major-faults ./test

perf 是否可用取决于安装情况和系统权限。

六十八、查看虚拟地址与物理页映射

Linux 提供:

/proc/<PID>/pagemap

它可以用于查询进程虚拟页面的部分映射状态。

但是:

  • 读取需要按照内核接口解析二进制条目。
  • PFN 信息受到权限限制。
  • 现代 Linux 通常不允许普通用户随意获取物理页框号。
  • 不同内核版本可能存在接口差异。

因此不应将 pagemap 简单理解成普通文本页表。

查看内核相关文档:

Documentation/admin-guide/mm/pagemap.rst

六十九、ARM64 如何理解页表

ARM64 与 x86-64 的核心目标相同:

Virtual Address
       |
       v
Translation Tables
       |
       v
Physical Address

但 ARM64 有自己的机制,例如:

  • TTBR0_EL1
  • TTBR1_EL1
  • TCR_EL1
  • Translation Table Descriptor
  • ASID
  • TLB
  • Translation Fault
  • Permission Fault

典型情况下:

用户态虚拟地址
       |
       v
TTBR0_EL1 所配置的转换表
       |
       v
物理地址

内核高地址映射通常由 TTBR1_EL1 所配置的转换表管理。

具体地址划分及页表级数由系统配置决定。

ARM64 可以使用 4 KiB、16 KiB、64 KiB 等不同基础页面粒度,不能机械套用 x86-64 的四级、每级 9 位模型。


第十六部分:常见错误认识

七十、错误一:虚拟内存就是 Swap

错误。

虚拟内存是一整套地址空间抽象和映射机制。

Swap 只是部分页面的后备存储与换入换出机制。

没有 Swap,Linux 依然可以使用虚拟内存和分页。

七十一、错误二:malloc 100 MiB 就立即占用 100 MiB RAM

不一定。

malloc 可能只管理或获取相应虚拟地址范围。

物理页面可能按需分配,也可能复用已有页面。

七十二、错误三:Heap 是物理内存中的固定区域

错误。

Heap 是进程虚拟地址空间中的逻辑区域。

它的页面可以映射到分散的物理页框。

七十三、错误四:两个进程不能共享物理内存

错误。

两个进程可以共享:

  • 只读代码页面。
  • 共享库页面。
  • 共享文件映射。
  • 显式共享内存。
  • COW 尚未分离的页面。

进程隔离并不意味着完全不能共享物理页。

七十四、错误五:TLB Miss 就是缺页异常

错误。

TLB Miss 表示地址转换缓存未命中。

Page Fault 表示当前访问无法直接通过正常页表转换或权限检查完成。

七十五、错误六:Page Fault 一定导致程序崩溃

错误。

很多 Page Fault 是正常的按需分页、COW 或换入过程。

只有无法合法处理的异常才可能导致 SIGSEGV、SIGBUS 等结果。

七十六、错误七:虚拟地址连续意味着物理地址连续

错误。

分页机制允许连续虚拟页映射到分散物理页框。

七十七、错误八:所有页面都固定为 4 KiB

错误。

页面大小取决于架构、配置及映射类型。

七十八、错误九:每个变量一定存储在 RAM 中

错误。

编译器可能将变量:

  • 放在寄存器中。
  • 优化成常量。
  • 完全消除。

即使存在内存对象,也不意味着其页面此刻一定常驻物理 RAM。

七十九、错误十:用户程序可以随意访问物理地址

错误。

普通用户程序主要通过虚拟地址访问内存。

硬件与内核共同实施地址转换和权限保护。


第十七部分:最终完整知识模型

八十、第一层:程序文件

源代码
  |
  v
ELF 文件
  |
  +-- .text
  +-- .rodata
  +-- .data
  +-- .bss 描述
  +-- Program Header

这一层主要解决:

程序在磁盘上如何组织。

八十一、第二层:进程虚拟地址空间

Process Virtual Address Space
  |
  +-- Text
  +-- rodata
  +-- Data
  +-- BSS
  +-- Heap
  +-- mmap
  +-- Stack

这一层主要解决:

程序运行时如何组织代码、数据和内存对象。

八十二、第三层:虚拟页面

Virtual Address Space
       |
       v
Virtual Pages
       |
       +-- VPN
       +-- Offset

这一层主要解决:

如何把虚拟地址空间划分为可管理的页面。

八十三、第四层:页表与 MMU

Virtual Page
      |
      v
Page Table
      |
      v
MMU / TLB
      |
      v
Physical Frame

这一层主要解决:

虚拟地址如何转换为物理地址,以及访问权限如何受到控制。

八十四、第五层:物理内存管理

Linux Kernel
      |
      +-- Buddy Allocator
      +-- SLUB
      +-- Page Reclaim
      +-- Page Cache
      +-- Swap
      +-- NUMA
      |
      v
Physical RAM

这一层主要解决:

真实物理页面如何分配、复用、回收和管理。

八十五、完整总图

                  C / C++ 程序
                       |
                       v
                 ELF 可执行文件
                       |
                       v
                    execve()
                       |
                       v
              Linux 创建程序映像
                       |
                       v
              进程虚拟地址空间
                       |
      +----------------+----------------+
      |                |                |
      v                v                v
  Text/Data        Heap/mmap          Stack
      |                |                |
      +----------------+----------------+
                       |
                       v
                 Virtual Pages
                       |
                       v
                 Virtual Address
                       |
                       v
                  MMU / TLB
                       |
                       v
                 Page Tables
                       |
          +------------+------------+
          |                         |
          v                         v
     映射有效                 缺页/权限异常
          |                         |
          |                         v
          |                    Linux Kernel
          |                         |
          |                 +-------+-------+
          |                 |               |
          |                 v               v
          |             分配物理页       文件/Swap/COW
          |                 |               |
          |                 +-------+-------+
          |                         |
          |                         v
          |                    更新页表
          |                         |
          +------------+------------+
                       |
                       v
                 Physical Address
                       |
                       v
                Cache / Memory
                       |
                       v
                  CPU 使用数据

这张图需要理解成逻辑关系图,而不是严格的硬件时序图:TLB 查询与页表遍历是地址转换的不同路径,Linux 内核通常只在需要软件介入时处理异常。


第十八部分:最终总结

八十六、必须掌握的核心概念

概念 核心含义
ELF 程序的可执行文件格式
Section ELF 内部逻辑内容
Segment ELF 加载映射单元
VMA 虚拟地址范围及其属性
Virtual Address 程序使用的虚拟地址
Physical Address 物理地址空间中的地址
Page 虚拟页面
Page Frame 物理页框
VPN 虚拟页号
PFN 物理页框号
Page Table 虚拟页到物理页框的映射结构
PTE 页表项
MMU 硬件地址转换单元
TLB 地址转换缓存
Page Fault 地址转换或权限异常
Demand Paging 按需建立页面映射
COW 写时复制
Swap 可交换页面的后备存储机制
Page Cache 文件页面缓存
Buddy 物理页块分配器
SLUB 内核对象分配器
Huge Page 大页面映射
ASLR 地址空间布局随机化

八十七、最终需要记住的十句话

  1. ELF 文件是磁盘上的程序组织形式,进程是程序运行时的实体。
  2. Text、Data、BSS、Heap、mmap、Stack 主要描述进程虚拟地址空间中的不同区域。
  3. 每个进程通常拥有独立的用户态虚拟地址空间。
  4. 虚拟内存不是物理 RAM,也不等于 Swap。
  5. 虚拟地址通过页表和 MMU 转换为物理地址。
  6. 页表映射以页面为单位,虚拟页与物理页框不必连续。
  7. TLB 缓存地址转换结果,提高访问效率。
  8. Page Fault 可能是正常的按需分页过程,并不一定是程序错误。
  9. Linux 可以通过 COW、共享页面、Page Cache、Swap 等机制提高内存利用率。
  10. 用户程序管理的是虚拟地址中的对象,Linux 内核负责协调实际物理页面的分配、映射和回收。

八十八、一句话建立完整认识

程序编译成 ELF 后,Linux 根据加载信息建立进程虚拟地址空间;代码、数据、堆、栈和映射区域通过虚拟页面组织;CPU 使用虚拟地址访问内存,MMU 借助 TLB 和页表完成地址转换;当页面尚未建立有效映射时,Linux 可以通过缺页异常按需分配、加载或恢复物理页面;最终由内核管理真实物理 RAM,实现进程隔离、内存共享和高效内存利用。


参考资料

posted on 2026-10-01 08:16  心宇16969  阅读(20)  评论(0)    收藏  举报