第四章:重新排列文件系统

有时你会发现你的文件系统并未按照你想要的方式布局,或者你有不必要的重复。

虽然你不能拾取(pick up)和重新定位(relocate)UFS文件系统,并且洗牌ZFS文件系统可能会受到惩罚,但FreeBSD包括两个用于任意重新排列和回收文件系统的工具:null mounts和union mounts。

空挂载和联合挂载对许多人来说是违反直觉的,下面通过示例来理解。

Null Mounts

null mount有时也称作回环(loopback)挂载,提供了第二种访问部分现有文件系统的方式。

你应该见过/usr/home,也见过/home;或者通过/usr/src访问/media。

大多数挂载命令适用于分区或磁盘映像。

空挂载适用于任何目录。使用mount_nullfs命令创建空挂载。

虽然你可以对符号链接执行类似的技巧,但使用工作路径或使用系统调用getwd或getcwd的命令会暴露非符号链接路径。

对于空挂载,此软件会看到空挂载路径。

空挂载适用于UFS和ZFS。

用户不太关心真正的路径,但chroot和jail等应用程序肯定会关心。空挂载可以让你在系统上的许多jail之间共享只读端口树。

通过示例最容易理解空挂载如何改变系统行为。

我们的测试机器将用户主目录放在/home。FreeBSD的安装程序将主目录放在/usr/home下。在这里,我使用空挂载让用户主目录在两个位置都可用。

让我们从使用符号链接开始:

我现在可以在这两个位置访问我的主目录:

pwd命令可以理解我位于一个以/home为根的目录树中,即使我是通过转到/usr/home/mwl到达那里的。

删除符号链接,并将其替换为空挂载。mount_null命令接受两个参数:现有目录树,和你希望挂载它的点。

这对用户有什么影响?我们现在可以转到/usr/home/mwl或/home/mwl,这其实是在访问同一目录,就像使用符号链接一样。

现在我使用的是空挂载,pwd命令显示了我的实际位置:

虽然真正的文件系统以/home为根,但pwd命令只看到了空挂载。

我们中的许多人都有闭源程序或不熟悉的带有硬编码路径的脚本。即使你在服务器上移动文件系统,空挂载也可以使该软件保持工作状态。

你可以将大多数标准mount选项与mount_nullfs一起使用。在命令行中使用-o并列出选项,用逗号分割:

-o ro选项将空挂载为只读。你可以修改/home中的文件,但是在/usr/home中的副本是只读的。这个特殊的例子真的会让人反感,所以你可能不应该这样做。但是,根据你的用例,将文件系统noexec或nosuid空挂载可能是有意义的。虽然noatime和async对于你的用例来说听起来更合理,但它们在null挂载中没有效果。

你可以在启动时使用/etc/fstab条目自动执行空挂载:

如果你决定进入文件系统编程,空挂载是一个很好的起点。它们包含文件系统实现的基本框架。有关详细信息,可参阅mount_nullfs(8)。

Union Mounts

所有FreeBSD文件系统都是可堆叠的,这意味着你可以将一个挂载到另一个上。用户只能看到顶部的文件系统。

同样,这比示例比解释更清晰。

假设目录/usr/src包含FreeBSD源代码。在这里,我们在/usr/src挂载了一个未使用的全新文件系统:

/usr/src目录现在显示为空。我已经将新文件系统堆叠在旧文件系统之上。原始的/usr/src仍然在磁盘上,但没有人能看到它。卸载空分区会显示代码。

联合挂载允许用户在同一位置同时查看两个文件系统的内容。如果我在/usr/src上联合挂载一个文件系统并运行ls,我将看到这两个文件系统的内容。

使用mount_unionfs命令执行联合挂载。第一个参数是挂载在顶部的文件系统;第二个是挂载点和较低的文件系统。下面是一个相当混乱但有说明性的示例:

我把自己的家目录挂载到了系统源代码上。现在/usr/src目录包含了/usr/src以前的所有文件,和我的家目录中所有的内容。所以,它里面会有/usr/src/UPDATING和/usr/src/.cshrc。

我再也找不到任何东西了。

联合挂载可以在UFS和ZFS中使用,但不能混用。你可以将UFS分区联合挂载到另一个UFS分区上,或者将ZFS数据集联合挂载到另一个ZFS数据集中。

在你尝试查看或更改联合挂载的数据之前,联合挂载UFS在ZFS上似乎有效,反之亦然。

