12.10. 调整磁盘

下面的章节将讨论各种可用于磁盘设备的调整机制和选项。在许多情况下,带有机械部件的磁盘,如 SCSI 驱动器,将成为降低整个系统性能的瓶颈。虽然一个解决方案是安装一个没有机械部件的驱动器,如固态驱动器,但机械驱动器在不久的将来不会消失。在调整磁盘时,建议利用 iostat(8) 命令的功能来测试系统的各种变化。这个命令将使用户获得关于系统 IO 的宝贵信息。

12.10.1. sysctl 变量

12.10.1.1. vfs.vmiodirenable

vfs.vmiodirenable sysctl(8) 变量可以被设置为 0(关闭)或 1(打开)。默认情况下,它被设置为1。这个变量控制系统对目录的缓存方式。大多数目录都很小,在文件系统中只使用一个片段(通常为 1K ),在缓冲区缓存中通常为 512 字节。如果关闭这个变量,缓冲区缓存将只缓存固定数量的目录,即使系统有大量的内存。当打开时,这个 sysctl(8) 允许缓冲区缓存使用虚拟机页面缓存来缓存目录,使所有的内存都可以用于缓存目录。然而,用于缓存目录的最小核心内存是物理页大小(通常是 4K )而不是 512 字节。如果系统正在运行任何操作大量文件的服务,建议保持该选项的启用。这些服务可能包括网络缓存、大型邮件系统和新闻系统。保持这个选项通常不会降低性能,即使浪费了内存,但人们应该通过实验来发现问题。

12.10.1.2. vfs.write_behind

vfs.write_behind sysctl(8) 变量的默认值是 1 (on)。这告诉文件系统在收集完整的集群时发出媒体写入,这通常发生在写入大的顺序文件时。这可以避免在 I/O 性能无益的情况下用脏缓冲区使缓冲区缓存饱和。然而,这可能会拖延进程,在某些情况下应该关闭。

12.10.1.3. vfs.hirunningspace

vfs.hirunningspace sysctl(8) 变量决定了在任何给定的实例中,有多少未完成的写 I/O 可以被排到磁盘控制器的系统中。默认值通常是足够的,但在有许多磁盘的机器上,可以尝试把它提高到4或5兆字节。设置太高的值,超过缓冲区缓存的写入阈值,会导致糟糕的集群性能。不要任意设置这个值,因为更高的写入值可能会增加同时进行的读取的延迟。

还有其他各种与缓冲区高速缓存和虚拟机页面高速缓存相关的 sysctl(8) 值。不建议修改这些值,因为虚拟机系统在自动调整自己方面做得很好。

12.10.1.4. vm.swap_idle_enabled

vm.swap_idle_enabled sysctl(8) 变量在有许多活跃的登录用户和大量空闲进程的大型多用户系统中非常有用。这样的系统往往会对自由内存储备产生持续的压力。通过 vm.swap_idle_threshold1vm.swap_idle_threshold2 打开这个功能并调整交换滞后(以空闲秒为单位),比正常的分页算法更快地降低与空闲进程相关的内存页的优先级。这给了分页守护程序一个帮助。只有在需要时才打开这个选项,因为这样做的代价是尽早对内存进行预分页,而不是晚分页,这会消耗更多的交换空间和磁盘带宽。在一个小系统中,这个选项的影响是可以确定的,但是在一个已经在进行适度分页的大系统中,这个选项允许虚拟机系统轻松地将整个进程放入和取出内存。

12.10.1.5. hw.ata.wc

关闭 IDE 写缓存可以减少对 IDE 磁盘的写入带宽,但由于硬盘供应商引入的数据一致性问题,有时可能是必要的。问题是,有些 IDE 硬盘会在写入完成时撒谎。在打开 IDE 写缓存的情况下,IDE硬盘会不按顺序向磁盘写入数据,在磁盘负载较重的情况下,有时会无限期地延迟写入一些块。崩溃或断电可能导致严重的文件系统损坏。通过观察 hw.ata.wc sysctl(8) 变量来检查系统上的默认情况。如果 IDE 写缓存被关闭,可以在 /boot/loader.conf 中把这个只读变量设置为 1,以便在启动时启用它。

更多信息,请参考 ata(4)的手册。

12.10.1.6. SCSI_DELAY (kern.cam.scsi_delay)

SCSI_DELAY 内核配置选项可以用来减少系统启动时间。默认值是相当高的,可能会在启动过程中造成 15 秒的延迟。把它减少到 5 秒通常对现代硬盘有效。应该使用 kern.cam.scsi_delay 启动时间可调。调谐器和内核配置选项接受以毫秒而不是秒为单位的值。

12.10.2. 软更新

要对文件系统进行微调,可以使用 tunefs(8) 。这个程序有许多不同的选项。要打开和关闭软更新,请使用:

  1. # tunefs -n enable /filesystem
  2. # tunefs -n disable /filesystem

文件系统在挂载时不能用 tunefs(8) 来修改。在单用户模式下,启用软更新的好时机是在任何分区被挂载之前。

软更新被推荐用于 UFS 文件系统,因为它通过使用内存缓存极大地提高了元数据的性能,主要是文件的创建和删除。软更新有两个需要注意的缺点。首先,软更新保证了文件系统在崩溃情况下的一致性,但可能很容易比更新物理磁盘晚几秒甚至一分钟。如果系统崩溃了,未写入的数据可能会丢失。其次,软更新延迟了文件系统块的释放。如果根文件系统几乎满了,执行一个主要的更新,如 make installworld,会导致文件系统的空间耗尽,更新失败。

