0001 .. include:: ../disclaimer-zh_CN.rst
0002
0003 :Original: Documentation/mm/highmem.rst
0004
0005 :翻译:
0006
0007 司延腾 Yanteng Si <siyanteng@loongson.cn>
0008
0009 :校译:
0010
0011 ==========
0012 高内存处理
0013 ==========
0014
0015 作者: Peter Zijlstra <a.p.zijlstra@chello.nl>
0016
0017 .. contents:: :local:
0018
0019 高内存是什么?
0020 ==============
0021
0022 当物理内存的大小接近或超过虚拟内存的最大大小时,就会使用高内存(highmem)。在这一点上,内
0023 核不可能在任何时候都保持所有可用的物理内存的映射。这意味着内核需要开始使用它想访问的物理内
0024 存的临时映射。
0025
0026 没有被永久映射覆盖的那部分(物理)内存就是我们所说的 "高内存"。对于这个边界的确切位置,有
0027 各种架构上的限制。
0028
0029 例如,在i386架构中,我们选择将内核映射到每个进程的虚拟空间,这样我们就不必为内核的进入/退
0030 出付出全部的TLB作废代价。这意味着可用的虚拟内存空间(i386上为4GiB)必须在用户和内核空间之
0031 间进行划分。
0032
0033 使用这种方法的架构的传统分配方式是3:1,3GiB用于用户空间,顶部的1GiB用于内核空间。::
0034
0035 +--------+ 0xffffffff
0036 | Kernel |
0037 +--------+ 0xc0000000
0038 | |
0039 | User |
0040 | |
0041 +--------+ 0x00000000
0042
0043 这意味着内核在任何时候最多可以映射1GiB的物理内存,但是由于我们需要虚拟地址空间来做其他事
0044 情--包括访问其余物理内存的临时映射--实际的直接映射通常会更少(通常在~896MiB左右)。
0045
0046 其他有mm上下文标签的TLB的架构可以有独立的内核和用户映射。然而,一些硬件(如一些ARM)在使
0047 用mm上下文标签时,其虚拟空间有限。
0048
0049
0050 临时虚拟映射
0051 ============
0052
0053 内核包含几种创建临时映射的方法。下面的列表按照使用的优先顺序显示了它们。
0054
0055 * kmap_local_page()。这个函数是用来要求短期映射的。它可以从任何上下文(包括中断)中调用,
0056 但是映射只能在获取它们的上下文中使用。
0057
0058 在可行的情况下,这个函数应该比其他所有的函数优先使用。
0059
0060 这些映射是线程本地和CPU本地的,这意味着映射只能从这个线程中访问,并且当映射处于活动状
0061 态时,该线程与CPU绑定。即使线程被抢占了(因为抢占永远不会被函数禁用),CPU也不能通过
0062 CPU-hotplug从系统中拔出,直到映射被处理掉。
0063
0064 在本地的kmap区域中采取pagefaults是有效的,除非获取本地映射的上下文由于其他原因不允许
0065 这样做。
0066
0067 kmap_local_page()总是返回一个有效的虚拟地址,并且假定kunmap_local()不会失败。
0068
0069 嵌套kmap_local_page()和kmap_atomic()映射在一定程度上是允许的(最多到KMAP_TYPE_NR),
0070 但是它们的调用必须严格排序,因为映射的实现是基于堆栈的。关于如何管理嵌套映射的细节,
0071 请参见kmap_local_page() kdocs(包含在 "函数 "部分)。
0072
0073 * kmap_atomic(). 这允许对单个页面进行非常短的时间映射。由于映射被限制在发布它的CPU上,
0074 它表现得很好,但发布的任务因此被要求留在该CPU上直到它完成,以免其他任务取代它的映射。
0075
0076 kmap_atomic()也可以被中断上下文使用,因为它不睡眠,调用者也可能在调用kunmap_atomic()
0077 后才睡眠。
0078
0079 内核中对kmap_atomic()的每次调用都会创建一个不可抢占的段,并禁用缺页异常。这可能是
0080 未预期延迟的来源之一。因此用户应该选择kmap_local_page()而不是kmap_atomic()。
0081
0082 假设k[un]map_atomic()不会失败。
0083
0084 * kmap()。这应该被用来对单个页面进行短时间的映射,对抢占或迁移没有限制。它会带来开销,
0085 因为映射空间是受限制的,并且受到全局锁的保护,以实现同步。当不再需要映射时,必须用
0086 kunmap()释放该页被映射的地址。
0087
0088 映射变化必须广播到所有CPU(核)上,kmap()还需要在kmap的池被回绕(TLB项用光了,需要从第
0089 一项复用)时进行全局TLB无效化,当映射空间被完全利用时,它可能会阻塞,直到有一个可用的
0090 槽出现。因此,kmap()只能从可抢占的上下文中调用。
0091
0092 如果一个映射必须持续相对较长的时间,上述所有的工作都是必要的,但是内核中大部分的
0093 高内存映射都是短暂的,而且只在一个地方使用。这意味着在这种情况下,kmap()的成本大
0094 多被浪费了。kmap()并不是为长期映射而设计的,但是它已经朝着这个方向发展了,在较新
0095 的代码中强烈不鼓励使用它,前面的函数集应该是首选。
0096
0097 在64位系统中,调用kmap_local_page()、kmap_atomic()和kmap()没有实际作用,因为64位
0098 地址空间足以永久映射所有物理内存页面。
0099
0100 * vmap()。这可以用来将多个物理页长期映射到一个连续的虚拟空间。它需要全局同步来解除
0101 映射。
0102
0103 临时映射的成本
0104 ==============
0105
0106 创建临时映射的代价可能相当高。体系架构必须操作内核的页表、数据TLB和/或MMU的寄存器。
0107
0108 如果CONFIG_HIGHMEM没有被设置,那么内核会尝试用一点计算来创建映射,将页面结构地址转换成
0109 指向页面内容的指针,而不是去捣鼓映射。在这种情况下,解映射操作可能是一个空操作。
0110
0111 如果CONFIG_MMU没有被设置,那么就不可能有临时映射和高内存。在这种情况下,也将使用计算方法。
0112
0113
0114 i386 PAE
0115 ========
0116
0117 在某些情况下,i386 架构将允许你在 32 位机器上安装多达 64GiB 的内存。但这有一些后果:
0118
0119 * Linux需要为系统中的每个页面建立一个页帧结构,而且页帧需要驻在永久映射中,这意味着:
0120
0121 * 你最多可以有896M/sizeof(struct page)页帧;由于页结构体是32字节的,所以最终会有
0122 112G的页;然而,内核需要在内存中存储更多的页帧......
0123
0124 * PAE使你的页表变大--这使系统变慢,因为更多的数据需要在TLB填充等方面被访问。一个好处
0125 是,PAE有更多的PTE位,可以提供像NX和PAT这样的高级功能。
0126
0127 一般的建议是,你不要在32位机器上使用超过8GiB的空间--尽管更多的空间可能对你和你的工作
0128 量有用,但你几乎是靠你自己--不要指望内核开发者真的会很关心事情的进展情况。
0129
0130 函数
0131 ====
0132
0133 该API在以下内核代码中:
0134
0135 include/linux/highmem.h
0136
0137 include/linux/highmem-internal.h