.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/mm/highmem.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

==========
高内存处理
==========

作者: Peter Zijlstra <a.p.zijlstra@chello.nl>

.. contents:: :local:

高内存是什么？
==============

当物理内存的大小接近或超过虚拟内存的最大大小时，就会使用高内存（highmem）。在这一点上，内
核不可能在任何时候都保持所有可用的物理内存的映射。这意味着内核需要开始使用它想访问的物理内
存的临时映射。

没有被永久映射覆盖的那部分（物理）内存就是我们所说的 "高内存"。对于这个边界的确切位置，有
各种架构上的限制。

例如，在i386架构中，我们选择将内核映射到每个进程的虚拟空间，这样我们就不必为内核的进入/退
出付出全部的TLB作废代价。这意味着可用的虚拟内存空间（i386上为4GiB）必须在用户和内核空间之
间进行划分。

使用这种方法的架构的传统分配方式是3:1，3GiB用于用户空间，顶部的1GiB用于内核空间。::

		+--------+ 0xffffffff
		| Kernel |
		+--------+ 0xc0000000
		|        |
		| User   |
		|        |
		+--------+ 0x00000000

这意味着内核在任何时候最多可以映射1GiB的物理内存，但是由于我们需要虚拟地址空间来做其他事
情--包括访问其余物理内存的临时映射--实际的直接映射通常会更少（通常在~896MiB左右）。

其他有mm上下文标签的TLB的架构可以有独立的内核和用户映射。然而，一些硬件（如一些ARM）在使
用mm上下文标签时，其虚拟空间有限。


临时虚拟映射
============

内核包含几种创建临时映射的方法。下面的列表按照使用的优先顺序显示了它们。

* kmap_local_page()。这个函数是用来要求短期映射的。它可以从任何上下文（包括中断）中调用，
  但是映射只能在获取它们的上下文中使用。

  在可行的情况下，这个函数应该比其他所有的函数优先使用。

  这些映射是线程本地和CPU本地的，这意味着映射只能从这个线程中访问，并且当映射处于活跃状
  态时，线程被绑定到CPU上。尽管这个函数从来没有禁用过抢占，但在映射被处理之前，CPU不能
  通过CPU-hotplug从系统中拔出。

  在本地的kmap区域中采取pagefaults是有效的，除非获取本地映射的上下文由于其他原因不允许
  这样做。

  如前所述，缺页异常和抢占从未被禁用。没有必要禁用抢占，因为当上下文切换到一个不同的任务
  时，离开的任务的映射被保存，而进入的任务的映射被恢复。

  kmap_local_page()总是返回一个有效的虚拟地址，并且假定kunmap_local()不会失败。

  在CONFIG_HIGHMEM=n的内核中，对于低内存页，它返回直接映射的虚拟地址。只有真正的高内
  存页面才会被临时映射。因此，用户可以为那些已知不是来自ZONE_HIGHMEM的页面调用普通的
  page_address()。然而，使用kmap_local_page() / kunmap_local()总是安全的。

  虽然它比kmap()快得多，但在高内存的情况下，它对指针的有效性有限制。与kmap()映射相反，
  本地映射只在调用者的上下文中有效，不能传递给其他上下文。这意味着用户必须绝对保证返回
  地址的使用只限于映射它的线程。

  大多数代码可以被设计成使用线程本地映射。因此，用户在设计他们的代码时，应该尽量避免使用
  kmap()，将页面映射到将被使用的同一线程中，并优先使用kmap_local_page()。

  嵌套kmap_local_page()和kmap_atomic()映射在一定程度上是允许的（最多到KMAP_TYPE_NR），
  但是它们的调用必须严格排序，因为映射的实现是基于堆栈的。关于如何管理嵌套映射的细节，
  请参见kmap_local_page() kdocs（包含在 "函数 "部分）。

* kmap_atomic().  这允许对单个页面进行非常短的时间映射。由于映射被限制在发布它的CPU上，
  它表现得很好，但发布的任务因此被要求留在该CPU上直到它完成，以免其他任务取代它的映射。

  kmap_atomic()也可以被中断上下文使用，因为它不睡眠，调用者也可能在调用kunmap_atomic()
  后才睡眠。

  内核中对kmap_atomic()的每次调用都会创建一个不可抢占的段，并禁用缺页异常。这可能是
  未预期延迟的来源之一。因此用户应该选择kmap_local_page()而不是kmap_atomic()。

  假设k[un]map_atomic()不会失败。

* kmap()。这应该被用来对单个页面进行短时间的映射，对抢占或迁移没有限制。它会带来开销，
  因为映射空间是受限制的，并且受到全局锁的保护，以实现同步。当不再需要映射时，必须用
  kunmap()释放该页被映射的地址。

  映射变化必须广播到所有CPU（核）上，kmap()还需要在kmap的池被回绕（TLB项用光了，需要从第
   一项复用）时进行全局TLB无效化，当映射空间被完全利用时，它可能会阻塞，直到有一个可用的
   槽出现。因此，kmap()只能从可抢占的上下文中调用。

  如果一个映射必须持续相对较长的时间，上述所有的工作都是必要的，但是内核中大部分的
  高内存映射都是短暂的，而且只在一个地方使用。这意味着在这种情况下，kmap()的成本大
  多被浪费了。kmap()并不是为长期映射而设计的，但是它已经朝着这个方向发展了，在较新
  的代码中强烈不鼓励使用它，前面的函数集应该是首选。

  在64位系统中，调用kmap_local_page()、kmap_atomic()和kmap()没有实际作用，因为64位
  地址空间足以永久映射所有物理内存页面。

* vmap()。这可以用来将多个物理页长期映射到一个连续的虚拟空间。它需要全局同步来解除
  映射。

临时映射的成本
==============

创建临时映射的代价可能相当高。体系架构必须操作内核的页表、数据TLB和/或MMU的寄存器。

如果CONFIG_HIGHMEM没有被设置，那么内核会尝试用一点计算来创建映射，将页面结构地址转换成
指向页面内容的指针，而不是去捣鼓映射。在这种情况下，解映射操作可能是一个空操作。

如果CONFIG_MMU没有被设置，那么就不可能有临时映射和高内存。在这种情况下，也将使用计算方法。


i386 PAE
========

在某些情况下，i386 架构将允许你在 32 位机器上安装多达 64GiB 的内存。但这有一些后果:

* Linux需要为系统中的每个页面建立一个页帧结构，而且页帧需要驻在永久映射中，这意味着：

* 你最多可以有896M/sizeof(struct page)页帧；由于页结构体是32字节的，所以最终会有
  112G的页；然而，内核需要在内存中存储更多的页帧......

* PAE使你的页表变大--这使系统变慢，因为更多的数据需要在TLB填充等方面被访问。一个好处
  是，PAE有更多的PTE位，可以提供像NX和PAT这样的高级功能。

一般的建议是，你不要在32位机器上使用超过8GiB的空间--尽管更多的空间可能对你和你的工作
量有用，但你几乎是靠你自己--不要指望内核开发者真的会很关心事情的进展情况。

函数
====

该API在以下内核代码中:

include/linux/highmem.h

include/linux/highmem-internal.h
