diff options
Diffstat (limited to 'Documentation/translations/zh_CN/mm')
9 files changed, 645 insertions, 26 deletions
diff --git a/Documentation/translations/zh_CN/mm/active_mm.rst b/Documentation/translations/zh_CN/mm/active_mm.rst index c2816f523bd7..b3352668c4c8 100644 --- a/Documentation/translations/zh_CN/mm/active_mm.rst +++ b/Documentation/translations/zh_CN/mm/active_mm.rst @@ -13,6 +13,11 @@ Active MM ========= +注意,在配置了 CONFIG_MMU_LAZY_TLB_REFCOUNT=n 的内核中,mm_count 引用计数 +可能不再包括“懒惰”用户(运行任务中 ->active_mm == mm && ->mm == NULL)。 +获取和释放这些懒惰引用必须使用 mmgrab_lazy_tlb() 和 mmdrop_lazy_tlb() 这 +两个辅助函数,它们抽象了这个配置选项。 + 这是一封linux之父回复开发者的一封邮件,所以翻译时我尽量保持邮件格式的完整。 :: diff --git a/Documentation/translations/zh_CN/mm/damon/faq.rst b/Documentation/translations/zh_CN/mm/damon/faq.rst index de4be417494a..234d63f4f072 100644 --- a/Documentation/translations/zh_CN/mm/damon/faq.rst +++ b/Documentation/translations/zh_CN/mm/damon/faq.rst @@ -13,23 +13,6 @@ 常见问题 ======== -为什么是一个新的子系统,而不是扩展perf或其他用户空间工具? -========================================================== - -首先,因为它需要尽可能的轻量级,以便可以在线使用,所以应该避免任何不必要的开销,如内核-用户 -空间的上下文切换成本。第二,DAMON的目标是被包括内核在内的其他程序所使用。因此,对特定工具 -(如perf)的依赖性是不可取的。这就是DAMON在内核空间实现的两个最大的原因。 - - -“闲置页面跟踪” 或 “perf mem” 可以替代DAMON吗? -============================================== - -闲置页跟踪是物理地址空间访问检查的一个低层次的原始方法。“perf mem”也是类似的,尽管它可以 -使用采样来减少开销。另一方面,DAMON是一个更高层次的框架,用于监控各种地址空间。它专注于内 -存管理优化,并提供复杂的精度/开销处理机制。因此,“空闲页面跟踪” 和 “perf mem” 可以提供 -DAMON输出的一个子集,但不能替代DAMON。 - - DAMON是否只支持虚拟内存? ========================= diff --git a/Documentation/translations/zh_CN/mm/hmm.rst b/Documentation/translations/zh_CN/mm/hmm.rst index babbbe756c0f..0669f947d0bc 100644 --- a/Documentation/translations/zh_CN/mm/hmm.rst +++ b/Documentation/translations/zh_CN/mm/hmm.rst @@ -129,13 +129,7 @@ struct page可以与现有的 mm 机制进行最简单、最干净的集成。 int hmm_range_fault(struct hmm_range *range); 如果请求写访问,它将在丢失或只读条目上触发缺页异常(见下文)。缺页异常使用通用的 mm 缺 -页异常代码路径,就像 CPU 缺页异常一样。 - -这两个函数都将 CPU 页表条目复制到它们的 pfns 数组参数中。该数组中的每个条目对应于虚拟 -范围中的一个地址。HMM 提供了一组标志来帮助驱动程序识别特殊的 CPU 页表项。 - -在 sync_cpu_device_pagetables() 回调中锁定是驱动程序必须尊重的最重要的方面,以保 -持事物正确同步。使用模式是:: +页异常代码路径,就像 CPU 缺页异常一样。使用模式是:: int driver_populate_range(...) { diff --git a/Documentation/translations/zh_CN/mm/index.rst b/Documentation/translations/zh_CN/mm/index.rst index b950dd118be7..c8726bce8f74 100644 --- a/Documentation/translations/zh_CN/mm/index.rst +++ b/Documentation/translations/zh_CN/mm/index.rst @@ -53,6 +53,8 @@ Linux内存管理文档 page_migration page_owner page_table_check + page_tables + physical_memory remap_file_pages split_page_table_lock vmalloced-kernel-stacks diff --git a/Documentation/translations/zh_CN/mm/overcommit-accounting.rst b/Documentation/translations/zh_CN/mm/overcommit-accounting.rst index d8452d8b7fbb..f136a8b81859 100644 --- a/Documentation/translations/zh_CN/mm/overcommit-accounting.rst +++ b/Documentation/translations/zh_CN/mm/overcommit-accounting.rst @@ -16,8 +16,7 @@ Linux内核支持下列超量使用处理模式 0 启发式超量使用处理。拒绝明显的地址空间超量使用。用于一个典型的系统。 - 它确保严重的疯狂分配失败,同时允许超量使用以减少swap的使用。在这种模式下, - 允许root分配稍多的内存。这是默认的。 + 它确保严重的疯狂分配失败,同时允许超量使用以减少swap的使用。这是默认的。 1 总是超量使用。适用于一些科学应用。经典的例子是使用稀疏数组的代码,只是依赖 几乎完全由零页组成的虚拟内存 diff --git a/Documentation/translations/zh_CN/mm/page_owner.rst b/Documentation/translations/zh_CN/mm/page_owner.rst index b72a972271d9..c0d1ca4b9695 100644 --- a/Documentation/translations/zh_CN/mm/page_owner.rst +++ b/Documentation/translations/zh_CN/mm/page_owner.rst @@ -26,6 +26,9 @@ page owner是用来追踪谁分配的每一个页面。它可以用来调试内 页面所有者也可以用于各种目的。例如,可以通过每个页面的gfp标志信息获得精确的碎片 统计。如果启用了page owner,它就已经实现并激活了。我们非常欢迎其他用途。 +它也可以用来显示所有的栈以及它们当前分配的基础页面数,这让我们能够快速了解内存的 +使用情况,而无需浏览所有页面并匹配分配和释放操作。 + page owner在默认情况下是禁用的。所以,如果你想使用它,你需要在你的启动cmdline 中加入"page_owner=on"。如果内核是用page owner构建的,并且由于没有启用启动 选项而在运行时禁用page owner,那么运行时的开销是很小的。如果在运行时禁用,它不 @@ -60,6 +63,49 @@ page owner在默认情况下是禁用的。所以,如果你想使用它,你 4) 分析来自页面所有者的信息:: + cat /sys/kernel/debug/page_owner_stacks/show_stacks > stacks.txt + cat stacks.txt + post_alloc_hook+0x177/0x1a0 + get_page_from_freelist+0xd01/0xd80 + __alloc_pages+0x39e/0x7e0 + allocate_slab+0xbc/0x3f0 + ___slab_alloc+0x528/0x8a0 + kmem_cache_alloc+0x224/0x3b0 + sk_prot_alloc+0x58/0x1a0 + sk_alloc+0x32/0x4f0 + inet_create+0x427/0xb50 + __sock_create+0x2e4/0x650 + inet_ctl_sock_create+0x30/0x180 + igmp_net_init+0xc1/0x130 + ops_init+0x167/0x410 + setup_net+0x304/0xa60 + copy_net_ns+0x29b/0x4a0 + create_new_namespaces+0x4a1/0x820 + nr_base_pages: 16 + ... + ... + echo 7000 > /sys/kernel/debug/page_owner_stacks/count_threshold + cat /sys/kernel/debug/page_owner_stacks/show_stacks> stacks_7000.txt + cat stacks_7000.txt + post_alloc_hook+0x177/0x1a0 + get_page_from_freelist+0xd01/0xd80 + __alloc_pages+0x39e/0x7e0 + alloc_pages_mpol+0x22e/0x490 + folio_alloc+0xd5/0x110 + filemap_alloc_folio+0x78/0x230 + page_cache_ra_order+0x287/0x6f0 + filemap_get_pages+0x517/0x1160 + filemap_read+0x304/0x9f0 + xfs_file_buffered_read+0xe6/0x1d0 [xfs] + xfs_file_read_iter+0x1f0/0x380 [xfs] + __kernel_read+0x3b9/0x730 + kernel_read_file+0x309/0x4d0 + __do_sys_finit_module+0x381/0x730 + do_syscall_64+0x8d/0x150 + entry_SYSCALL_64_after_hwframe+0x62/0x6a + nr_base_pages: 20824 + ... + cat /sys/kernel/debug/page_owner > page_owner_full.txt ./page_owner_sort page_owner_full.txt sorted_page_owner.txt diff --git a/Documentation/translations/zh_CN/mm/page_table_check.rst b/Documentation/translations/zh_CN/mm/page_table_check.rst index e8077310a76c..dc34570dceff 100644 --- a/Documentation/translations/zh_CN/mm/page_table_check.rst +++ b/Documentation/translations/zh_CN/mm/page_table_check.rst @@ -54,3 +54,16 @@ 可以选择用PAGE_TABLE_CHECK_ENFORCED来构建内核,以便在没有额外的内核参数的情况下获得页表 支持。 + +实现注意事项 +============ + +我们特意决定不使用 VMA 信息,以避免依赖于 MM 状态(除了有限的 “struct page” 信息)。页表检查 +独立于 Linux-MM 状态机,它验证用户可访问的页面不会被错误地共享。 + +PAGE_TABLE_CHECK 依赖于 EXCLUSIVE_SYSTEM_RAM。原因在于,若没有 EXCLUSIVE_SYSTEM_RAM, +用户被允许通过 /dev/mem 将任意物理内存区域映射到用户空间。同时,页面可能在映射到用户空间期间 +改变自己的属性(例如,从匿名页面变为命名页面),导致页表检查检测到“损坏”。 + +即使有 EXCLUSIVE_SYSTEM_RAM,I/O 页面可能仍然被允许通过 /dev/mem 映射。然而,这些页面始终 +被视为命名页面,所以它们不会破坏页表检查中使用的逻辑。 diff --git a/Documentation/translations/zh_CN/mm/page_tables.rst b/Documentation/translations/zh_CN/mm/page_tables.rst new file mode 100644 index 000000000000..c9f750fc5298 --- /dev/null +++ b/Documentation/translations/zh_CN/mm/page_tables.rst @@ -0,0 +1,221 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/page_tables.rst + +:翻译: + + 张鹏宇 Pengyu Zhang <zpenya1314@gmail.com> + +:校译: + +==== +页表 +==== + +分页虚拟内存是随虚拟内存的概念一起于 1962 年在 Ferranti Atlas 计算机上被提出的, +这是第一台有分页虚拟内存的计算机。随着时间推移,这个特性被迁移到更新的计算机上, +并且成为所有类 Unix 系统实际的特性。在 1985 年,这个特性被包含在了英特尔 80386 +中,也就是运行 Linux 1.0 的CPU。 + +页表将 CPU 看到的虚拟地址映射到外部内存总线上看到的物理地址。 + +Linux 将页表定义为一个分级结构,目前有五级。对于支持的每种架构,其代码会根据硬件 +限制对这个层级结构进行映射。 + +虚拟地址对应的物理地址通常由底层物理页帧引用。 **页帧号(page frame number,pfn)** +是页的物理地址(在外部内存总线看到的地址)除以 `PAGE_SIZE` 得到的值。 + +物理内存地址 0 对应 *pfn 0*,而最大的 pfn 对应处理器外部地址总线所能寻址物理地址 +的最后一页。 + +在页粒度为 4KB 且地址范围为32位的情况下,pfn 0 对应地址0x00000000,pfn 1 对应 +地址0x00001000,pfn 2 对应地址 0x00002000,以此类推,直到 pfn 0xfffff 对应 +0xfffff000。如果页粒度为 16KB,则 pfn 分别对应地址 0x00004000、0x00008000 +... 0xffffc000,pfn 的范围从 0 到 0x3ffff。 + +如你所见,对于 4KB 页面粒度,页基址使用地址的 12-31 位,这就是为什么在这种情况下 +`PAGE_SHIFT` 被定义为 12,并且 `PAGE_SIZE` 通常由页偏移定义,为 `(1 << PAGE_SHIFT)`。 + +随着内存容量的增加,久而久之层级结构逐渐加深。Linux 最初使用 4KB 页面和一个名为 +`swapper_pg_dir` 的页表,该页表拥有 1024 个表项(entries),覆盖 4MB 的内存, +事实上Torvald 的第一台计算机正好就有 4MB 物理内存。表项在这张表中被称为 *PTE*:s +- 页表项(page table entries)。 + +软件页表层级结构反映了页表硬件已经变得分层化的事实,而这种分层化的目的是为了节省 +页表内存并加快地址映射速度。 + +当然,人们可以想象一张拥有大量表项的单一线性的页表将整个内存分为一个个页。而且, +这样的页表会非常稀疏,因为虚拟内存中大部分位置通常是未使用的。通过页表分层,虚拟 +内存中的大量空洞不会浪费宝贵的页表内存,因为只需要在上层页表中将大块的区域标记为 +未映射即可。 + +另外,在现代处理器中,上层页表项可以直接指向一个物理地址范围,这使得单个上层 +页表项可以连续映射几兆字节甚至几千兆字节的内存范围,从而快捷地实现虚拟地址到 +物理地址的映射:当你找到一个像这样的大型映射范围时,无需在层级结构中进一步遍历。 + +页表的层级结构目前发展为如下所示:: + + +-----+ + | PGD | + +-----+ + | + | +-----+ + +-->| P4D | + +-----+ + | + | +-----+ + +-->| PUD | + +-----+ + | + | +-----+ + +-->| PMD | + +-----+ + | + | +-----+ + +-->| PTE | + +-----+ + + +不同页表层级的符号含义从最底层开始如下: + +- **pte**, `pte_t`, `pteval_t` = **页表项** - 前面提到过。*pte* 是一个由 + `PTRS_PER_PTE` 个 `pteval_t` 类型元素组成的数组,每个元素将一个虚拟内存页 + 映射到一个物理内存页。体系结构定义了 `pteval_t` 的大小和内容。 + + 一个典型的例子是 `pteval_t` 是一个 32 或者 64 位的值,其中高位是 **pfn**, + 而低位则一些特定体系架构相关的位,如内存保护。 + + 这个 **表项(entry)** 有点令人困惑,因为在 Linux 1.0 中它确实指的是单层顶级 + 页表中的单个页表项,但在首次引入二级页表时,它被重新定义为映射元素的数组。 + 因此,*pte* 现在指的是最底层的页 *表*,而不是一个页表 *项*。 + +- **pmd**, `pmd_t`, `pmdval_t` = **页中间目录(Page Middle Directory)**, + 位于 *pte* 之上的层级结构,包含 `PTRS_PER_PMD` 个指向 *pte* 的引用。 + +- **pud**, `pud_t`, `pudval_t` = **页上级目录(Page Upper Directory)** + 是在其他层级之后引入的,用于处理四级页表。它可能未被使用,或者像我们稍后 + 讨论的那样被“折叠”。 + +- **p4d**, `p4d_t`, `p4dval_t` = **页四级目录(Page Level 4 Directory)** + 是在 *pud* 之后用于处理五级页表引入的。至此,显然需要用数字来替代 *pgd*、 + *pmd*、*pud* 等目录层级的名称,不能再继续使用临时的命名方式。这个目录层级 + 只在实际拥有五级页表的系统上使用,否则它会被折叠。 + +- **pgd**, `pgd_t`, `pgdval_t` = **页全局目录(Page Global Directory)** - + Linux 内核用于处理内核内存的 *PGD* 主页表仍然位于 `swapper_pg_dir`。 + 但系统中的每个用户空间进程也有自己的内存上下文,因此也有自己的 *pgd*, + 它位于 `struct mm_struct` 中,而 `struct mm_struct` 又在每个 `struct task_struct` + 中有引用。所以,任务(进程)存在一个形式为 `struct mm_struct` 的内存上下文, + 而这个结构体中有一个指向指向相应的页全局目录 `struct pgt_t *pgd` 指针。 + +重申一下:页表层级结构中的每一层都是一个 *指针数组*,所以 *pgd* 包含 `PTRS_PER_PGD` +个指向下一层的指针,*p4d* 包含 `PTRS_PER_P4D` 个指向 *pud* 项的指针,依此类推。 +每一层的指针数量由体系结构定义。:: + + PMD + --> +-----+ PTE + | ptr |-------> +-----+ + | ptr |- | ptr |-------> PAGE + | ptr | \ | ptr | + | ptr | \ ... + | ... | \ + | ptr | \ PTE + +-----+ +----> +-----+ + | ptr |-------> PAGE + | ptr | + ... + +页表折叠 +======== + +如果架构不使用所有的页表层级,那么这些层级可以被 *折叠*,也就是说被跳过。在 +访问下一层时,所有在页表上执行的操作都会在编译时增强,以跳过这一层。 + +与架构无关的页表处理代码(例如虚拟内存管理器)需要编写得能够遍历当前的所有五个 +层级。对于特定架构的代码,也应优先采用这种风格,以便对未来的变化具有更好的适应性。 + +MMU,TLB 和缺页异常 +=================== + +`内存管理单元(MMU)` 是处理虚拟地址到物理地址转换的硬件组件。它可能会使用相对较小 +的硬件缓存,如 `转换后备缓冲区(TLB)` 和 `页遍历缓存`,以加快这些地址翻译过程。 + +当 CPU 访存时,它会向 MMU 提供一个虚拟地址。MMU 会首先检查 TLB 或者页遍历缓存 +(在支持的架构上)是否存在对应的转换结果。如果没有,MMU 会通过遍历来确定物理地址 +并且建立映射。 + +当页面被写入时,该页的脏位会被设置(即打开)。每个内存页面都有相关的权限位和脏位。 +后者表明这个页自从被加载到内存以来是否被修改。 + +如果没有任何阻碍,物理内存到头来可以被任意访问并且对物理帧进行请求的操作。 + +MMU 无法找到某些转换有多种原因。有可能是 CPU 试图去访问当前进程没有权限访问的 +内存,或者因为访问的数据还不在物理内存中。 + +当这些情况发生时,MMU 会触发缺页异常,这是一种异常类型,用于通知 CPU 暂停当前 +执行并运行一个特殊的函数去处理这些异常。 + +缺页异常有一些常见且预期的原因。这些因素是由称为“懒加载”和“写时复制”的进程管理 +优化技术来触发的。缺页异常也可能发生在当页帧被交换到持久存储(交换分区或者文件) +并从其物理地址移出时。 + +这些技术提高了内存效率,减少了延迟,并且最小化了空间占用。本文档不会深入讨论 +“懒加载”和“写时复制”的细节,因为这些的主题属于进程地址管理范畴,超出了本文范围。 + +交换技术和前面提到的其他技术不同,因为它是在压力过大下情况下减少内存消耗的一种 +迫不得已的手段,因此是不受欢迎的。 + +交换不适用于由内核逻辑地址映射的内存。这些地址是内核虚拟地址空间的子集,直接映射 +一段连续的物理内存。对于提供的任意逻辑地址,它的物理地址可以通过对偏移量进行简单 +的算数运算来确定。对逻辑地址的访问很快,因为这避免了复杂的页表查找,但代价是这些 +内存不能被驱逐或置换。 + +如果内核无法为必须存在于物理帧中的数据腾出空间,那么它会调用内存不足(out-of-memory, +OOM)杀手,通过杀掉低优先级的进程来腾出空间,直到内存压力下降到安全阈值之下。 + +另外,代码漏洞或指示 CPU 访问的精心制作的恶意地址也可能导致缺页异常。一个进程的 +线程可以利用指令来访问不属于其地址空间的(非共享)内存,或者试图执行写入只读位置 +的指令。 + +如果上述情况发生在用户态,内核会向当前线程发送 `段错误` (SIGSEGV)信号。该信号 +通常导致线程及其所属的进程终止。 + +本文将简化并概述 Linux 内核如何处理这些缺页中断、创建表和表项、检查内存是否存在, +以及当内存不存在时,如何请求从持久存储或其他设备加载数据,并更新 MMU 及其缓存。 + +最初的步骤依赖于架构。大多是架构跳转到 `do_page_fault()`,而 x86 中断处理程序是由 +`DEFINE_IDTENTRY_RAW_ERRORCODE()` 宏定义的,该宏调用 `handle_page_fault()`。 + +无论调用路径如何,所有架构最终都会调用 `handle_mm_fault()`,该函数通常会调用 +`__handle_mm_fault()` 来执行实际分配页表的任务。 + +如果不幸无法调用 `__handle_mm_fault()` 则意味着虚拟地址指向了无权访问的物理 +内存区域(至少对于当前上下文如此)。这种情况会导致内核向该进程发送上述的 SIGSEGV +信号,并引发前面提到的后果。 + +这些用于查找偏移量的函数名称通常以 `*_offset()` 结尾,其中“\*”可以是 pgd,p4d, +pud,pmd 或者 pte;而分配相应层级页表的函数名称是 `*_alloc`,它们按照上述命名 +约定以对应页表层级的类型命名。 + +页表遍历可能在中间或者上层结束(PMD,PUD)。 + +Linux 支持比通常 4KB 更大的页面(即所谓的 `巨页`)。当使用这种较大的页面时,没有 +必要使用更低层的页表项(PTE)。巨页通常包含 2MB 到 1GB 的大块连续物理区域,分别由 +PMD 和 PUD 页表项映射。 + +巨页带来许多好处,如减少 TLB 压力,减少页表开销,提高内存分配效率,以及改善 +特定工作负载的性能。然而,这些好处也伴随着权衡,如内存浪费和分配难度增加。 + +在遍历和分配的最后,如果没有返回错误,`__handle_mm_fault()` 最终调用 `handle_pte_fault()` +通过 `do_fault()` 执行 `do_read_fault()`、 `do_cow_fault()` 和 `do_shared_fault()`。 +“read”,“cow”和“shared”分别暗示了它处理错误的类型和原因。 + +实际的工作流程实现是非常复杂的。其设计允许 Linux 根据每种架构的特定特性处理缺页 +异常,同时仍然共享一个通用的整体结构。 + +为了总结 Linux 如何处理缺页中断的概述,需要补充的是,缺页异常处理程序可以通过 +`pagefault_disable()` 和 `pagefault_enable()` 分别禁用和启用。 + +许多代码路径使用了这两个函数,因为它们需要禁止陷入缺页异常处理程序,主要是为了 +防止死锁。 diff --git a/Documentation/translations/zh_CN/mm/physical_memory.rst b/Documentation/translations/zh_CN/mm/physical_memory.rst new file mode 100644 index 000000000000..4594d15cefec --- /dev/null +++ b/Documentation/translations/zh_CN/mm/physical_memory.rst @@ -0,0 +1,356 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/mm/physical_memory.rst + +:翻译: + + 王亚鑫 Yaxin Wang <wang.yaxin@zte.com.cn> + +======== +物理内存 +======== + +Linux可用于多种架构,因此需要一个与架构无关的抽象来表示物理内存。本章描述 +了管理运行系统中物理内存的结构。 + +第一个与内存管理相关的主要概念是 `非一致性内存访问(NUMA) +<https://en.wikipedia.org/wiki/Non-uniform_memory_access>` + +在多核和多插槽机器中,内存可能被组织成不同的存储区,这些存储区根据与处理器 +的距离“不同”而有不同的访问开销。例如,可能为每个CPU分配内存存储区,或者为 +外围设备在附近分配一个非常适合DMA的内存存储区。 + +每个存储区被称为一个节点,节点在Linux中表示为 ``struct pglist_data``, +即使是在UMA架构中也是这样表示。该结构总是通过 ``pg_data_t`` 来引用。特 +定节点的 ``pg_data_t`` 结构体可以通过NODE_DATA(nid)引用,其中nid被称 +为该节点的ID。 + +对于非一致性内存访问(NUMA)架构,节点数据结构在引导时由特定于架构的代码早 +期分配。通常,这些结构在其所在的内存区上本地分配。对于一致性内存访问(UMA) +架构,只使用一个静态的 ``pg_data_t`` 结构体,称为 ``contig_page_data``。 +节点将会在 :ref:`节点 <nodes>` 章节中进一步讨论。 + +整个物理内存被划分为一个或多个被称为区域的块,这些区域表示内存的范围。这 +些范围通常由访问内存的架构限制来决定。在节点内,与特定区域对应的内存范围 +由 ``struct zone`` 结构体描述,该结构被定义为 ``zone_t``,每种区域都 +属于以下描述类型的一种。 + +* ``ZONE_DMA`` 和 ``ZONE_DMA32`` 在历史上代表适用于DMA的内存,这些 + 内存由那些不能访问所有可寻址内存的外设访问。多年来,已经有了更好、更稳 + 固的接口来获取满足特定DMA需求的内存(这些接口由 + Documentation/core-api/dma-api.rst 文档描述),但是 ``ZONE_DMA`` + 和 ``ZONE_DMA32`` 仍然表示访问受限的内存范围。 + +取决于架构的不同,这两种区域可以在构建时通过关闭 ``CONFIG_ZONE_DMA`` 和 +``CONFIG_ZONE_DMA32`` 配置选项来禁用。一些64位的平台可能需要这两种区域, +因为他们支持具有不同DMA寻址限制的外设。 + +* ``ZONE_NORMAL`` 是普通内存的区域,这种内存可以被内核随时访问。如果DMA + 设备支持将数据传输到所有可寻址的内存区域,那么可在该区域的页面上执行DMA + 操作。``ZONE_NORMAL`` 总是开启的。 + +* ``ZONE_HIGHMEM`` 是指那些没有在内核页表中永久映射的物理内存部分。该区 + 域的内存只能通过临时映射被内核访问。该区域只在某些32位架构上可用,并且是 + 通过 ``CONFIG_HIGHMEM`` 选项开启。 + +* ``ZONE_MOVABLE`` 是指可访问的普通内存区域,就像 ``ZONE_NORMAL`` + 一样。不同之处在于 ``ZONE_MOVABLE`` 中的大多数页面内容是可移动的。 + 这意味着这些页面的虚拟地址不会改变,但它们的内容可能会在不同的物理页面 + 之间移动。通常,在内存热插拔期间填充 ``ZONE_MOVABLE``,在启动时也可 + 以使用 ``kernelcore``、``movablecore`` 和 ``movable_node`` + 这些内核命令行参数来填充。更多详细信息,请参阅内核文档 + Documentation/mm/page_migration.rst 和 + Documentation/admin-guide/mm/memory-hotplug.rst。 + +* ``ZONE_DEVICE`` 表示位于持久性内存(PMEM)和图形处理单元(GPU) + 等设备上的内存。它与RAM区域类型有不同的特性,并且它的存在是为了提供 + :ref:`struct page<Pages>` 结构和内存映射服务,以便设备驱动程序能 + 识别物理地址范围。``ZONE_DEVICE`` 通过 ``CONFIG_ZONE_DEVICE`` + 选项开启。 + +需要注意的是,许多内核操作只能使用 ``ZONE_NORMAL`` 来执行,因此它是 +性能最关键区域。区域在 :ref:`区域 <zones>` 章节中有更详细的讨论。 + +节点和区域范围之间的关系由固件报告的物理内存映射决定,另外也由内存寻址 +的架构约束以及内核命令行中的某些参数决定。 + +例如,在具有2GB RAM的x86统一内存架构(UMA)机器上运行32位内核时,整 +个内存将位于节点0,并且将有三个区域: ``ZONE_DMA``、 ``ZONE_NORMAL`` +和 ``ZONE_HIGHMEM``:: + + 0 2G + +-------------------------------------------------------------+ + | node 0 | + +-------------------------------------------------------------+ + + 0 16M 896M 2G + +----------+-----------------------+--------------------------+ + | ZONE_DMA | ZONE_NORMAL | ZONE_HIGHMEM | + +----------+-----------------------+--------------------------+ + + +在内核构建时关闭 ``ZONE_DMA`` 开启 ``ZONE_DMA32``,并且具有16GB +RAM平均分配在两个节点上的arm64机器上,使用 ``movablecore=80%`` 参数 +启动时,``ZONE_DMA32``、``ZONE_NORMAL`` 和 ``ZONE_MOVABLE`` +位于节点0,而 ``ZONE_NORMAL`` 和 ``ZONE_MOVABLE`` 位于节点1:: + + + 1G 9G 17G + +--------------------------------+ +--------------------------+ + | node 0 | | node 1 | + +--------------------------------+ +--------------------------+ + + 1G 4G 4200M 9G 9320M 17G + +---------+----------+-----------+ +------------+-------------+ + | DMA32 | NORMAL | MOVABLE | | NORMAL | MOVABLE | + +---------+----------+-----------+ +------------+-------------+ + + +内存存储区可能位于交错的节点。在下面的例子中,一台x86机器有16GB的RAM分 +布在4个内存存储区上,偶数编号的内存存储区属于节点0,奇数编号的内存条属于 +节点1:: + + 0 4G 8G 12G 16G + +-------------+ +-------------+ +-------------+ +-------------+ + | node 0 | | node 1 | | node 0 | | node 1 | + +-------------+ +-------------+ +-------------+ +-------------+ + + 0 16M 4G + +-----+-------+ +-------------+ +-------------+ +-------------+ + | DMA | DMA32 | | NORMAL | | NORMAL | | NORMAL | + +-----+-------+ +-------------+ +-------------+ +-------------+ + +在这种情况下,节点0将覆盖从0到12GB的内存范围,而节点1将覆盖从4GB到16GB +的内存范围。 + +.. _nodes_zh_CN: + +节点 +==== + +正如我们所提到的,内存中的每个节点由 ``pg_data_t`` 描述,通过 +``struct pglist_data`` 结构体的类型定义。在分配页面时,默认情况下,Linux +使用节点本地分配策略,从离当前运行CPU的最近节点分配内存。由于进程倾向于在同 +一个CPU上运行,很可能会使用当前节点的内存。分配策略可以由用户控制,如内核文 +档 Documentation/admin-guide/mm/numa_memory_policy.rst 中所述。 + +大多数NUMA(非统一内存访问)架构维护了一个指向节点结构的指针数组。这些实际 +的结构在启动过程中的早期被分配,这时特定于架构的代码解析了固件报告的物理内 +存映射。节点初始化的大部分工作是在由free_area_init()实现的启动过程之后 +完成,该函数在后面的小节 :ref:`初始化 <initialization>` 中有详细描述。 + +除了节点结构,内核还维护了一个名为 ``node_states`` 的 ``nodemask_t`` +位掩码数组。这个数组中的每个位掩码代表一组特定属性的节点,这些属性由 +``enum node_states`` 定义,定义如下: + +``N_POSSIBLE`` +节点可能在某个时刻上线。 + +``N_ONLINE`` +节点已经上线。 + +``N_NORMAL_MEMORY`` +节点拥有普通内存。 + +``N_HIGH_MEMORY`` +节点拥有普通或高端内存。当关闭 ``CONFIG_HIGHMEM`` 配置时, +也可以称为 ``N_NORMAL_MEMORY``。 + +``N_MEMORY`` +节点拥有(普通、高端、可移动)内存。 + +``N_CPU`` +节点拥有一个或多个CPU。 + +对于具有上述属性的每个节点,``node_states[<property>]`` +掩码中对应于节点ID的位会被置位。 + +例如,对于具有常规内存和CPU的节点2,第二个bit将被设置:: + + node_states[N_POSSIBLE] + node_states[N_ONLINE] + node_states[N_NORMAL_MEMORY] + node_states[N_HIGH_MEMORY] + node_states[N_MEMORY] + node_states[N_CPU] + +有关使用节点掩码(nodemasks)可能进行的各种操作,请参考 +``include/linux/nodemask.h``。 + +除此之外,节点掩码(nodemasks)提供用于遍历节点的宏,即 +``for_each_node()`` 和 ``for_each_online_node()``。 + +例如,要为每个在线节点调用函数 foo(),可以这样操作:: + + for_each_online_node(nid) { + pg_data_t *pgdat = NODE_DATA(nid); + + foo(pgdat); + } + +节点数据结构 +------------ + +节点结构 ``struct pglist_data`` 在 ``include/linux/mmzone.h`` +中声明。这里我们将简要描述这个结构体的字段: + +通用字段 +~~~~~~~~ + +``node_zones`` +表示该节点的区域列表。并非所有区域都可能被填充,但这是 +完整的列表。它被该节点的node_zonelists以及其它节点的 +node_zonelists引用。 + +``node_zonelists`` +表示所有节点中所有区域的列表。此列表定义了分配内存时首选的区域 +顺序。``node_zonelists`` 在核心内存管理结构初始化期间, +由 ``mm/page_alloc.c`` 中的 ``build_zonelists()`` +函数设置。 + +``nr_zones`` +表示此节点中已填充区域的数量。 + +``node_mem_map`` +对于使用FLATMEM内存模型的UMA系统,0号节点的 ``node_mem_map`` +表示每个物理帧的struct pages数组。 + +``node_page_ext`` +对于使用FLATMEM内存模型的UMA系统,0号节点的 ``node_page_ext`` +是struct pages的扩展数组。只有在构建时开启了 ``CONFIG_PAGE_EXTENSION`` +选项的内核中才可用。 + +``node_start_pfn`` +表示此节点中起始页面帧的页面帧号。 + +``node_present_pages`` +表示此节点中存在的物理页面的总数。 + +``node_spanned_pages`` +表示包括空洞在内的物理页面范围的总大小。 + +``node_size_lock`` +一个保护定义节点范围字段的锁。仅在开启了 ``CONFIG_MEMORY_HOTPLUG`` 或 +``CONFIG_DEFERRED_STRUCT_PAGE_INIT`` 配置选项中的某一个时才定义。提 +供了 ``pgdat_resize_lock()`` 和 ``pgdat_resize_unlock()`` 用来操作 +``node_size_lock``,而无需检查 ``CONFIG_MEMORY_HOTPLUG`` 或 +``CONFIG_DEFERRED_STRUCT_PAGE_INIT`` 选项。 + +``node_id`` +节点的节点ID(NID),从0开始。 + +``totalreserve_pages`` +这是每个节点保留的页面,这些页面不可用于用户空间分配。 + +``first_deferred_pfn`` +如果大型机器上的内存初始化被推迟,那么第一个PFN(页帧号)是需要初始化的。 +在开启了 ``CONFIG_DEFERRED_STRUCT_PAGE_INIT`` 选项时定义。 + +``deferred_split_queue`` +每个节点的大页队列,这些大页的拆分被推迟了。仅在开启了 ``CONFIG_TRANSPARENT_HUGEPAGE`` +配置选项时定义。 + +``__lruvec`` +每个节点的lruvec持有LRU(最近最少使用)列表和相关参数。仅在禁用了内存 +控制组(cgroups)时使用。它不应该直接访问,而应该使用 ``mem_cgroup_lruvec()`` +来查找lruvecs。 + +回收控制 +~~~~~~~~ + +另见内核文档 Documentation/mm/page_reclaim.rst 文件。 + +``kswapd`` +每个节点的kswapd内核线程实例。 + +``kswapd_wait``, ``pfmemalloc_wait``, ``reclaim_wait`` +同步内存回收任务的工作队列。 + +``nr_writeback_throttled`` +等待写回脏页时,被限制的任务数量。 + +``kswapd_order`` +控制kswapd尝试回收的order。 + +``kswapd_highest_zoneidx`` +kswapd线程可以回收的最高区域索引。 + +``kswapd_failures`` +kswapd无法回收任何页面的运行次数。 + +``min_unmapped_pages`` +无法回收的未映射文件支持的最小页面数量。由 ``vm.min_unmapped_ratio`` +系统控制台(sysctl)参数决定。在开启 ``CONFIG_NUMA`` 配置时定义。 + +``min_slab_pages`` +无法回收的SLAB页面的最少数量。由 ``vm.min_slab_ratio`` 系统控制台 +(sysctl)参数决定。在开启 ``CONFIG_NUMA`` 时定义。 + +``flags`` +控制回收行为的标志位。 + +内存压缩控制 +~~~~~~~~~~~~ + +``kcompactd_max_order`` +kcompactd应尝试实现的页面order。 + +``kcompactd_highest_zoneidx`` +kcompactd可以压缩的最高区域索引。 + +``kcompactd_wait`` +同步内存压缩任务的工作队列。 + +``kcompactd`` +每个节点的kcompactd内核线程实例。 + +``proactive_compact_trigger`` +决定是否使用主动压缩。由 ``vm.compaction_proactiveness`` 系统控 +制台(sysctl)参数控制。 + +统计信息 +~~~~~~~~ + +``per_cpu_nodestats`` +表示节点的Per-CPU虚拟内存统计信息。 + +``vm_stat`` +表示节点的虚拟内存统计数据。 + +.. _zones_zh_CN: + +区域 +==== + +.. admonition:: Stub + + 本节内容不完整。请列出并描述相应的字段。 + +.. _pages_zh_CN: + +页 +==== + +.. admonition:: Stub + + 本节内容不完整。请列出并描述相应的字段。 + +.. _folios_zh_CN: + +页码 +==== + +.. admonition:: Stub + + 本节内容不完整。请列出并描述相应的字段。 + +.. _initialization_zh_CN: + +初始化 +====== + +.. admonition:: Stub + + 本节内容不完整。请列出并描述相应的字段。 |