为什么在现有目录上进行挂载?


52

需要一个现有目录作为安装点

$ ls
$ sudo mount /dev/sdb2 ./datadisk
mount: mount point ./datadisk does not exist
$ mkdir datadisk
$ sudo mount /dev/sdb2 ./datadisk
$

我发现它令人困惑,因为它覆盖了目录的现有内容。挂载点目录中有两种可能的内容,它们可能会意外切换(对于未执行挂载的用户)。

为什么没有mount出现在新创建的目录中?这是图形操作系统显示可移动媒体的方式。很明显,目录是已挂载(存在)还是未挂载(不存在)。我很确定这是有充分理由的,但是我还没有发现它。


1
如果您想要这种行为,请使用udisksctl。为什么要使用mount
muru

1
因为它是Unix的方式。因为这样更灵活,所以您可以将其安装在任何地方。因为将它们安装在任何地方都可以使您根据需要扩展服务器,例如,为数据库分区获取新磁盘,将数据库分区中的数据移至新磁盘,然后将其安装在正确的位置以允许数据库数据成长更多。
Rui F Ribeiro

8
作为历史记录,在Windows和LInux基本上摧毁所有其他操作系统之前,有一家名为Apollo的公司。他们编写了一个类似unix(比Unix更好的设计!)的操作系统。它创建了自动挂载NFS导出的目录。实际上,您无法挂载在预先存在的目录上。惠普购买了Apollo,放弃了操作系统,并使用Apollo的64位CPU作为HP-PA。Apollo的远程过程调用系统成为OSF的DCE,它显然存在于Windows内部。知道是成功的一半!
Bruce Ediger 2015年

不知何故,这在我的ubuntu 14.04,3系统上发生了。我还没有调查。当我的SD卡挂载时,它最终会到达下面没有任何路径的路径。如果我卸载了它并尝试将其手动安装回去,我得到的错误是在安装点没有目录。
Skaperen 2015年

2
@BruceEdiger better design than Unix![需要引用]
Ruslan

Answers:


51

这是一个实施细节泄漏的情况。

在UNIX系统中,每个目录都包含一个映射到inode编号的名称列表。索引节点保存元数据,该元数据告诉系统它是文件,目录,特殊设备,命名管道等。如果是文件或目录,它还告诉系统在磁盘上何处查找文件或目录内容。大多数inode是文件或目录。该-i选项ls将列出inode编号。

挂载文件系统需要一个目录索引节点,并在内核的内存副本中设置一个标志,以表示“实际上,当查找该目录的内容时,请转而使用另一个文件系统”(请参见本演示文稿的幻灯片10 )。这是相对容易的,因为它正在更改单个数据项。

它为什么不为您创建指向新inode的目录条目?您可以通过两种方法来实现,这两种方法都有缺点。一种是物理上将新目录写入文件系统-但是如果文件系统是只读的,则失败!另一种是在每个目录列表处理过程中添加实际上并不存在的“额外”内容列表。这很奇怪,并且可能对每个文件操作造成小的性能影响。

如果要动态创建安装点,automount系统可以执行此操作。特殊非磁盘文件系统也可以随意创建目录,例如procsysdevfs等等。

编辑:另请参阅答案,当您“挂载”具有内容的现有文件夹时会发生什么?


除非它不会在索引节点上设置标志。sudo mount --bind / /mnt ; ls /mnt/proc->空。我不知道它是如何工作的。
sourcejedi 2015年

fs/namespace.c我认为确切的操作在;我对来源不熟悉,也不想花太多时间钻研细节。我从链接的演示文稿中获得了“ inode上的标志”。
pjc50

2
@sourcejedi:bind挂载仅绑定您实际引用的文件系统。它们不会递归绑定安装在其下的其他文件系统。这是找到隐藏在坐骑中的垃圾的便捷方法。(例如,如果某些东西在/var/cache某个时间/var挂载失败,但最终在根FS上结束了)path_resolution(7)。(较早的linux-manpages在第2节中提供了该手册页,例如die.net)IDK Linux实际在内部如何工作,以优化检查每个目录组件是否可能进行安装。也许将该VFS条目固定在缓存中?
彼得·科德斯

