WK100%头像
关注

Linux 虚拟内存的理解

本文的产生源自我对虚拟地址的两个问题:

1.虚拟内存到底真实存在吗?如果存在难道不用依附于硬件吗?

2.如果不存在它又是怎么把对应的数据传进物理内存的?

本文需要你了解一定关于虚拟内存的知识,如果你也有这样的疑问,希望本文能帮到你,同时如果有错误的地方希望指出。欢迎评论区讨论,我也将好的评论改进如文章,谢谢!

Linux 虚拟内存与地址转换

核心思想:

程序使用的是虚拟地址,真正访问 RAM 时使用的是物理地址。

CPU 通过 MMU + 页表完成虚拟地址到物理地址的转换。


一、虚拟地址是什么?

1. 虚拟地址 ≠ 物理地址

假设:

int a = 10;
printf("%p\n", &a);

输出:

0x7fffffffdc20

这个地址通常是:

虚拟地址

它并不是说:

RAM 上真的存在一个地址叫 0x7fffffffdc20。

而是:

对于当前进程来说,这个地址编号可以通过地址转换找到对应的物理内存。

可以类比成:

虚拟地址 = 房间编号
物理地址 = 房间真正所在的位置

二、虚拟地址到底“存不存在”?

这个问题非常重要。

应该区分:

概念是否存在
0x12345678 这个数字✅ 存在
虚拟地址这个概念✅ 存在
虚拟地址对应的物理内存❓ 不一定
有效的虚拟地址映射❓ 不一定
物理地址如果存在对应物理内存,则存在

所以更准确的说法不是:

“这个虚拟地址不存在。”

而是:

这个虚拟地址没有建立有效的映射。


三、虚拟地址为什么可以使用?

程序看到的是:

虚拟地址空间

例如:

0x00000000
      ↓
0x10000000  ─────→ 物理内存 A
      ↓
0x20000000  ─────→ 物理内存 C
      ↓
0x30000000  ─────→ 没有映射
      ↓
0x40000000  ─────→ 物理内存 B

注意:

0x30000000

这个地址编号本身可以存在。

但是它当前没有有效的物理内存映射。

如果程序访问:

*(int *)0x30000000

就可能产生:

Page Fault

如果操作系统发现这是非法访问,最终可能导致:

Segmentation Fault

四、CPU 如何访问内存?

程序:

int a = 10;

CPU 需要访问 a。

大致过程:

程序
 ↓
CPU
 ↓
虚拟地址
 ↓
MMU
 ↓
页表
 ↓
物理地址
 ↓
内存控制器
 ↓
RAM

所以:

虚拟地址不是直接去访问内存条。

而是:

CPU 使用虚拟地址发起内存访问,MMU 将虚拟地址转换成物理地址,然后内存系统根据物理地址完成真正的读写。


五、虚拟地址的组成

虚拟地址并不是简单地作为一个完整数字去查页表。

通常可以拆成:

虚拟地址 = 虚拟页号 + 页内偏移

例如:

虚拟地址

┌──────────────┬────────────┐
│   虚拟页号   │  页内偏移  │
└──────────────┴────────────┘

其中:

  • 虚拟页号(VPN):用于寻找对应的物理页

  • 页内偏移(Offset):表示数据在这一页中的具体位置


六、为什么页内偏移不变?

假设:

虚拟地址:

0x12345 | 0x678
   ↑         ↑
虚拟页号   页内偏移

页表找到:

虚拟页号 0x12345
        ↓
物理页号 0x8ABCD

那么:

虚拟地址:

0x12345 | 0x678

        ↓ 页表转换

物理地址:

0x8ABCD | 0x678

注意:

虚拟页号 → 物理页号

但是:

页内偏移不变

所以可以记成:

页表负责把“虚拟页”找到对应的“物理页”,页内偏移保持不变。


七、页(Page)是什么?

操作系统通常不会把每一个字节单独建立映射。

而是把内存划分成一个个固定大小的块。

虚拟内存中的块:

虚拟页(Page)

物理内存中的对应块:

物理页框(Page Frame)

例如常见的:

4 KiB

也就是:

4096 Bytes = 2^12 Bytes

因此,如果页大小是 4 KiB:

页内偏移 = 12 bit

八、页表是什么?

页表可以理解成:

记录虚拟页和物理页之间映射关系的数据结构。

简单理解:

虚拟页号       物理页号

0x12345   →    0x8ABCD
0x56789   →    0x12345
0xABCDE   →    0x789AB

因此:

虚拟地址
    ↓
找到虚拟页号
    ↓
查页表
    ↓
得到物理页号
    ↓
加上页内偏移
    ↓
得到物理地址

九、谁负责地址转换?

负责虚拟地址转换的是:

MMU

全称:

Memory Management Unit
内存管理单元

可以把 MMU 理解成:

硬件地址翻译器。

大致过程:

C 程序
   ↓
CPU
   ↓
虚拟地址
   ↓
MMU
   ↓
查页表
   ↓
物理地址
   ↓
