物理内存是通过 vm_page_t 结构进行逐页管理。 物理内存页面通过将其相应的 vm_page_t 结构放置在几个分页队列之一来进行分类。
页面可以处于有线、活动、非活动、缓存或自由状态。 除了有线状态之外,页面通常放置在表示其状态的双向链接列表队列中。 有线页面不会放置在任何队列上。
FreeBSD 实现了一个更复杂的页面队列,用于缓存和空闲页面,以实现页面着色。每个状态都涉及根据处理器的 L1 和 L2 缓存的大小排列的多个队列。当需要分配新页面时,FreeBSD 试图获得一个相对于 VM 对象而言从 L1 和 L2 缓存的角度合理对齐的页面。
另外,页面可以以引用计数或繁忙计数锁定。VM 系统还使用页面标志中的 PG_BUSY 位实现页面的“终极锁定”状态。
通常情况下,每个分页队列都以 LRU 方式运作。页面通常最初处于有线或活动状态。在有线状态下,页面通常与某个页表关联。VM 系统通过扫描更活跃的页面队列(LRU)中的页面来使页面老化,以便将它们移到不太活跃的分页队列中。移动到缓存中的页面仍然与 VM 对象关联,但是可能立即被重复使用。在空闲队列中的页面是真正空闲的。FreeBSD 试图最小化空闲队列中的页面数量,但必须保持一定数量的真正空闲页面,以便在中断时进行页面分配。
如果进程尝试访问其页表中不存在但存在于其中一个分页队列(如非活动队列或缓存队列)的页面,则会发生相对廉价的页面重新激活故障,导致页面被重新激活。如果页面根本不存在于系统内存中,则进程必须在页面从磁盘中带入时阻塞。
FreeBSD 动态调整其分页队列,并尝试保持各个队列中页面的合理比例,以及保持干净页面与脏页面的合理比例。发生的重新平衡量取决于系统的内存负载。这种重新平衡是由分页守护程序实现的,涉及清洗脏页面(与其后备存储同步),注意页面何时被活动引用(重置其在 LRU 队列中的位置或在队列之间移动它们),在队列不平衡时迁移页面之间的页面等。FreeBSD 的虚拟内存系统愿意接受合理数量的重新激活页面故障,以确定页面实际上是多么活跃或多么空闲。这导致更好的决策何时清洗或交换出页面。
FreeBSD 实现了通用“VM 对象”的概念。VM 对象可以与各种类型的后备存储相关联,包括未支持、交换支持、物理设备支持或文件支持存储。由于文件系统使用相同的 VM 对象来管理与文件相关的内核数据,因此结果是统一的缓冲区缓存。
VM 对象可以被阴影化。也就是说,它们可以堆叠在彼此之上。例如,您可以在文件支持的 VM 对象之上堆叠一个交换支持的 VM 对象,以实现 MAP_PRIVATE mmap()。这种堆叠也用于实现各种共享属性,包括 forked 地址空间的写时复制。
值得注意的是, vm_page_t 只能一次与一个 VM 对象相关联。VM 对象阴影化实现了同一页面在多个实例之间的感知共享。
vnode-backed VM 对象,如文件-backed 对象,通常需要维护其自己的干净/脏信息,独立于 VM 系统的干净/脏概念。例如,当 VM 系统决定将物理页同步到其备份存储时,VM 系统在将页面实际写入备份存储之前需要标记页面为干净。此外,文件系统需要能够将文件的某些部分或文件元数据映射到 KVM 中以对其进行操作。
用于管理这些实体的称为文件系统缓冲区, struct buf 's,或 bp 's。当文件系统需要在 VM 对象的一部分上操作时,通常将对象的部分映射到一个结构 buf 中,然后将结构 buf 中的页面映射到 KVM 中。以相同方式,磁盘 I/O 通常通过将对象的部分映射到缓冲区结构然后在缓冲区结构上发出 I/O 来发出。底层的 vm_page_t's 通常在 I/O 期间很忙碌。文件系统缓冲区还有自己的忙碌概念,对于更愿意操作文件系统缓冲区而不是硬 VM 页面的文件系统驱动程序代码很有用。
FreeBSD 保留了有限数量的 KVM 以保留从结构 bufs 的映射,但应明确指出,这个 KVM 仅用于保存映射,不限制缓存数据的能力。物理数据缓存严格是 vm_page_t 的功能,而不是文件系统缓冲区的功能。然而,由于文件系统缓冲区用于占位 I/O,它们确实限制了可能的并发 I/O 数量。但是,由于通常有几千个文件系统缓冲区可用,这通常不是问题。
FreeBSD 将物理页表拓扑结构与 VM 系统分开。所有硬 per-process 页表都可以动态重建,通常被视为一次性的。特殊的页表,如管理 KVM 的页表通常是永久预分配的。这些页表不是一次性的。
FreeBSD 通过 vm_map_t 和 vm_entry_t 结构将 vm_objects 的部分与虚拟内存中的地址范围关联起来。页面表直接从 vm_map_t / vm_entry_t / vm_object_t 层次结构合成。请记住,我提到物理页面仅与 vm_object 直接关联;这并不完全正确。 vm_page_t 也链接到它们积极关联的页面表中。一个 vm_page_t 可以链接到多个称为页面表的 pmap。然而,层次关联保持不变,因此对同一对象中同一页面的所有引用都引用相同的 vm_page_t ,从而使我们在整个系统中实现了缓冲区缓存统一化。
FreeBSD 使用 KVM 保存各种内核结构。在 KVM 中保存的最大实体是文件系统缓冲区缓存。也就是,与 struct buf 实体相关的映射。
不像 Linux,FreeBSD 不会将所有物理内存映射到 KVM 中。这意味着 FreeBSD 可以处理 32 位平台上高达 4G 的内存配置。事实上,如果 MMU 有能力的话,FreeBSD 理论上可以处理多达 8TB 的内存配置。然而,由于大多数 32 位平台只能映射 4GB 的内存,这一点无关紧要。
KVM 通过几种机制进行管理。用于管理 KVM 的主要机制是区域分配器。区域分配器获取一块 KVM 并将其分割成具有恒定大小的内存块,以便为特定类型的结构分配空间。您可以使用 vmstat -m 来查看按区域分解的当前 KVM 利用情况的概述。
为使 FreeBSD 内核动态调优,已经做出了协同努力。通常情况下,您无需干预除 maxusers 和 NMBCLUSTERS 内核配置选项之外的任何内容。也就是说,内核编译选项通常在/usr/src/sys/i386/conf/CONFIG_FILE 中指定。您可以在/usr/src/sys/i386/conf/LINT 中找到所有可用内核配置选项的描述。
在大型系统配置中,您可能希望增加 maxusers 。通常取值范围为 10 到 128。请注意,将 maxusers 提高过高可能会导致系统溢出可用的 KVM,导致不可预测的操作。最好将 maxusers 保持在某个合理的数字,并添加其他选项,如 NMBCLUSTERS ,以增加特定资源。
如果您的系统将大量使用网络,您可能希望增加 NMBCLUSTERS 。典型值范围为 1024 到 4096。
NBUF 参数也是传统上用于扩展系统的。该参数确定系统可以用于映射 I/O 文件系统缓冲区的 KVA 量。请注意,该参数与统一缓冲区缓存完全无关!该参数在 3.0-CURRENT 及更高版本内核中动态调整,通常不应手动调整。我们建议您不要尝试指定 NBUF 参数。让系统自行选择。值过小可能导致文件系统操作极其低效,而值过大可能通过使太多页面被固定而使页面队列饥饿。
默认情况下,FreeBSD 内核未经过优化。您可以使用内核配置中的 makeoptions 指令设置调试和优化标志。请注意,除非您可以容纳结果为大型(通常为 7 MB 以上)的内核,否则不应使用 -g 。
Sysctl 提供了一种在运行时调整内核参数的方法。通常情况下,您不需要干预任何 sysctl 变量,特别是与 VM 相关的变量。
运行时虚拟机和系统调整相对简单。首先,尽可能在您的 UFS/FFS 文件系统上使用 Soft Updates。 /usr/src/sys/ufs/ffs/README.softupdates 包含了如何配置它的说明(和限制)。
其次,配置足够的交换空间。您应该在每个物理磁盘上配置一个交换分区,最多四个,甚至在您的“工作”磁盘上也是如此。您的交换空间应该至少是主内存的 2 倍,如果您的内存不多甚至可能更多。您还应根据您打算在机器上放置的最大内存配置来设置交换分区的大小,以便以后不必重新分区您的磁盘。如果您想能够容纳崩溃转储,您的第一个交换分区的大小必须至少与主内存一样大,并且 /var/crash 必须具有足够的免费空间来容纳转储。
基于 NFS 的交换在 4.X 或之后的系统上是完全可以接受的,但您必须意识到 NFS 服务器将承受分页负载的大部分。