2
是的,这就是我的意思。因此fs/namei.c(路径-> inode查找)虽然调用了namespace.c lookup_mnt()。Dentry(目录缓存条目)上有一个标志。但这仅仅是优化的实现细节。它不会告诉您在那里安装了哪个文件系统。您必须查看安装表。(有关更多实现的详细信息,请参阅m_hash()。至少,Linux避免了附加的字符串比较,并且AFAICS同时设法在绑定绑定之间重用dentry,因为它是由向导编写的)。
sourcejedi 2015年

1
@PeterCordes man 8 mount:: mount --bind foo foo。bind mount调用仅附加单个文件系统(的一部分),而不附加可能的子安装。使用以下命令,将包括子装载在内的整个文件层次结构附加在第二位mount --rbind olddir newdir
mikeserv

19

如果mount(2) 需要创建新目录作为挂载点,则无法在只读文件系统下挂载任何内容。那将是愚蠢的,所以我们可以排除这一点。

如果mount 可以选择创建一个新目录作为安装点,那将很奇怪。并不是一直都在进行挂载/卸载,因此在内核中添加额外的逻辑以使用单个系统调用来完成这两个步骤并不是很重要的。如果需要,只需留给用户空间进行mkdir(2)系统调用。德米特里(Dmitry)的答案指出,同时mount(2)做这两种事情会使它变得非原子性。而你想要一个额外的参数mount(2)与模式标志一样open(2)重视,对O_CREATO_EXCL等等。这纯粹是愚蠢相比,让用户空间做。

或者,也许您是在问让mount(8)(进行mount(2)系统调用的传统程序)执行此操作?那是可能的,但是已经有一个非常好的mkdir(1)工作,而Unix的设计都是关于可以组合的好的小型工具。如果您想要一个能同时实现这两个功能的工具,则可以很容易地编写一个shell脚本,以使用两个更简单的工具构建该工具。(或者,如大师所说,udisksctl已经这样做了,所以您不必编写它。)而且,mount(8)util-linux的Linux正常模式支持mount -o x-mount.mkdir[=mode]使用它的x-语法来表示用户空间的选项,而不是将选项传递给文件系统。


现在,更有趣的问题是:为什么在父文件系统上根本没有目录?

就像pjc50的答案指出了(没有关系,即使他有我的名字缩写!),在目录列表中显示挂载点也将需要对每个目录进行额外检查readdir()

将挂载点作为目录存在于包含它们的目录中(在父FS上)是一个不错的技巧。 readdir()完全不必注意它是一个安装点。仅将安装点用作路径组件时,才会发生这种情况。当然,路径解析必须检查安装表中路径的每个目录组件。


1
If mount(2) required the creation of a new directory to be the mount point, you couldn't mount anything under a read-only filesystem. That would be dumb-我认为它更聪明:从用户的角度来看,只读文件系统不应更改,但允许挂载意味着它可以更改
Izkata 2015年

2
@Izkata:将文件系统设置为只读并不意味着VFS的整个子树都被冻结。它可能具有指向读写目录的符号链接,或者在重新挂载父fs时已经在其下具有读写安装点ro。对于只读文件系统,有许多用例,其中您的参数没有意义。
彼得·科德斯

2
man 8 mountx-mount.mkdir[=mode] 允许创建目标目录(挂载点)。可选参数mode指定用于mkdir(2)八进制表示法的文件系统访问模式。默认模式是0755。仅root用户支持此功能。
mikeserv

我看不到带有已装入的读写文件系统的只读文件系统的任何重要用例,尤其是在早期的Unix中。@PeterCordes
kubanczyk 2015年

@kubanczyk:只读根文件系统,具有读写/tmp/home。或只读的NFS挂载,/usr/usr/local在其上挂载本地。或更一般而言,任何共享的只读映像上都装有可修改的部分。(也可以使用LiveCD可引导映像上使用的自定义文件系统(例如overlayfs或其他Linux联合文件系统)在每个文件的基础上完成对只读映像的本地mod 。)最初,我想到的是最初安装在以下位置的根FS:引导,但使其变为rw可能会在其他安装之前发生。
彼得·科德斯

12

挂载到现有目录mount实际上是一个原子调用:它成功或失败,至少从用户的角度来看。如果mount必须自己创建安装点,它将有两个故障点,从而无法保证干净的回滚。想象以下情况:

  1. mount 成功创建挂载点
  2. mount 尝试将新文件系统挂载到该目录,但失败
  3. mount 尝试删除安装点,但失败

