物理地址和虚拟地址的分布

上一节内容的学习我们知道了CPU是如何访问内存的,CPU拿到内存后就可以向其它人(kernel的其它模块、内核线程、用户空间进程、等等)提供服务,主要包括:

  • 以虚拟地址(VA)的形式,为应用程序提供远大于物理内存的虚拟地址空间(Virtual Address Space)
  • 每个进程都有独立的虚拟地址空间,不会相互影响,进而可提供非常好的内存保护(memory protection)
  • 提供内存映射(Memory Mapping)机制,以便把物理内存、I/O空间、Kernel Image、文件等对象映射到相应进程的地址空间中,方便进程的访问
  • 提供公平、高效的物理内存分配(Physical Memory Allocation)算法
  • 提供进程间内存共享的方法(以虚拟内存的形式),也称作Shared Virtual Memory

在提供这些服务之前需要对内存进行合理的划分和管理,下面让我们看下是如何划分的。

物理地址空间布局

Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区中,如图,分别为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。

  • ZONE_DMA 的范围是 0~16M,该区域的物理页面专门供 I/O 设备的 DMA 使用。之所以需要单独管理 DMA 的物理页面,是因为 DMA 使用物理地址访问内存,不经过 MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于 DMA。
  • ZONE_NORMAL 的范围是 16M~896M,该区域的物理页面是内核能够直接使用的。
  • ZONE_HIGHMEM 的范围是 896M~结束,该区域即为高端内存,内核不能直接使用。

Linux内核空间虚拟地址分布

在 Kernel Image 下面有 16M 的内核空间用于 DMA 操作。位于内核空间高端的 128M 地址主要由3部分组成,分别为 vmalloc area、持久化内核映射区、临时内核映射区。

由于 ZONE_NORMAL 和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如 Kernel 代码、GDT、IDT、PGD、mem_map 数组等放在 ZONE_NORMAL 里。而将用户数据、页表(PT)等不常用数据放在 ZONE_HIGHMEM 里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问 I/O 设备存储空间时,就使用 ioremap() 将位于物理地址高端的 mmio 区内存映射到内核空间的 vmalloc area 中,在使用完之后便断开映射关系。

Linux用户空间虚拟地址分布

用户进程的代码区一般从虚拟地址空间的 0x08048000 开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。

Linux物理地址和虚拟地址的关系

Linux 将 4G 的线性地址空间分为2部分,0~3G 为 user space,3G~4G 为 kernel space。

由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到 1G 的内核线性空间中,这显然不可能。于是,内核将 0~896M 的物理地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问 ZONE_DMA 和 ZONE_NORMAL 里的物理页面;此时内核剩下的 128M 线性地址空间不足以完全映射所有的 ZONE_HIGHMEM,Linux 采取了动态映射的方法,即按需的将 ZONE_HIGHMEM 里的物理页面映射到 kernel space 的最后 128M 线性地址空间里,使用完之后释放映射关系,以供其它物理页面映射。虽然这样存在效率的问题,但是内核毕竟可以正常的访问所有的物理地址空间了。

到这里我们应该知道了 Linux 是如何用虚拟地址来映射物理地址的,最后我们用一张图来总结一下:

原文发布于微信公众号 - 人人都是极客(rrgeek)

原文发表时间:2018-12-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券