Post

虚拟地址转换[一] - 基本流程

LinuxWindows等操作系统中,为什么不直接使用Physical Address(物理地址),而要用Virtual Address(虚拟地址)呢(在Intel的手册中也被称为Linear Address,具体原因请参考这篇文章)?

因为使用虚拟地址可以带来诸多好处:

  1. 在支持多进程的系统中,如果各个进程的镜像文件都使用物理地址,则在加载到同一物理内存空间的时候,可能发生冲突。
  2. 直接使用物理地址,不便于进行进程地址空间的隔离。
  3. 物理内存是有限的,在物理内存整体吃紧的时候,可以让多个进程通过分时复用的方法共享一个物理页面(某个进程需要保存的内容可以暂时swap到外部的disk/flash),这有点类似于多线程分时复用共享CPU的方式。

既然使用虚拟地址,就涉及到将虚拟地址转换为物理地址的过程,这需要MMUMemory Management Unit)和页表(page table)的共同参与。

🧩 MMU

MMU是处理器/核(processer)中的一个硬件单元,通常每个核有一个MMUMMU由两部分组成:TLB(Translation Lookaside Buffer)table walk unit

img

🌲 Page Table

page table是每个进程独有的,是软件实现的,是存储在main memory(比如DDR)中的。

🍃 Address Translation

因为访问内存中的页表相对耗时,尤其是在现在普遍使用多级页表的情况下,需要多次的内存访问,为了加快访问速度,系统设计人员为page table设计了一个硬件缓存 - TLBCPU首先会在TLB中查找,因为在TLB中找起来很快。

TLB之所以快:

  • 一是因为它含有的entries的数目较少
  • 二是TLB是集成进CPU的,它几乎可以按照CPU的速度运行。

img

如果在TLB中找到了含有该虚拟地址的entryTLB hit),则可从该entry【1】中直接获取对应的物理地址,否则就得去查找当前进程的page table(这里其实可能用到paging structure caches)。这个时候,组成MMU的另一个部分table walk unit就被召唤出来了,这里面的table就是page table

使用table walk unit硬件单元来查找page table的方式被称为hardware TLB miss handling,通常被CISC架构的处理器(比如IA-32)所采用。它要在page table中查找不到,出现page fault的时候才会交由软件(操作系统)处理。

与之相对的通常被RISC架构的处理器(比如Alpha)采用的software TLB miss handlingTLB missCPU就不再参与了,由操作系统通过软件的方式来查找page table。使用硬件的方式更快,而使用软件的方式灵活性更强。IA-64提供了一种混合模式,可以兼顾两者的优点。

如果在page table中找到了该虚拟地址对应的entryppresent)位是1,说明该虚拟地址对应的物理页面当前驻留在内存中,也就是page table hit。找到了还没完,接下来还有两件事要做:

  1. 既然是因为在TLB里找不到才找到这儿来的,自然要更新TLB
  2. 进行权限检测,包括可读/可写/可执行权限,user/supervisor模式权限等。如果没有正确的权限,将触发SIGSEGVSegmentation Fault)。

如果该虚拟地址对应的entryp位是0,就会触发page fault,可能有这几种情况:

  1. 这个虚拟地址被分配后还从来没有被access过(比如malloc之后还没有操作分配到的空间,则不会真正分配物理内存)。触发page fault后分配物理内存,也就是demand paging,有了确定的demand了之后才分,然后将p位置1。
  2. 对应的这个物理页面的内容被换出到外部的disk/flash了,这个时候page table entry里存的是换出页面在外部swap area里暂存的位置,可以将其换回物理内存,再次建立映射,然后将p位置1。

关于在TLB中具体是怎么找的,在page table中又是怎么”walk”的,请看下回分解。

【1】:entry有入口的意思,对于TLB和单级页表的一个entry,就是指向对应page的首地址(入口);对于后文介绍的多级页表的一个entry,就是指向下一级页表的首地址(入口)。


🧭 如何判断当前系统是否启用了swap

✅ 方法一:看当前swap是否启用

1
$ swapon --show

或:

1
$ cat /proc/swaps

如果没有输出,说明swap没启用。否则你会看到:

1
2
3
$ swapon --show
NAME      TYPE   SIZE USED PRIO
/swapfile file 923.2M   0B   -2

📌 如何设置或启用swap

🧱 创建swap文件(常见做法):

1
2
3
4
$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

验证:

1
$ swapon --show

让它重启后生效:

1
$ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

📌 关键概念解析

kswapd是什么?

  • Linux内核的内存回收守护线程之一,内核中表现为kswapd0kswapd1等。
  • 它在系统空闲或内存水位低时启动,用于回收页缓存和匿名页
  • 主要作用:平衡zonefree pages数量,维持水位。

✅ 哪些页会被写入swap

只有满足以下条件的页才会被写入swap

  • 匿名页anonymous page),即没有文件映射的内存页,比如heapstackmalloc出来的内存。
  • 当前页不是active或者最近没被访问过(LRU判定)
  • 被回收后不能直接释放的,就会被写入swapfile(称为swap-out

🧠 swapfilekswapd中的角色

kswapd 工作流程中发现:

  1. 当前zone(如DMA/Normal/HighMem)低于low watermark
  2. kswapd开始扫描页表、回收页
  3. 回收的页中发现匿名页,无法直接释放
  4. 则这些匿名页会被写入swap(也就是/swapfile

🔁 后续如果这些页被再次访问,会触发page fault,然后/swapfile中读回内存(称为swap-in)。

🧪 实验看看swapfile是否真的生效:

步骤:

1
2
3
4
$ sudo apt update
$ sudo apt install stress
# 启动一个吃内存的程序(建议先关闭无关程序)
$ stress --vm 1 --vm-bytes 2G --timeout 30s

然后你可以观察swap是否被用到了:

1
$ watch -n 1 free -h

你会看到Swap: 那一行的USED开始增长,说明/swapfile正在被写入。

🔍 内核源码角度(简要)

在内核源码中(如mm/vmscan.c),kswapd最终会调用到:

1
2
3
4
5
shrink_node()
  └── shrink_lruvec()
        └── shrink_list()
              └── shrink_anon()    // 针对匿名页
                    └── try_to_swap_out()

try_to_swap_out()会把匿名页放入swap

1
2
swap_entry_t entry = get_swap_page()
set_pte_at(...)  // 把 PTE 设置为 swap entry

最终数据写入/swapfile/dev/sdX swap设备。


/etc/fstabFile System Table

  • 作用:静态配置文件,列出系统启动时自动挂载的文件系统和挂载参数。
  • 位置:通常是只读的文本文件,系统管理员手动编辑。
  • 内容:定义了设备、挂载点、文件系统类型、挂载选项等。
  • 作用时间点:系统启动时由挂载程序读取,决定挂载哪些分区。

/etc/mtabMounted File Systems Table

  • 作用:记录当前系统中实际挂载的所有文件系统信息,反映实时挂载状态。
  • 内容:包含设备名、挂载点、文件系统类型、挂载选项等当前使用信息。
  • 更新方式
    • 过去是普通文件,由挂载和卸载命令动态更新。
    • 现代Linux发行版多用/proc/mounts(内核虚拟文件系统)替代/etc/mtab,通过软链接实现。
  • 用途
    • 命令如mount(不带参数时)查看当前挂载状态
    • umount和其他工具使用它判断当前挂载状态

转载:虚拟地址转换[一] - 基本流程

This post is licensed under CC BY 4.0 by the author.