该系统最终会导致失败的副作用mount

这是另一个:

  1. umount 成功卸载文件系统
  2. umount 尝试删除安装点,但失败

现在,应该umount返回成功还是失败?


5
mount对于错误,有8个不同的返回码,也可以将它们组合在一起。当目录删除失败时,它可能只会添加另一个。man7.org/linux/man-pages/man8/mount.8.html#RETURN_CODES
混乱

8
我认为OP正在询问为什么挂载点根本必须是现有目录,而不是为什么mount系统调用不会创建该挂载点。尽管那可能仅仅是我对OP所要问的内容的解释/期望,或者是我是否要问的内容。
彼得·科德斯

3

可能发生的另一种情况:

引导时,基本的只读映像会加载到根目录中。因此,当您想堆砌真正的根时,您想覆盖它。因此,您可以想象mount syscall只是将romountpoint 交换到rw

在这里,假设您在root mountpoint上存在文件系统问题,您希望能够尝试对其进行修复。使用mount重叠,您可以卸载文件系统并使用fsck基本映像中提供的文件系统来解决它。

此功能在需要强大安全性来跟踪ro分区与分区之间的更改的系统中也很有用rw


1
我不确定这如何回答问题。您是否指出,如果mount 需要在挂载点的位置创建一个新目录,则无法在只读文件系统之上挂载任何内容?前段令人困惑:这不是Linux initrd的工作方式。它使用pivot_root系统调用来更改根fs,而不仅仅是在其上装载更多的东西。在接下来的几段中,这很难遵循您的逻辑,因为我认为您在谈论pivot_root(2)
彼得·科德斯

2
@PeterCordes - Linux有不使用一个initrd多年当切换另一根设备的initrd会pivot_root,然后umount在虚拟盘。但是initramfs是rootfs:您不能pivot_rootrootfs也不能将其卸载。而是删除rootfs中的所有内容以释放空间(find -xdev / -exec rm {} \;),用新的root(cd /newmount; mount --move . /; chroot .)覆盖rootfs ,将stdin / stdout / stderr附加到新的/ dev / console上,以及exec新的init
mikeserv

@mikeserv:整齐!当我们开始使用initramfs而不是initrd时,我还没有意识到切换根目录的基本机制。从“确保正确的内核模块插入其中”的角度来看,它们是>。<相同的。 我仍然认为这并不能很好地回答这个问题。似乎假设是“不可能在rofs下挂载”的解释,并给出了一个非常具体的问题案例(这似乎不太可能,因为initramfs在引导时不是只读挂载的。即使可以,也可以将其重新挂载为read -write而不影响cpio.gz映像。)
Peter Cordes

@PeterCordes-我不太明白这个答案。我刚刚看到您的评论-initramfs是一个文件系统-它真的不可能是只读的-它的fs缓存化身。
mikeserv

2

我也一直想知道。

一个简单的包装器,例如:

#!/bin/sh
eval "mkdir -p \"\$$#\"" 
/bin/mount "$@"  

如果保存为可执行脚本mount,该脚本/bin在您的PATH中覆盖的目录中命名,则它会在您感到困扰时解决此问题

(在运行实际的mount二进制文件之前,它会创建一个以的最后一个参数命名的目录mount,如果该目录尚不存在。)


或者,如果您不希望mount包装程序的调用失败以创建目录,则可以执行以下操作:

#!/bin/sh
set -e
eval "lastArg=\"\$$#\""
test -d "$lastArg" || { mkdir "$lastArg"; madeDir=1; }
/bin/mount "$@"  ||  {  test -z "$madeDir" || rmdir "$lastArg"; }

mount命令不应该使用这样创建的目录吗?
muru 2015年

1
@muru这就是最后一行。
PSkocik 2015年

哦,你的意思是,应该这样使用:mount /dev/foo /some/path?我以为它会像它那样工作udisksctl,所以您可以运行mount /dev/foo
muru 2015年

4
你可以得到最后CMDLINE ARG没有eval扩大$#,使用"${@:-1}"。我用DASH进行了测试,因为我认为它不支持POSIX sh所需要的支持。 /bin/dash -c 'echo ${@:-1}' foo bar版画bar
彼得·科德斯

1
您可以使用man -o x-mount.mkdir...
mikeserv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.