物理内存与虚拟内存


一 ,什么是物理内存?

物理内存(RAM)就是电脑里真实存在的内存空间,也就是内存条
电脑里有 CPU、RAM(内存)、SSD(硬盘)

  • CPU:负责执行指令,进行计算
  • RAM:存放程序运行时需要用到的数据
  • SSD:用来存放文件和程序,关机后数据也能保存
    比如电脑安装了 16GB 的内存条,就有大约 16GB 的物理内存
    当我们运行一个 C 程序时,程序需要的数据会按需要放到 RAM 中,然后 CPU 再去访问这些数据

RAM 的读取速度比 SSD 快得多,但是断电后,RAM 中的数据一般就没了


二 ,什么是虚拟内存?

虚拟内存不是电脑里真的多出来了一块内存,而是操作系统给程序提供的虚拟地址空间

#include <stdio.h>

int main()
{
    int a=10;
    printf("%p\n",(void *)&a);
    return 0;
}

分析

  • &a:取出变量 a 的地址
  • %p:输出地址

这里输出的一般是虚拟地址,并不是数据在内存条里的实际地址

  • 比如程序 A 和程序 B
    程序 A:虚拟地址 1000 → 物理地址 5000
    程序 B:虚拟地址 1000 → 物理地址 8000

  • 两个程序的虚拟地址虽然相同,但是对应的物理地址可以不同

也就是说,程序使用的是虚拟地址,数据真正存放的位置是在物理内存里

为什么要有虚拟内存

假如程序直接使用物理地址,不同程序之间就容易互相影响
操作系统给每个程序分配自己的虚拟地址空间,这样的话

  1. 不同程序一般不能随便访问对方的内存
  2. 程序不用自己去找物理内存的位置
  3. 申请了虚拟内存,也不代表马上就用了同样大小的物理内存

虚拟内存不等于 Swap(交换空间),这两个不是一个东西

虚拟内存的划分

  1. 代码段 - - -存放程序的指令
  2. 数据段 - - -存放全局变量和静态变量
  3. 堆 - - - 程序自己申请的内存
  4. 栈 - - - 函数的临时工作台
  5. 内存映射区 - - -链接文件与内存的区域

三, 虚拟地址是怎么转换成物理地址的?

1. 页

操作系统不是一个字节一个字节地管理内存,而是把内存分成一块一块的,这一块就叫页
比如很多 Linux 电脑的一个页是 4096 字节,也就是 4KiB

不同电脑的页大小不一定相同

2. 页表

页表用来记录虚拟页和物理页之间的对应关系
比如
虚拟页 1 -> 物理页 8
虚拟页 2 -> 物理页 3
虚拟页 3 -> 物理页 12

程序访问虚拟地址时,需要根据页表找到它对应的物理地址

3. MMU 和 TLB

  • MMU:CPU 里负责把虚拟地址转换成物理地址的硬件
  • TLB:用来保存最近使用过的地址对应关系,避免每次都去查页表

如果 TLB 里有对应关系,就可以直接使用,没有的话,再查页表
TLB 没查到,不代表发生了缺页异常

4. 缺页异常

比如程序申请了一块内存,但是还没有真正使用,这时操作系统可能还没有给它分配对应的物理内存,等程序第一次访问这块内存时,就可能发生缺页异常

  • 如果是正常的内存访问,操作系统可以分配物理内存,然后继续运行
  • 如果数据暂时在硬盘上,就可能需要先把数据读回来
  • 如果访问了不允许访问的地址,就可能出现段错误

缺页异常不一定是程序出错,也可能是操作系统正常分配内存的过程


四, Swap 是什么?