内存系统

注意:

C 程序本身不负责把虚拟地址转换成物理地址。


十、页表放在哪里?

页表本身也是数据。

因此:

页表本身也存放在物理内存中。

可以简单理解成:

物理内存

┌─────────────────────┐
│        页表         │
├─────────────────────┤
│      程序数据       │
├─────────────────────┤
│      其他数据       │
├─────────────────────┤
│      其他进程       │
└─────────────────────┘

CPU 需要知道:

当前进程使用的页表在哪里?

在 x86-64 中,会涉及:

CR3

可以把它理解成:

CPU 用来定位当前地址空间顶层页表的重要寄存器。


十一、为什么不能每次都查页表?

如果每次访问内存都:

CPU
 ↓
查页表
 ↓
得到物理地址
 ↓
访问内存

会增加访问成本。

所以 CPU 提供了:

TLB

全称:

Translation Lookaside Buffer

可以理解为:

页表映射结果的高速缓存。

例如:

虚拟页号        物理页号

0x12345   →     0x8ABCD
0x56789   →     0x12345

这些转换结果可以被缓存到 TLB。


十二、TLB 命中和未命中

CPU 访问:

虚拟地址

首先会尝试:

查 TLB

情况一:TLB 命中

CPU
 ↓
虚拟地址
 ↓
TLB
 ↓
命中
 ↓
直接得到物理页号
 ↓
物理地址
 ↓
访问内存

不需要重新进行完整的页表遍历。


情况二:TLB 未命中

CPU
 ↓
虚拟地址
 ↓
TLB
 ↓
未命中
 ↓
查多级页表
 ↓
得到物理页号
 ↓
更新 TLB
 ↓
访问物理内存

所以:

TLB = 页表映射结果的高速缓存

十三、现代系统为什么使用多级页表?

如果使用一张巨大的页表:

虚拟地址
   ↓
一张超级大的页表
   ↓
物理地址

会产生巨大的空间开销。

所以现代系统通常使用:

多级页表

例如 x86-64 常见的四级分页结构可以简化理解成:

虚拟地址
   │
   ├── 第一级索引
   │
   ├── 第二级索引
   │
   ├── 第三级索引
   │
   ├── 第四级索引
   │
   └── 页内偏移

大致过程:

虚拟地址
    ↓
PML4
    ↓
PDPT
    ↓
PD
    ↓
PT
    ↓
物理页
    ↓
+ 页内偏移
    ↓
物理地址

十四、可以把多级页表理解成“查字典”

例如:

虚拟地址

┌──────┬──────┬──────┬──────┐
│索引1 │索引2 │索引3 │偏移  │
└──────┴──────┴──────┴──────┘
   ↓      ↓      ↓
 PML4   PDPT     PD
                 ↓
                 PT
                 ↓
              物理页

CPU 根据虚拟地址中的不同索引:

第一层 → 第二层 → 第三层 → 第四层

最终找到:

物理页号

然后加上:

页内偏移

得到:

物理地址

十五、找到物理地址以后怎么办?

地址转换完成以后:

虚拟地址
     ↓
    MMU
     ↓
物理地址
     ↓
内存系统
     ↓
RAM

可以简单理解为:

CPU
 │
 │ 物理地址
 ↓
内存控制器
 │
 ↓
内存总线 / 内存接口
 │
 ↓
RAM

例如:

a = 20;

本质上是 CPU 发起一次写内存操作:

我要把数据 20
写入某个物理地址

十六、Page Fault(缺页异常)

如果 CPU 访问:

虚拟地址

但是页表发现:

当前没有有效的物理页映射

就可能产生:

Page Fault

过程:

CPU
 ↓
访问虚拟地址
 ↓
MMU
 ↓
发现页面没有有效映射
 ↓
Page Fault
 ↓
CPU 转入异常处理
 ↓
操作系统处理

操作系统根据具体情况决定怎么办。


十七、Page Fault 不一定意味着程序出错

这是一个非常容易混淆的地方。

Page Fault:

不等于一定发生错误。

例如某个页面暂时不在 RAM 中,但它属于合法的虚拟内存区域。

操作系统可能:

磁盘 / SSD
     ↓
读取页面
     ↓
放入物理内存
     ↓
建立页表映射
     ↓
重新执行相关指令
     ↓
程序继续运行

但是,如果访问的是一个完全非法的地址:

非法虚拟地址
      ↓
Page Fault
      ↓
操作系统发现非法访问
      ↓
终止进程
      ↓
Segmentation Fault

所以:

Page Fault
    ≠
一定是错误

十八、完整的内存访问过程

假设:

int a = 10;
a = 20;

CPU 需要修改 a。

完整过程可以理解成:

                C 程序
                   │
                   ↓
              变量 a
                   │
                   ↓
             虚拟地址
            0x12345678
                   │
                   ↓
                  TLB
             ┌─────┴─────┐
             │           │
           命中          未命中
             │           │
             │           ↓
             │        多级页表
             │           │
             │           ↓
             │       物理页号
             │           │
             └─────┬─────┘
                   ↓
              物理地址
            0x8ABCD678
                   │
                   ↓
               内存系统
                   │
                   ↓
                  RAM
                   │
                   ↓
               数据变成 20

