blob: 2c0ee0cbf5c492b01d6dd45de914eb9986652084 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
.. 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
|