Swap 就是交换空间,一般使用硬盘上的一部分空间

  • 比如电脑只有 16GB 的 RAM,但是同时运行的程序比较多,物理内存不太够用,Linux 就可能把暂时不用的一些内存页放到 Swap 里,等需要这些数据时,再把它们读回来

  • Swap 用的是硬盘空间,不是真的增加了内存条

  • SSD 比 RAM 慢很多,所以频繁使用 Swap 可能会让电脑变卡

  • 也不是 RAM 占用高就一定会用到 Swap,还要看系统设置


五, malloc 申请的是虚拟内存还是物理内存?

我们写 C 语言时,经常会用到 malloc

int *p=malloc(100*sizeof(int));

相当于申请一块能存放 100 个 int 的内存空间
但是 malloc 成功后,并不代表操作系统已经马上分配了同样大小的物理内存

在常见的 Linux 环境下,系统可能先准备好虚拟地址空间,等程序真正访问时,再分配对应的物理页

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

#define SIZE (64*1024*1024)

int main()
{
    char *p=malloc(SIZE);
    if(p==NULL)return 1;

    printf("第一次暂停\n");
    getchar();

    for(size_t i=0;i<SIZE;i+=4096)
    {
        p[i]=1;
    }

    printf("第二次暂停\n");
    getchar();

    free(p);
    return 0;
}

分析

  • SIZE:申请 64*1024*1024 字节,也就是 64MiB
  • malloc(SIZE):申请内存
  • 第一个 getchar():让程序先停下来,方便查看内存占用
  • p[i]=1:每隔 4096 字节写入一个数据。如果页大小是 4KiB,相当于每页都写入一次
  • 第二个 getchar():再次暂停,查看内存占用有没有变化
  • free(p):释放之前申请的内存,释放以后就不能再通过 p 使用这块空间

六, 用 Ubuntu 终端查看内存

1. 查看内存使用情况

终端输入
free -h

  • free:查看系统内存的使用情况
  • -h:让输出的数字更容易看,比如用 MiB、GiB 表示
  • Mem:物理内存
  • Swap:交换空间

这里的 free 是 Linux 命令,不是 C 语言里的 free(p)。

2. 编译与运行代码

先将代码保存为 show.c,然后在终端输入:

gcc show.c -o show
./show
  • gcc show.c -o show:编译代码,生成 show 可执行文件
  • ./show:运行程序

程序第一次会停在:

第一次暂停

先不要按回车,再打开一个终端,输入:

ps -C show -o pid,comm,vsz,rss

分析

  • ps:查看进程信息
  • -C show:只查看进程名为 show 的程序
  • -o:指定输出哪些信息
  • pid:进程编号
  • comm:进程名称
  • vsz:虚拟内存大小
  • rss:当前驻留在物理内存中的大小

这里 VSZ 和 RSS 的单位通常都是 KiB,不是字节

比如 RSS 显示 1680,就是 1680KiB,它不一定为 0,因为程序本身运行也要占用一些物理内存,并不只有 malloc 申请的那部分
然后回到第一个终端,按一次回车,让程序执行写入操作,再运行一次上面的 ps 命令

分析

  • 执行 malloc 后,VSZ 已经增加,RSS很少
  • 往内存里写入数据后,VSZ不变,RSS 会明显增加

申请了内存空间,不代表马上占用同样大小的 RAM,等真正访问以后,物理内存占用才可能增加

RSS 也可能包含共享的内存页,不能直接理解成这个程序独占的全部内存


七 , 内存单位

  • 1KiB = 1024 字节
  • 1MiB = 1024KiB
  • 1GiB = 1024MiB

注意 free -h 和 ps 显示的单位可能不一样


总结

  1. 物理内存就是电脑里真实存在的 RAM
  2. 虚拟内存是操作系统给程序提供的地址空间,程序一般使用虚拟地址
  3. 页表和 MMU 用来完成虚拟地址到物理地址的转换,TLB 可以加快查找
  4. Swap 是交换空间,不等于虚拟内存
  5. malloc 申请成功后,物理内存不一定马上增加同样多,可以用 VSZ 和 RSS 观察
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