十九、一个最重要的概念:进程拥有自己的虚拟地址空间

不同进程可以拥有相同的虚拟地址。

例如:

进程 A:

虚拟地址 0x12345678
        ↓
物理页 A

而:

进程 B:

虚拟地址 0x12345678
        ↓
物理页 B

虽然:

虚拟地址一样

但是:

页表不同

因此最终可能对应:

不同的物理内存

这就是为什么每个进程都可以拥有自己的:

虚拟地址空间

二十、指针和虚拟地址

例如:

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

可以简单理解成:

        a
┌──────────────┐
│      10      │
└──────────────┘
       ↑
       │
       │ &a
       │
       p
┌──────────────┐
│  虚拟地址值  │
└──────────────┘

所以:

p

保存的是一个地址值。

而:

*p

表示:

访问这个地址所对应的对象。

CPU 最终仍然需要经过:

虚拟地址
 ↓
MMU
 ↓
页表 / TLB
 ↓
物理地址
 ↓
RAM

二十一、几个概念一定不要混

1. 虚拟地址

程序使用的地址

2. 物理地址

用于定位物理内存的地址

3. 页

虚拟地址空间中的固定大小内存块

4. 物理页框

物理内存中的固定大小内存块

5. 页表

记录虚拟页 → 物理页映射关系的数据结构

6. MMU

负责地址转换的硬件

7. TLB

缓存虚拟页 → 物理页转换结果的高速缓存

8. Page Fault

访问虚拟页面时发生的一种异常

不一定意味着程序错误。


9. Segmentation Fault

程序进行了非法的内存访问

操作系统通常会终止该进程。


二十二、最终一定要记住的模型

把下面这张图记住,虚拟内存的主线基本就通了:

                     程序
                      │
                      ↓
                  虚拟地址
                      │
                      ↓
                     CPU
                      │
                      ↓
                     TLB
                ┌─────┴─────┐
                │           │
              命中          未命中
                │           │
                │           ↓
                │        多级页表
                │           │
                │           ↓
                │       物理页号
                │           │
                └─────┬─────┘
                      ↓
                  物理地址
                      │
                      ↓
                  内存系统
                      │
                      ↓
                     RAM

如果页面没有有效映射:

虚拟地址
   ↓
TLB / 页表
   ↓
Page Fault
   ↓
操作系统
   ↓
合法访问?
  ↙     ↘
是       否
↓         ↓
处理      非法访问
↓         ↓
继续      Segmentation Fault

二十三、一句话总结

程序使用虚拟地址,MMU 通过 TLB 和多级页表把虚拟页转换成物理页,页内偏移保持不变,最终形成物理地址,再由内存系统访问 RAM。

整个链条:

虚拟地址
   ↓
TLB
   ↓
多级页表
   ↓
物理页号
   +
页内偏移
   ↓
物理地址
   ↓
内存系统
   ↓
RAM

二十四、接下来学习的知识路线

建议按照这个顺序继续学习:

虚拟地址
   ↓
虚拟页 / 物理页
   ↓
页表
   ↓
多级页表
   ↓
TLB
   ↓
Page Fault
   ↓
进程虚拟地址空间
   ↓
malloc()
   ↓
fork()
   ↓
写时复制 COW
   ↓
mmap()

其中最值得重点理解的是:

页表 → 多级页表 → TLB → Page Fault → fork + COW

这条线一旦打通,Linux 的进程、内存管理、malloc、fork、COW会变得非常容易串起来。

二十五. 虚拟内存 = 软件 + 硬件(回答第二个问题)

可以画成:

              虚拟内存
                  │
        ┌─────────┴─────────┐
        │                   │
      操作系统              硬件
        │                   │
     管理页表              MMU
     管理地址空间           TLB
     处理Page Fault        地址转换
        │                   │
        └─────────┬─────────┘
                  ↓
                RAM

所以你的问题可以直接回答:

虚拟内存是真实存在的吗?

是。

但它真实存在的是一种计算机系统机制和地址空间抽象,不是一块独立的硬件。

它需要依附硬件吗?

需要。

现代 CPU 必须提供类似 MMU、TLB、分页机制这样的硬件支持,操作系统再利用这些硬件建立虚拟内存系统。


最后把三个东西彻底分开

物理内存
= 真正的 RAM 硬件

虚拟地址空间
= 进程看到的地址空间

虚拟内存
= 利用 CPU 硬件 + 操作系统
  把虚拟地址空间和物理内存联系起来的机制

所以你现在如果看到:

“虚拟内存”

脑子里千万不要想象成:

另一块隐藏的内存条 ❌

应该想象成:

程序
 ↓
虚拟地址空间
 ↓
页表
 ↓
MMU/TLB
 ↓
物理内存

这才是虚拟内存。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2502_93625125/article/details/166688292

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--