|
| 1 | +.. SPDX-License-Identifier: GPL-2.0 |
| 2 | +
|
| 3 | +.. include:: ../disclaimer-zh_CN.rst |
| 4 | + |
| 5 | +:Original: Documentation/mm/physical_memory.rst |
| 6 | + |
| 7 | +:翻译: |
| 8 | + |
| 9 | + 王亚鑫 Yaxin Wang < [email protected]> |
| 10 | + |
| 11 | +======== |
| 12 | +物理内存 |
| 13 | +======== |
| 14 | + |
| 15 | +Linux可用于多种架构,因此需要一个与架构无关的抽象来表示物理内存。本章描述 |
| 16 | +了管理运行系统中物理内存的结构。 |
| 17 | + |
| 18 | +第一个与内存管理相关的主要概念是 `非一致性内存访问(NUMA) |
| 19 | +<https://en.wikipedia.org/wiki/Non-uniform_memory_access>` |
| 20 | + |
| 21 | +在多核和多插槽机器中,内存可能被组织成不同的存储区,这些存储区根据与处理器 |
| 22 | +的距离“不同”而有不同的访问开销。例如,可能为每个CPU分配内存存储区,或者为 |
| 23 | +外围设备在附近分配一个非常适合DMA的内存存储区。 |
| 24 | + |
| 25 | +每个存储区被称为一个节点,节点在Linux中表示为 ``struct pglist_data``, |
| 26 | +即使是在UMA架构中也是这样表示。该结构总是通过 ``pg_data_t`` 来引用。特 |
| 27 | +定节点的 ``pg_data_t`` 结构体可以通过NODE_DATA(nid)引用,其中nid被称 |
| 28 | +为该节点的ID。 |
| 29 | + |
| 30 | +对于非一致性内存访问(NUMA)架构,节点数据结构在引导时由特定于架构的代码早 |
| 31 | +期分配。通常,这些结构在其所在的内存区上本地分配。对于一致性内存访问(UMA) |
| 32 | +架构,只使用一个静态的 ``pg_data_t`` 结构体,称为 ``contig_page_data``。 |
| 33 | +节点将会在 :ref:`节点 <nodes>` 章节中进一步讨论。 |
| 34 | + |
| 35 | +整个物理内存被划分为一个或多个被称为区域的块,这些区域表示内存的范围。这 |
| 36 | +些范围通常由访问内存的架构限制来决定。在节点内,与特定区域对应的内存范围 |
| 37 | +由 ``struct zone`` 结构体描述,该结构被定义为 ``zone_t``,每种区域都 |
| 38 | +属于以下描述类型的一种。 |
| 39 | + |
| 40 | +* ``ZONE_DMA`` 和 ``ZONE_DMA32`` 在历史上代表适用于DMA的内存,这些 |
| 41 | + 内存由那些不能访问所有可寻址内存的外设访问。多年来,已经有了更好、更稳 |
| 42 | + 固的接口来获取满足特定DMA需求的内存(这些接口由 |
| 43 | + Documentation/core-api/dma-api.rst 文档描述),但是 ``ZONE_DMA`` |
| 44 | + 和 ``ZONE_DMA32`` 仍然表示访问受限的内存范围。 |
| 45 | + |
| 46 | +取决于架构的不同,这两种区域可以在构建时通过关闭 ``CONFIG_ZONE_DMA`` 和 |
| 47 | +``CONFIG_ZONE_DMA32`` 配置选项来禁用。一些64位的平台可能需要这两种区域, |
| 48 | +因为他们支持具有不同DMA寻址限制的外设。 |
| 49 | + |
| 50 | +* ``ZONE_NORMAL`` 是普通内存的区域,这种内存可以被内核随时访问。如果DMA |
| 51 | + 设备支持将数据传输到所有可寻址的内存区域,那么可在该区域的页面上执行DMA |
| 52 | + 操作。``ZONE_NORMAL`` 总是开启的。 |
| 53 | + |
| 54 | +* ``ZONE_HIGHMEM`` 是指那些没有在内核页表中永久映射的物理内存部分。该区 |
| 55 | + 域的内存只能通过临时映射被内核访问。该区域只在某些32位架构上可用,并且是 |
| 56 | + 通过 ``CONFIG_HIGHMEM`` 选项开启。 |
| 57 | + |
| 58 | +* ``ZONE_MOVABLE`` 是指可访问的普通内存区域,就像 ``ZONE_NORMAL`` |
| 59 | + 一样。不同之处在于 ``ZONE_MOVABLE`` 中的大多数页面内容是可移动的。 |
| 60 | + 这意味着这些页面的虚拟地址不会改变,但它们的内容可能会在不同的物理页面 |
| 61 | + 之间移动。通常,在内存热插拔期间填充 ``ZONE_MOVABLE``,在启动时也可 |
| 62 | + 以使用 ``kernelcore``、``movablecore`` 和 ``movable_node`` |
| 63 | + 这些内核命令行参数来填充。更多详细信息,请参阅内核文档 |
| 64 | + Documentation/mm/page_migration.rst 和 |
| 65 | + Documentation/admin-guide/mm/memory-hotplug.rst。 |
| 66 | + |
| 67 | +* ``ZONE_DEVICE`` 表示位于持久性内存(PMEM)和图形处理单元(GPU) |
| 68 | + 等设备上的内存。它与RAM区域类型有不同的特性,并且它的存在是为了提供 |
| 69 | + :ref:`struct page<Pages>` 结构和内存映射服务,以便设备驱动程序能 |
| 70 | + 识别物理地址范围。``ZONE_DEVICE`` 通过 ``CONFIG_ZONE_DEVICE`` |
| 71 | + 选项开启。 |
| 72 | + |
| 73 | +需要注意的是,许多内核操作只能使用 ``ZONE_NORMAL`` 来执行,因此它是 |
| 74 | +性能最关键区域。区域在 :ref:`区域 <zones>` 章节中有更详细的讨论。 |
| 75 | + |
| 76 | +节点和区域范围之间的关系由固件报告的物理内存映射决定,另外也由内存寻址 |
| 77 | +的架构约束以及内核命令行中的某些参数决定。 |
| 78 | + |
| 79 | +例如,在具有2GB RAM的x86统一内存架构(UMA)机器上运行32位内核时,整 |
| 80 | +个内存将位于节点0,并且将有三个区域: ``ZONE_DMA``、 ``ZONE_NORMAL`` |
| 81 | +和 ``ZONE_HIGHMEM``:: |
| 82 | + |
| 83 | + 0 2G |
| 84 | + +-------------------------------------------------------------+ |
| 85 | + | node 0 | |
| 86 | + +-------------------------------------------------------------+ |
| 87 | + |
| 88 | + 0 16M 896M 2G |
| 89 | + +----------+-----------------------+--------------------------+ |
| 90 | + | ZONE_DMA | ZONE_NORMAL | ZONE_HIGHMEM | |
| 91 | + +----------+-----------------------+--------------------------+ |
| 92 | + |
| 93 | + |
| 94 | +在内核构建时关闭 ``ZONE_DMA`` 开启 ``ZONE_DMA32``,并且具有16GB |
| 95 | +RAM平均分配在两个节点上的arm64机器上,使用 ``movablecore=80%`` 参数 |
| 96 | +启动时,``ZONE_DMA32``、``ZONE_NORMAL`` 和 ``ZONE_MOVABLE`` |
| 97 | +位于节点0,而 ``ZONE_NORMAL`` 和 ``ZONE_MOVABLE`` 位于节点1:: |
| 98 | + |
| 99 | + |
| 100 | + 1G 9G 17G |
| 101 | + +--------------------------------+ +--------------------------+ |
| 102 | + | node 0 | | node 1 | |
| 103 | + +--------------------------------+ +--------------------------+ |
| 104 | + |
| 105 | + 1G 4G 4200M 9G 9320M 17G |
| 106 | + +---------+----------+-----------+ +------------+-------------+ |
| 107 | + | DMA32 | NORMAL | MOVABLE | | NORMAL | MOVABLE | |
| 108 | + +---------+----------+-----------+ +------------+-------------+ |
| 109 | + |
| 110 | + |
| 111 | +内存存储区可能位于交错的节点。在下面的例子中,一台x86机器有16GB的RAM分 |
| 112 | +布在4个内存存储区上,偶数编号的内存存储区属于节点0,奇数编号的内存条属于 |
| 113 | +节点1:: |
| 114 | + |
| 115 | + 0 4G 8G 12G 16G |
| 116 | + +-------------+ +-------------+ +-------------+ +-------------+ |
| 117 | + | node 0 | | node 1 | | node 0 | | node 1 | |
| 118 | + +-------------+ +-------------+ +-------------+ +-------------+ |
| 119 | + |
| 120 | + 0 16M 4G |
| 121 | + +-----+-------+ +-------------+ +-------------+ +-------------+ |
| 122 | + | DMA | DMA32 | | NORMAL | | NORMAL | | NORMAL | |
| 123 | + +-----+-------+ +-------------+ +-------------+ +-------------+ |
| 124 | + |
| 125 | +在这种情况下,节点0将覆盖从0到12GB的内存范围,而节点1将覆盖从4GB到16GB |
| 126 | +的内存范围。 |
| 127 | + |
| 128 | +.. _nodes_zh_CN: |
| 129 | + |
| 130 | +节点 |
| 131 | +==== |
| 132 | + |
| 133 | +正如我们所提到的,内存中的每个节点由 ``pg_data_t`` 描述,通过 |
| 134 | +``struct pglist_data`` 结构体的类型定义。在分配页面时,默认情况下,Linux |
| 135 | +使用节点本地分配策略,从离当前运行CPU的最近节点分配内存。由于进程倾向于在同 |
| 136 | +一个CPU上运行,很可能会使用当前节点的内存。分配策略可以由用户控制,如内核文 |
| 137 | +档 Documentation/admin-guide/mm/numa_memory_policy.rst 中所述。 |
| 138 | + |
| 139 | +大多数NUMA(非统一内存访问)架构维护了一个指向节点结构的指针数组。这些实际 |
| 140 | +的结构在启动过程中的早期被分配,这时特定于架构的代码解析了固件报告的物理内 |
| 141 | +存映射。节点初始化的大部分工作是在由free_area_init()实现的启动过程之后 |
| 142 | +完成,该函数在后面的小节 :ref:`初始化 <initialization>` 中有详细描述。 |
| 143 | + |
| 144 | +除了节点结构,内核还维护了一个名为 ``node_states`` 的 ``nodemask_t`` |
| 145 | +位掩码数组。这个数组中的每个位掩码代表一组特定属性的节点,这些属性由 |
| 146 | +``enum node_states`` 定义,定义如下: |
| 147 | + |
| 148 | +``N_POSSIBLE`` |
| 149 | +节点可能在某个时刻上线。 |
| 150 | + |
| 151 | +``N_ONLINE`` |
| 152 | +节点已经上线。 |
| 153 | + |
| 154 | +``N_NORMAL_MEMORY`` |
| 155 | +节点拥有普通内存。 |
| 156 | + |
| 157 | +``N_HIGH_MEMORY`` |
| 158 | +节点拥有普通或高端内存。当关闭 ``CONFIG_HIGHMEM`` 配置时, |
| 159 | +也可以称为 ``N_NORMAL_MEMORY``。 |
| 160 | + |
| 161 | +``N_MEMORY`` |
| 162 | +节点拥有(普通、高端、可移动)内存。 |
| 163 | + |
| 164 | +``N_CPU`` |
| 165 | +节点拥有一个或多个CPU。 |
| 166 | + |
| 167 | +对于具有上述属性的每个节点,``node_states[<property>]`` |
| 168 | +掩码中对应于节点ID的位会被置位。 |
| 169 | + |
| 170 | +例如,对于具有常规内存和CPU的节点2,第二个bit将被设置:: |
| 171 | + |
| 172 | + node_states[N_POSSIBLE] |
| 173 | + node_states[N_ONLINE] |
| 174 | + node_states[N_NORMAL_MEMORY] |
| 175 | + node_states[N_HIGH_MEMORY] |
| 176 | + node_states[N_MEMORY] |
| 177 | + node_states[N_CPU] |
| 178 | + |
| 179 | +有关使用节点掩码(nodemasks)可能进行的各种操作,请参考 |
| 180 | +``include/linux/nodemask.h``。 |
| 181 | + |
| 182 | +除此之外,节点掩码(nodemasks)提供用于遍历节点的宏,即 |
| 183 | +``for_each_node()`` 和 ``for_each_online_node()``。 |
| 184 | + |
| 185 | +例如,要为每个在线节点调用函数 foo(),可以这样操作:: |
| 186 | + |
| 187 | + for_each_online_node(nid) { |
| 188 | + pg_data_t *pgdat = NODE_DATA(nid); |
| 189 | + |
| 190 | + foo(pgdat); |
| 191 | + } |
| 192 | + |
| 193 | +节点数据结构 |
| 194 | +------------ |
| 195 | + |
| 196 | +节点结构 ``struct pglist_data`` 在 ``include/linux/mmzone.h`` |
| 197 | +中声明。这里我们将简要描述这个结构体的字段: |
| 198 | + |
| 199 | +通用字段 |
| 200 | +~~~~~~~~ |
| 201 | + |
| 202 | +``node_zones`` |
| 203 | +表示该节点的区域列表。并非所有区域都可能被填充,但这是 |
| 204 | +完整的列表。它被该节点的node_zonelists以及其它节点的 |
| 205 | +node_zonelists引用。 |
| 206 | + |
| 207 | +``node_zonelists`` |
| 208 | +表示所有节点中所有区域的列表。此列表定义了分配内存时首选的区域 |
| 209 | +顺序。``node_zonelists`` 在核心内存管理结构初始化期间, |
| 210 | +由 ``mm/page_alloc.c`` 中的 ``build_zonelists()`` |
| 211 | +函数设置。 |
| 212 | + |
| 213 | +``nr_zones`` |
| 214 | +表示此节点中已填充区域的数量。 |
| 215 | + |
| 216 | +``node_mem_map`` |
| 217 | +对于使用FLATMEM内存模型的UMA系统,0号节点的 ``node_mem_map`` |
| 218 | +表示每个物理帧的struct pages数组。 |
| 219 | + |
| 220 | +``node_page_ext`` |
| 221 | +对于使用FLATMEM内存模型的UMA系统,0号节点的 ``node_page_ext`` |
| 222 | +是struct pages的扩展数组。只有在构建时开启了 ``CONFIG_PAGE_EXTENSION`` |
| 223 | +选项的内核中才可用。 |
| 224 | + |
| 225 | +``node_start_pfn`` |
| 226 | +表示此节点中起始页面帧的页面帧号。 |
| 227 | + |
| 228 | +``node_present_pages`` |
| 229 | +表示此节点中存在的物理页面的总数。 |
| 230 | + |
| 231 | +``node_spanned_pages`` |
| 232 | +表示包括空洞在内的物理页面范围的总大小。 |
| 233 | + |
| 234 | +``node_size_lock`` |
| 235 | +一个保护定义节点范围字段的锁。仅在开启了 ``CONFIG_MEMORY_HOTPLUG`` 或 |
| 236 | +``CONFIG_DEFERRED_STRUCT_PAGE_INIT`` 配置选项中的某一个时才定义。提 |
| 237 | +供了 ``pgdat_resize_lock()`` 和 ``pgdat_resize_unlock()`` 用来操作 |
| 238 | +``node_size_lock``,而无需检查 ``CONFIG_MEMORY_HOTPLUG`` 或 |
| 239 | +``CONFIG_DEFERRED_STRUCT_PAGE_INIT`` 选项。 |
| 240 | + |
| 241 | +``node_id`` |
| 242 | +节点的节点ID(NID),从0开始。 |
| 243 | + |
| 244 | +``totalreserve_pages`` |
| 245 | +这是每个节点保留的页面,这些页面不可用于用户空间分配。 |
| 246 | + |
| 247 | +``first_deferred_pfn`` |
| 248 | +如果大型机器上的内存初始化被推迟,那么第一个PFN(页帧号)是需要初始化的。 |
| 249 | +在开启了 ``CONFIG_DEFERRED_STRUCT_PAGE_INIT`` 选项时定义。 |
| 250 | + |
| 251 | +``deferred_split_queue`` |
| 252 | +每个节点的大页队列,这些大页的拆分被推迟了。仅在开启了 ``CONFIG_TRANSPARENT_HUGEPAGE`` |
| 253 | +配置选项时定义。 |
| 254 | + |
| 255 | +``__lruvec`` |
| 256 | +每个节点的lruvec持有LRU(最近最少使用)列表和相关参数。仅在禁用了内存 |
| 257 | +控制组(cgroups)时使用。它不应该直接访问,而应该使用 ``mem_cgroup_lruvec()`` |
| 258 | +来查找lruvecs。 |
| 259 | + |
| 260 | +回收控制 |
| 261 | +~~~~~~~~ |
| 262 | + |
| 263 | +另见内核文档 Documentation/mm/page_reclaim.rst 文件。 |
| 264 | + |
| 265 | +``kswapd`` |
| 266 | +每个节点的kswapd内核线程实例。 |
| 267 | + |
| 268 | +``kswapd_wait``, ``pfmemalloc_wait``, ``reclaim_wait`` |
| 269 | +同步内存回收任务的工作队列。 |
| 270 | + |
| 271 | +``nr_writeback_throttled`` |
| 272 | +等待写回脏页时,被限制的任务数量。 |
| 273 | + |
| 274 | +``kswapd_order`` |
| 275 | +控制kswapd尝试回收的order。 |
| 276 | + |
| 277 | +``kswapd_highest_zoneidx`` |
| 278 | +kswapd线程可以回收的最高区域索引。 |
| 279 | + |
| 280 | +``kswapd_failures`` |
| 281 | +kswapd无法回收任何页面的运行次数。 |
| 282 | + |
| 283 | +``min_unmapped_pages`` |
| 284 | +无法回收的未映射文件支持的最小页面数量。由 ``vm.min_unmapped_ratio`` |
| 285 | +系统控制台(sysctl)参数决定。在开启 ``CONFIG_NUMA`` 配置时定义。 |
| 286 | + |
| 287 | +``min_slab_pages`` |
| 288 | +无法回收的SLAB页面的最少数量。由 ``vm.min_slab_ratio`` 系统控制台 |
| 289 | +(sysctl)参数决定。在开启 ``CONFIG_NUMA`` 时定义。 |
| 290 | + |
| 291 | +``flags`` |
| 292 | +控制回收行为的标志位。 |
| 293 | + |
| 294 | +内存压缩控制 |
| 295 | +~~~~~~~~~~~~ |
| 296 | + |
| 297 | +``kcompactd_max_order`` |
| 298 | +kcompactd应尝试实现的页面order。 |
| 299 | + |
| 300 | +``kcompactd_highest_zoneidx`` |
| 301 | +kcompactd可以压缩的最高区域索引。 |
| 302 | + |
| 303 | +``kcompactd_wait`` |
| 304 | +同步内存压缩任务的工作队列。 |
| 305 | + |
| 306 | +``kcompactd`` |
| 307 | +每个节点的kcompactd内核线程实例。 |
| 308 | + |
| 309 | +``proactive_compact_trigger`` |
| 310 | +决定是否使用主动压缩。由 ``vm.compaction_proactiveness`` 系统控 |
| 311 | +制台(sysctl)参数控制。 |
| 312 | + |
| 313 | +统计信息 |
| 314 | +~~~~~~~~ |
| 315 | + |
| 316 | +``per_cpu_nodestats`` |
| 317 | +表示节点的Per-CPU虚拟内存统计信息。 |
| 318 | + |
| 319 | +``vm_stat`` |
| 320 | +表示节点的虚拟内存统计数据。 |
| 321 | + |
| 322 | +.. _zones_zh_CN: |
| 323 | + |
| 324 | +区域 |
| 325 | +==== |
| 326 | + |
| 327 | +.. admonition:: Stub |
| 328 | + |
| 329 | + 本节内容不完整。请列出并描述相应的字段。 |
| 330 | + |
| 331 | +.. _pages_zh_CN: |
| 332 | + |
| 333 | +页 |
| 334 | +==== |
| 335 | + |
| 336 | +.. admonition:: Stub |
| 337 | + |
| 338 | + 本节内容不完整。请列出并描述相应的字段。 |
| 339 | + |
| 340 | +.. _folios_zh_CN: |
| 341 | + |
| 342 | +页码 |
| 343 | +==== |
| 344 | + |
| 345 | +.. admonition:: Stub |
| 346 | + |
| 347 | + 本节内容不完整。请列出并描述相应的字段。 |
| 348 | + |
| 349 | +.. _initialization_zh_CN: |
| 350 | + |
| 351 | +初始化 |
| 352 | +====== |
| 353 | + |
| 354 | +.. admonition:: Stub |
| 355 | + |
| 356 | + 本节内容不完整。请列出并描述相应的字段。 |
0 commit comments