# highmem

> 原文：[Documentation/vm/highmem.txt](https://www.kernel.org/doc/Documentation/vm/highmem.txt)\
> &#x20;翻译：[@silenttung](https://github.com/dongfu8107)\
> &#x20;校订：[@lzufalcon](https://github.com/lzufalcon)<br>

### 目录

* [什么是高端内存？](/linux-doc/zh-cn/vm/highmem.md#toc_24146_28010_2)
* [临时虚拟映射](/linux-doc/zh-cn/vm/highmem.md#toc_24146_28010_3)
* [使用 `KMAP_ATOMIC`](/linux-doc/zh-cn/vm/highmem.md#toc_24146_28010_4)
* [临时映射的代价](/linux-doc/zh-cn/vm/highmem.md#toc_24146_28010_5)
* [i386 PAE](/linux-doc/zh-cn/vm/highmem.md#toc_24146_28010_6)

## 高端内存处理

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

### 什么是高端内存？

当物理地址大小接近或者达到虚拟内存的最大值时，会使用高端内存( `highmem` )。那时对于内核来说不可能维护所有可用的物理内存映射。这意味着内核开始对要被访问的物理内存片段使用临时映射。

这部分没有被永久映射的(物理)内存就是我们所提及的"高端内存"。高端内存的边界具体位于哪里，有各种各样的体系结构相关的限制。

举个例子，在 `i386` 体系结构中，我们选择将内核映射到每个进程的 `VM` 空间，这样我们不需要在进入内核态或者从内核态退出时无效所有的 `TLB`。这意味可用的虚拟内存空间( `i386` 是 `4G` )需要在用户地址空间和内核地址空间分割。

多个体系结构传统划分方法是 `3:1`， `3GiB` 属于用户空间，高端的 `1GiB` 是内核空间:

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

这意味着内核任意一次最多只能映射 `1GiB` 物理内存，但因为我们需要虚拟地址空间做其他的 事情-包括使用临时映射去访问剩余的物理内存-直接映射实际会更少(通常约 `896MiB` )。

其他用 `TLB` 来标记内存管理的体系结构有独立的内核空间和用户空间映射。然而一些硬件 (比如 `ARM` 系列)，当它们使用内存管理上下文标记时有有限的虚拟空间。

### 临时虚拟映射

内核包括以下几种创建临时映射的方法：

* `vmap()` . 该函数用来将多个物理页面长久映射到一个连续的虚拟地址空间。 `unmap` 时需要 做全局的同步。
* `kmap()` . 这个函数允许做一个独立页面的短时映射。它需要全局同步，但是有所摊销。 `kmap()` 以嵌入方式使用时易于死锁，所以新写的代码并不推荐使用。
* `kmap_atomic()` . 这个函数允许对一个独立页面做非常短的映射。因为这种映射局限于 所影响的 `CPU`，所以这个函数工作的很好，但也因此所涉及的任务要求一直在原来的 `CPU` 上 执行直到任务结束, 以免其他任务替换该映射。

  `kmap_atomic()` 也可能被中断上下文所使用，因为该函数不会休眠并且引用者也是直到 `kunmap_atomic()` 被调用时才会休眠.

  这可能被认为 `k[un]map_atomic()` 不会失败。

### 使用 `KMAP_ATOMIC`

何时何地使用 `kmap_atomic()` 是简单明了的。当代码想访问从高端内存分配所分配页面的内 容(看 `__GFP_HIGHMEM` )，比如当一个页面”在页缓冲“。有两个相关的API函数，可以像如下方式使用：

```
/* 寻找“感兴趣”的页面 */
struct page *page = find_get_page(mapping, offset);

/* 获得访问那个页面内容的地址 */
memset(vaddr, 0, PAGE_SIZE);

/* 解除映射那个页面 */
kunmap_atomic(vaddr);
```

注意 `kunmap_atomic()` 使用的是 `kmap_atomic()` 的结果，而不是 `kmap_atomic()` 的参数.

如果需要映射两个页面因为向从一个页面复制到另一个页面，必须保证 `kmap_aotmic` 调用严格 嵌套，比如：

```
vaddr1 = kmap_atomic(page1);
vaddr2 = kmap_atomic(page2);

memcpy(vaddr1, vaddr2, PAGE_SIZE);

kunmap_atomic(vaddr2);
kunmap_atomic(vaddr1);
```

### 临时映射的代价

创建临时映射的代价可谓是很大的。框架必须管理内核的页表，数据 `TLB`，也许或者必须处理 `MMU`的寄存器。

如果 `CONFIG_HIGHMEM` 没有选中，这是内核将尝试并且简单创建一个算术位的映射，这个映射 将转换页面结构体地址为一个指向页面内容的指针，"而不是篡改的映射"。在这种情况下， `unmap` 操作也许可能是一个空操作。

如果 `CONFIG_MMU` 没有被选中，就不可能有临时映射和高端内存。这种情况下，"算术方法"也将被 使用。

### i386 PAE

`i386` 体系结构在一些情况下允许 `32` 位机器访问到 `64GiB RAM`。这会带来很多后果:

* `Linux` 系统里对每个页要求有一个页帧结构，并且这些页帧要以永久映射方式存在，这 意味着:
* 最多有 `896M/sizeof(struce page)` 个页帧项； 一个页结构体大小为 `32` 字节, 那将 可存储大约 `112G`的页面\`; 然而kernel在内存中不仅仅需要保存页表。。。
* `PAE` 可以使得页表变的更大一些 `-` 这会使系统变慢，因为更多的数据要被访问需要 `TLB` 来回 转载等等. 一个优势是 `PAE` 有更多的 `PTE` 位并且能提供更高级的功能如 `NX` 和 `PAT`。

通常建议在 `32` 位机器上不要使用超过 `8GiB` 的内存-尽管更大的内存对你或者你的工作负载有用, 你得自己聪明一点-不要期待内核开发者当系统崩溃时真的会有多在意。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tinylab-1.gitbook.io/linux-doc/zh-cn/vm/highmem.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
