一文明白 Linux 虚拟内存

携手创作,共同成长!这是我参与「日新计划 8 月更文挑战」的第2天,点击查看活动详情

举个栗子:我的笔记本电脑内存就是 16GB,也是 内存容量,指的是 物理内存。

  • 物理内存也称为主存,大多数计算机用的主存都是 动态随机访问内存(DRAM

  • 只有 内核态 才可以直接访问物理内存


问题:为什么只有 内核态 才能直接访问 物理内存?

回答:区分为用户态和内核态,主要目的为了 保护系统程序

  • 内核态(Kernal Mode): 用户空间中的代码使用和一个局部的内存空间。
  • 用户态(User Mode): 内核空间中的代码可以访问所有内存。

问题:用户进程要访问内存时,该怎么办呢?

回答:Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问 虚拟内存

  • 在用户程序中,凡是与资源有关的操作( 存储分配、进行I/O传输以及管理文件等),都必须通过系统调用方式操作系统提出服务请求,并由操作系统代为完成。
  • 系统通过 硬件中断机制 进入内核态。

一文明白 Linux 虚拟内存


虚拟内存(virtual memory :将用户逻辑内存与物理内存分开。

为什么需要虚拟内存?

  1. 可以在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存。

  2. 虚拟内存使编程更加容易,因为程序员不再需要担心可用的有限物理内存空间,只需要关注所要解决的问题。

Tips:由于进程的虚拟地址空间比物理内存大很多Linux还提供了一系列的机制,应对内存不足的问题,比如缓存的回收、交换分区Swap以及OOM等。

# 来看下进程的 虚拟内存 与 实际使用内存对比:
$ top
...
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND   
  9481 donald    20   0  1.181t 2.384g 119996 R   5.6 15.3  16:04.50 chrome                
  
# VIRT :是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
# RES : 进程实际使用的物理内存大小,但不包括 Swap 和共享内存。

# 发现 VIRT 是 1.181t,而 RES 才 2.384g,申请的虚拟内存空间远远大于实际所申请的物理内存

一文明白 Linux 虚拟内存

由上图可知:普通进程看到的是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。

当进程通过 malloc() 或者 mmap() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过 缺页异常 陷入内核中分配内存。

进程虚拟地址空间是通过 Paging分页)这种方式来映射为物理内存,分页过程:

一文明白 Linux 虚拟内存

  1. CPU 将要请求的虚拟地址传给 MMUMemory Management Unit, 内存管理单元)

  2. MMU 先在高速缓存 TLBTranslation Lookaside Buffer, 页表缓存)中查找转换关系

    • 如果找到相应的物理地址,则直接访问
    • 如果找不到,则在地址转换表(Page Table)里查找计算
  3. 最后,进程访问的虚拟地址就对应到实际的物理地址。

虚拟内存空间

虚拟地址空间的内部又被分为 内核空间用户空间 两部分,不同字长(也就是单个 CPU 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。

  • 内核空间(Kernal Space :这个空间只有内核程序可以访问。
  • 用户空间(User Space :这部分内存专门给应用程序使用。

比如最常见的 32 位和 64 位系统,如下所示:

  • 32位系统:内核空间占 1G,用户空间占 3G。
  • 64位系统:内核空间和用户空间均占 128T,剩下中间部分未定义。

一文明白 Linux 虚拟内存


虚拟内存空间的分布情况:以 32位系统为例

一文明白 Linux 虚拟内存

  1. 只读段:包括代码和常量等。数据段,包括全局变量等。
  2. 堆:包括动态分配的内存,从低地址开始向上增长。
  3. 文件映射段:包括动态库、共享内存等,从高地址开始向下增长。
  4. 栈:包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。