12.10.2.1. 更多关于软更新的细节

元数据更新是对非内容数据的更新,如 inodes 或目录。有两种传统的方法来将文件系统的元数据写回磁盘。

历史上,默认行为是同步写出元数据更新。如果一个目录发生了变化,系统会等待,直到该变化被实际写入磁盘。文件数据缓冲区(文件内容)通过缓冲区缓存,随后异步备份到磁盘。这种实现方式的优点是操作安全。如果在更新过程中出现了故障,元数据总是处于一致的状态。一个文件要么被完全创建,要么根本就没有。如果一个文件的数据块在崩溃时还没有从缓冲区缓存中找到它们的位置,fsck(8) 会识别出这一点,并通过将文件长度设置为 0 来修复文件系统。此外,这个实现是清晰和简单的。缺点是,元数据的改变很慢。例如,rm -r 按顺序触及一个目录中的所有文件,但每个目录的变化都会同步写入磁盘。这包括对目录本身的更新,对 inode 表的更新,以及可能对文件分配的间接块的更新。类似的考虑也适用于使用 tar -x 解开大型层次结构。

第二种方法是使用异步的元数据更新。这是用 mount -o async 挂载的 UFS 文件系统的默认做法。由于所有的元数据更新也要通过缓冲区缓存,它们将与文件内容数据的更新混在一起。这个实现的好处是不需要等待每个元数据的更新被写入磁盘,所以所有引起大量元数据更新的操作都比同步情况下快得多。这个实现仍然是清晰和简单的,所以代码中出现错误的风险很低。缺点是不能保证文件系统的一致状态 如果在更新大量元数据的操作中出现故障,比如断电或有人按下复位按钮,文件系统将被留在不可预测的状态中。当系统再次启动时,没有机会检查文件系统的状态,因为文件的数据块可能已经被写入磁盘,而节点表或相关目录的更新却没有。因为磁盘上没有必要的信息,所以不可能实现 fsck(8) 来清理由此产生的混乱。如果文件系统已经被破坏得无法修复,唯一的选择就是重新格式化并从备份中恢复。

这个问题的通常解决方案是实现脏区记录,这也被称为日记。元数据的更新仍然是同步写入的,但只写入磁盘的一个小区域。后来,它们被移到适当的位置。由于日志区是磁盘上的一个小的、连续的区域,即使在繁重的操作中,磁盘磁头也不会有长距离的移动,所以这些操作比同步更新要快。此外,实现的复杂性是有限的,所以出现错误的风险很低。一个缺点是,所有的元数据都要写两次,一次写到日志区域,一次写到适当的位置,所以可能导致性能“下降”。另一方面,在崩溃的情况下,所有悬而未决的元数据操作可以迅速回滚,或者在系统再次启动后从日志区域完成,从而导致文件系统的快速启动。

Berkeley FFS 的开发者 Kirk McKusick 用软更新解决了这个问题。所有悬而未决的元数据更新被保存在内存中,并以排序的顺序写入磁盘(“有序元数据更新”)。这样做的效果是,在大量元数据操作的情况下,一个项目的后期更新会“抓住”仍在内存中且尚未被写入磁盘的早期更新。在更新被写入磁盘之前,所有的操作一般都是在内存中进行的,而且数据块会根据它们的位置进行排序,这样它们就不会在磁盘上领先于它们的元数据。如果系统崩溃,一个隐含的“日志倒退”会使所有没有被写入磁盘的操作看起来就像它们从未发生一样。一个一致的文件系统状态被维持,看起来是 30 到 60 秒之前的状态。所用的算法保证了所有正在使用的资源在它们的块和节点中都被标记成这样。在崩溃之后,唯一发生的资源分配错误是资源被标记为“已使用”(used),而实际上是“已释放”(free)。fsck(8)识别这种情况,并释放不再使用的资源。在文件系统崩溃后,用 mount -f 强行挂载文件系统的脏状态是安全的。为了释放可能未使用的资源,fsck(8) 需要在以后的时间运行。这就是后台 fsck(8) 的想法:在系统启动时,只记录文件系统的快照,之后再运行 fsck(8) 。然后,所有的文件系统都可以“dirty”挂载,所以系统的启动在多用户模式下进行。然后,后台 fsck(8) 被安排在所有需要这样做的文件系统上,以释放可能未使用的资源。不使用软更新的文件系统仍然需要常规的前台 fsck(8)

其优点是元数据操作的速度几乎与异步更新一样快,而且比记录要快,因为记录要写两次元数据。缺点是代码的复杂性,较高的内存消耗,以及一些特异性的问题。在崩溃之后,文件系统的状态显得有些“旧”。在标准的同步方法会导致一些零长度的文件在 fsck(8) 之后仍然存在的情况下,这些文件在软更新中根本就不存在,因为元数据和文件内容都没有被写入磁盘。在更新被写入磁盘之前,磁盘空间不会被释放,这可能发生在运行 rm(1) 之后的一段时间。当在一个没有足够自由空间容纳所有文件的文件系统上安装大量数据时,这可能会导致问题。