$ unshare -rm
# mount --bind /tmp /
# awk '{ if ($2 == "/") print $0; }' < /proc/mounts
/dev/mapper/alan_dell_2016-fedora / ext4 rw,seclabel,relatime 0 0
tmpfs / tmpfs rw,seclabel,nosuid,nodev 0 0
此新挂载不会更改所/
引用的物理目录。也可以看看/proc/self/root
。更改每个进程的根目录就是这样chroot
做的。当我访问 时/
,它仍然显示我的 ext4 根文件系统的内容,而不是 tmpfs:
# stat -f /tmp --format %T
tmpfs
# stat -f / --format %T
ext2/ext3
# ls -l -d -i /tmp
22161 drwxrwxrwt. 44 nfsnobody nfsnobody 1000 Jul 19 09:49 /tmp
# ls -l -d -i /
2 dr-xr-xr-x. 19 nfsnobody nfsnobody 4096 Jul 7 09:21 /
umount
除了在 tmpfs 挂载上运行之外。这是如何工作的——这两种类型的操作有什么区别?
# umount /
# awk '{ if ($2 == "/") print $0; }' < /proc/mounts
/dev/mapper/alan_dell_2016-fedora / ext4 rw,seclabel,relatime 0 0
环境
$ uname -r # Kernel version
4.17.3-200.fc28.x86_64
系统调用跟踪
我尝试使用umount /
run under重复此strace -f
操作,但它没有显示任何更具启发性的内容。 umount
只是调用umount2()
,并且它不传递任何标志(第二个参数为零)。
# strace -f umount /
...
statfs("/", {f_type=EXT2_SUPER_MAGIC, f_bsize=4096, f_blocks=10288440, f_bfree=2384614, f_bavail=1856230, f_files=2621440, f_ffree=2253065, f_fsid={val=[1557883181, 1665775425]}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_RELATIME}) = 0
stat("/sbin/umount.ext4", 0x7ffd79ccbb40) = -1 ENOENT (No such file or directory)
stat("/sbin/fs.d/umount.ext4", 0x7ffd79ccbb40) = -1 ENOENT (No such file or directory)
stat("/sbin/fs/umount.ext4", 0x7ffd79ccbb40) = -1 ENOENT (No such file or directory)
umount2("/", 0) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
答案1
看看 Linux v4.17 内部,我想我们可以说umount
on/
相当于umount
on /..
。并/..
访问“安装点堆的顶部”。
# stat -f / --format %T
ext2/ext3
# stat -f /.. --format %T
tmpfs
这种模糊的行为..
似乎是 POSIX 允许的。它只说“作为一种特殊情况,在根目录中,点点可能指的是根目录本身。”(POSIX.1-2017,第 4.13 节“路径名解析”)。
如果你想概括这一点并定义umount
on实现的行为其他挂载点,与 进行比较效果..
不太好。术语“安装点堆顶部”仍然适用,尽管它听起来不像一个非常正式的定义:)。
内核源代码
https://elixir.bootlin.com/linux/v4.17/source/fs/namei.c
查看哪里path_mountpoint()
调用follow_mount()
。
/**
* path_mountpoint - look up a path to be umounted
* @nd: lookup context
* @flags: lookup flags
* @path: pointer to container for result
*
* Look up the given name, but don't attempt to revalidate the last component.
* Returns 0 and "path" will be valid on success; Returns error otherwise.
*/
static int
path_mountpoint(struct nameidata *nd, unsigned flags, struct path *path)
{
const char *s = path_init(nd, flags);
int err;
if (IS_ERR(s))
return PTR_ERR(s);
while (!(err = link_path_walk(s, nd)) &&
(err = mountpoint_last(nd)) > 0) {
s = trailing_symlink(nd);
if (IS_ERR(s)) {
err = PTR_ERR(s);
break;
}
}
if (!err) {
*path = nd->path;
nd->path.mnt = NULL;
nd->path.dentry = NULL;
follow_mount(path);
}
terminate_walk(nd);
return err;
}
的评论follow_mount()
听起来像是与问题相关。它提到了follow_dotdot()
主要用户,这表明了这一点的调查。
/*
* Skip to top of mountpoint pile in refwalk mode for follow_dotdot()
*/
static void follow_mount(struct path *path)
{
while (d_mountpoint(path->dentry)) {
struct vfsmount *mounted = lookup_mnt(path);
if (!mounted)
break;
dput(path->dentry);
mntput(path->mnt);
path->mnt = mounted;
path->dentry = dget(mounted->mnt_root);
}
}
static int follow_dotdot(struct nameidata *nd)
我一直在考虑..
(“dotdot”)如何从安装点遍历到其父级,例如/tmp/..
。但我之前没有考虑到这样做可能会“跳到安装点堆的顶部”。即使顶部安装不是包含原始/tmp
目录的安装,也会发生这种情况!