联合挂载名声不太好。然而FreeBSD7.0完全重写了联合挂载功能,它比以前更可靠。

mount_unionfs手册页包含大量的可怕警告。常见的情况是,在典型的jail部署中工作得相当好,并且被广泛使用。

然而,联合挂载仍然会做许多文件系统开发人员认为可怕得事情。在部署之前应非常仔细地测试你的系统。

上层和下层

当你查询文件系统时,FreeBSD会首先检查上层。如果失败,查询将转到较低级别。每当你想知道为什么一个联合挂载会有这样的行为时,就回到这个事实上来。

创建的文件位于顶层。使用/usr/src上的/home/mwl联合挂载,如果我创建文件/usr/src/test,该文件将出现在顶部的文件系统中——/home/mwl。如果现在卸载联合挂载,/usr/src/test文件将与我的.cshrc和所有其他个人文件一起消失。

更改的文件也会放在顶层。如果我在这个示例联合挂载中编辑/usr/src/Makefile,FreeBSD会将/usr/src/Makefile复制到顶层,创建/home/mwl/Makefile。编辑位于上层。当断开联合挂载时,原始的/usr/src/Makefile保持不变,编辑后的版本显示在/home/mwl中。

当每个文件系统中的文件具有相同的文件名称时,在联合挂载中只能看到最上面的版本。如果我有/usr/src/UPDATING和/home/mwl/UPDATING,在这个联合挂载中,你只能看到我主目录中的版本。

联合挂载最常见的用途是jail。在本节的其余部分,我们以jail为例。

我在/jails/basejail中安装了一个基本的FreeBSD。我想把它用作/jails/jail1、/jails/jail2等的底层。这些单独的jail目录从空开始。

安装FreeBSD的层需要放在底层,因此任何更改都可以放在顶层。然而我们必须使用顶层的挂载点。使用下面的-o选项来实现:

本节的其余部分将讨论使用此配置的联合挂载的行为。

访问时间和联合挂载

你很快就会注意到,上层获得了一个目录层次结构。读取目录会更新目录的atime(访问时间)。当你进入/jails/jail1并运行 ls /usr/bin 时,上层会出现一个/usr/bin目录。上层的目录具有正确的联合挂载事件,而下层则保持其自己的正确事件。这些被称为影子目录(shadow directories)。

影子目录可能会令人困惑。假设你转到较低层并将/var/log/httpd移动到/var/log/httpd-old。联合挂载仍将有一个/var/log/httpd目录,因为影子目录仍然存在,你需要单独删除该目录。

大多数jail和虚拟主机的文件系统不需要atime。你可以使用noatime挂载选项在联合挂载中禁用atime,而无需在较低的文件系统中禁用它:

我有一个虚拟机确实需要atime,但这是因为我在机器上本地阅读邮件。Web、数据库和应用程序服务器通常不需要它。

删除和重命名文件

从联合装载中删除文件的行为因底层文件系统而异。

ZFS之上的联合挂载将不允许你从较低层删除或重命名文件,因为ZFS缺乏白化(whiteouts)支持。

rm命令看似有效,但实际上以静默方式失败。如果你试图用mv移动一个文件,你将创建该文件的副本。

对于大多数应用程序来说,无法更改较底层是一个好处,而不是问题。如果你在几十个jail下使用一个FreeBSD安装,你不希望这些jail中的任何一个改变底层安装。

UFS支持白化(whiteouts),这允许联合挂载的上层隐藏在下层可见的文件。从UFS支持的联合挂载的下层删除文件实际上会在上层创建一个同名但索引节点号为1的新文件。根据定义,inode 1不是任何文件的一部分。如果一个文件包含inode 1,则它不存在。文件的下层副本是隐藏的,而不是删除的。

白化意味着可以取消删除文件。rm的-W选项试图取消删除被涂白的文件。将文件名作为参数:

取消删除会从较低层显示原始文件。

我们可以在jail中编辑/etc/motd并保持我们的更改,在上层创建一个文件。删除该副本会为下层添加空白。如果我们取消删除文件,我们不会还原编辑过的文件;相反,我们看到的是较底层的/etc/motd。

联合挂载默认为始终为已删除的文件创建空白,即使该文件在较低层中不存在。虽然白化使用的空间很小,但如果你在嵌入式设备上使用联合挂载,你需要你能找到的每一块空间。将白化选项设置为whenneeded,以便使联合挂载仅在需要时创建白化。