ARM的虚拟内存分割:
首先介绍一下Linux内核在虚拟内存中的何处执行。Linux内核的RAM基址在 PAGE_OFFSET 符号中定义,其位置可以配置。从 PAGE_OFFSET 的名字中可以看出,它是内核RAM第一页的虚拟内存偏移量。
你可以从四种内存分割方法中选择一种,这让我想起了快餐店。PAGE_OFFSET 符号目前在 arch/arm/Kconfig 中定义如下:
config PAGE_OFFSET
hex
default PHYS_OFFSET if !MMU
default 0x40000000 if VMSPLIT_1G
default 0x80000000 if VMSPLIT_2G
default 0xB0000000 if VMSPLIT_3G_OPT
default 0xC0000000
首先注意到:如果没有MMU(例如在运行ARM Cortex-R类设备,或旧的ARM7芯片时),就会在物理内存和虚拟内存之间建立1:1的映射。页表的作用仅仅是填充缓存,地址不会被重写。这种情况下,PAGE_OFFSET通常会位于地址 0x00000000。不带虚拟内存的Linux内核被称为“uClinux”,曾经是Linux内核的一个分支,多年后才被吸纳成为主线内核的一部分。
不使用虚拟内存,在Linux甚至任何POSIX类系统中都是另类。后文我们假设引导都会使用虚拟内存。
PAGE_OFFSET 虚拟内存分割符号会在上述地址处建立虚拟内存空间,供存放内核使用。所以Linux内核会将所有代码、状态和数据结构(包括虚拟内存到物理内存的转译表)保存在下面的虚拟内存地址之一:
- 0x40000000 ~ 0xFFFFFFFF
- 0x80000000 ~ 0xFFFFFFFF
- 0xB0000000 ~ 0xFFFFFFFF
- 0xC0000000 ~ 0xFFFFFFFF ★★★
在这四者中,最后一个 0xC0000000 ~ 0xFFFFFFFF 是目前最常见的,这样内核就有1GB的地址空间可以使用。
SofTool.CN Notes:
本教程均按 PAGE_OFFSET 设置为 0xC0000000 进行分割,即内核空间地址范围: 0xC0000000 ~ 0xFFFFFFFF ,用户空间地址范围:0x00000000 ~ 0xBFFFFFFF
Linux内核下方(下方的意思:<0xC0000000 的地址 )的内存用于用户空间的代码,地址范围为0x00000000-PAGE_OFFSET-1(通常地址位于0x00000000 ~ 0xBFFFFFFF,共3GB)。Unix习惯提供超额内存,即操作系统乐观地给程序提供的虚拟内存空间,其大小通常会超过可用的物理内存大小。每个新生成的用户空间进程都以为自己有3GB的虚拟内存可用!这种超额提供从上世纪七十年代就成了Unix的特点。
为什么有四种分割方式?
答案很简单:ARM在嵌入式系统中有大量应用,这些系统可能更重视用户空间(如通常的平板电脑、手机甚至台式电脑),也可能更重视内核空间(如路由器)。绝大多数系统都重视用户空间,或者内存很少,所以怎样分割其实关系不大(不论怎样分割,内存都会很拥挤),所以最常见的分割就是将 PAGE_OFFSET 设置为 0xC0000000。
上图:内核空间和用户空间之间最常见的虚拟内存分割位于0xC0000000。
有可能系统有很多内存,且更重视内核空间,例如:带有很多内存(如4GB RAM)的路由器或NAS。此时你可能希望内核能够将一些内存作为页面缓存和网络缓存使用,提高最常见的操作速度,所以你会希望分割出更多的内核内存,比如在极端情况下可以将 PAGE_OFFSET 设置为 0x40000000。
这个虚拟内存映射永远存在,即使内核在执行用户空间代码时也是。一直保持内核映射,可以非常快地进行用户空间到内核空间的上下文切换,这样当用户空间进程需要调用内核时,不需要进行任何页表替换。只需要启动一个软件陷阱,切换到特权模式(supervisor mode),执行内核代码即可,虚拟内存的配置不需要变化。
在不同的用户空间之间执行上下文切换也更快:只需要用一段预先定义好的物理RAM块替换页表的低端部分(通常会替换内核映射,因为它很简单)即可。这段预先定义好的物理RAM是线性映射的,甚至被存储在一个特殊的地方:快表(translation lookaside buffer,简称 TLB)。快表TLB位于CPU芯片上,是“非常快的转译表”,所以能更快地进入内核空间。这些地址永远存在,永远是线性映射的,而且永远不会产生页面错误。