地址类型
一个虚拟内存系统, 意味着用户程序见到的地址不直接对应于硬件使用
的物理地址. 虚拟内存引入了一个间接层, 它允许了许多好事情. 有了虚拟内存, 系统重
运行的程序可以分配远多于物理上可用的内存; 确实, 即便一个单个进程可拥有一个虚拟
地址空间大于系统的物理内存. 虚拟内存也允许程序对进程的地址空间运用多种技巧, 包
括映射成员的内存到设备内存.
至此, 我们已经讨论了虚拟和物理地址, 但是许多细节被掩盖过去了. Linux 系统处理几
种类型的地址, 每个有它自己的含义. 不幸的是, 内核代码不是一直非常清楚确切地在每
个情况下在使用什么类型地地址, 因此程序员必须小心.
下面是一个 Linux 中使用的地址类型列表. 图 Linux 中使用的地址类型显示了这个地址
类型如何关联到物理内存.

Linux 中使用的地址类型
User virtual addresses
这是被用户程序见到的常规地址. 用户地址在长度上是 32 位或者 64 位, 依赖底
层的硬件结构, 并且每个进程有它自己的虚拟地址空间.
Physical addresses
在处理器和系统内存之间使用的地址. 物理地址是 32- 或者 64-位的量; 甚至
32-位系统在某些情况下可使用更大的物理地址.
Bus addresses
在外设和内存之间使用的地址. 经常, 它们和被处理器使用的物理地址相同, 但是
这不是必要的情况. 一些体系可提供一个 I/O 内存管理单元(IOMMU), 它在总线和
主内存之间重映射地址. 一个 IOMMU 可用多种方法使事情简单(例如, 使散布在内
存中的缓冲对设备看来是连续的, 例如), 但是当设定 DMA 操作时对 IOMMU 编程
是一个必须做的额外的步骤. 总线地址是高度特性依赖的, 当然.
Kernel logical addresses
这些组成了正常的内核地址空间. 这些地址映射了部分(也许全部)主存并且常常被
当作它们是物理内存来对待. 在大部分的体系上, 逻辑地址和它们的相关物理地址
只差一个常量偏移. 逻辑地址使用硬件的本地指针大小并且, 因此, 可能不能在重
装备的 32-位系统上寻址所有的物理内存. 逻辑地址常常存储于 unsigned long
或者 void * 类型的变量中. 从 kmalloc 返回的内存有内核逻辑地址.
Kernel virtual addresses
内核虚拟地址类似于逻辑地址, 它们都是从内核空间地址到物理地址的映射. 内核
虚拟地址不必有逻辑地址空间具备的线性的, 一对一到物理地址的映射, 但是. 所有的逻辑地址是内核虚拟地址, 但是许多内核虚拟地址不是逻辑地址. 例如,
vmalloc 分配的内存有虚拟地址(但没有直接物理映射). kmap 函数(本章稍后描述)
也返回虚拟地址. 虚拟地址常常存储于指针变量.
如果你有逻辑地址, 宏 __pa() ( 在 <asm/page.h> 中定义)返回它的关联的物理地址.
物理地址可被映射回逻辑地址使用 __va(), 但是只给低内存页.
不同的内核函数需要不同类型地址. 如果有不同的 C 类型被定义可能不错, 这样请求的
地址类型是明确的, 但是我们没有这样的好运. 在本章, 我们尽力对在哪里使用哪种类型
地址保持清晰.
物理地址和页
物理内存被划分为离散的单元称为页. 系统的许多内部内存处理在按页的基础上完成. 页
大小一个体系不同于另一个, 尽管大部分系统当前使用 4096-字节的页. 常量 PAGE_SIZE
(定义在 <asm/page.h>) 给出了页大小在任何给定的体系上.
如果你查看一个内存地址 - 虚拟或物理 - 它可分为一个页号和一个页内的偏移. 如果使
用 4096-字节页, 例如, 12 位低有效位是偏移, 并且剩下的, 高位指示页号. 如果你丢
弃偏移并且向右移动剩下的部分 offset 位, 结果被称为一个页帧号 (PFN). 移位来在页
帧号和地址之间转换是一个相当普通的操作. 宏 PAGE_SHIFT 告诉必须移动多少位来进行
这个转换.
高和低内存
逻辑和内核虚拟地址之间的不同在配备大量内存的 32-位系统中被突出. 用 32 位, 可能
寻址 4 G 内存. 但是, 直到最近, 在 32-位 系统的 Linux 被限制比那个少很多的内存,
因为它建立虚拟地址的方式.
内核( 在 x86 体系上, 在缺省配置里) 在用户空间和内核之间划分 4-G 虚拟地址; 在 2
个上下文中使用同一套映射. 一个典型的划分分出 3 GB 给用户空间, 和 1 GB 给内核空
间.
47\] 内核的代码和数据结构必须要适合这个空间, 但是内核地址空间最大的消费者是物
理内存的虚拟映射. 内核不能直接操作没有映射到内核的地址空间. 内核, 换句话说, 需
要它自己的虚拟地址给任何它必须直接接触的内存. 因此, 多年来, 能够被内核处理的的
最大量的物理内存是能够映射到虚拟地址的内核部分的数量, 减去内核代码自身需要的空
间. 结果, 基于 x86 的 Linux 系统可以工作在最多稍小于 1 GB 物理内存.
为应对更多内存的商业压力而不破坏 32-位 应用和系统的兼容性, 处理器制造商已经增
加了"地址扩展"特性到他们的产品中. 结果, 在许多情况下, 即便 32-位 处理器也能够
寻址多于 4GB 物理内存. 但是, 多少内存可被直接用逻辑地址映射的限制还存在. 这样
内存的最低部分(上到 1 或 2 GB, 根据硬件和内核配置)有逻辑地址; 剩下的(高内存)没
有. 在存取一个特定高地址页之前, 内核必须建立一个明确的虚拟映射来使这个也在内核
地址空间可用. 因此, 许多内核数据结构必须放在低内存; 高内存用作被保留为用户进程
页.
术语"高内存"对有些人可能是疑惑的, 特别因为它在 PC 世界里有其他的含义. 因此, 为
清晰起见, 我们将定义这些术语:
Low memory
逻辑地址在内核空间中存在的内存. 在大部分每个系统你可能会遇到, 所有的内存
都是低内存.
High memory
逻辑地址不存在的内存, 因为它在为内核虚拟地址设置的地址范围之外.
在 i386 系统上, 低和高内存之间的分界常常设置在刚刚在 1 GB 之下, 尽管那个边界在
内核配置时可被改变. 这个边界和在原始 PC 中有的老的 640 KB 限制没有任何关联, 并
且它的位置不是硬件规定的. 相反, 它是, 内核自身设置的一个限制当它在内核和用户空
间之间划分 32-位地址空间时.
我们将指出使用高内存的限制, 随着我们在本章遇到它们时
## 内存映射和 struct page
历史上, 内核已使用逻辑地址来引用物理内存页. 高内存支持的增加, 但是, 已暴露这个
方法的一个明显的问题 -- 逻辑地址对高内存不可用. 因此, 处理内存的内核函数更多在
使用指向 struct page 的指针来代替(在 \