umount流程(从文件系统树移除对应传播属性的装载实例) - 549642238/linux-stable GitHub Wiki
用户态mount工具最终调用内核系统调用接口umount -> ksys_umount -> do_umount,根据传入的Operations不同,可以分为以下情况:
1. 以lazy模式卸载装载实例:mount -l dir
2. 正常卸载装载实例:mount (-R) dir
SYSCALL_DEFINE2(umount, char __user *, name, int, flags) // umount系统调用接口
{
return ksys_umount(name, flags);
}
int ksys_umount(char __user *name, int flags) // umount系统调用
{
retval = user_path_mountpoint_at(AT_FDCWD, name, lookup_flags, &path); // 找到对应路径的path,它是要被umount的装载点
retval = do_umount(mnt, flags); // 具体卸载操作
}
do_umount:卸载装载实例Ref
static int do_umount(struct mount *mnt, int flags)
{
if (&mnt->mnt == current->fs->root.mnt && !(flags & MNT_DETACH)) { // 非lazy模式
return do_umount_root(sb); // 调用具体文件系统接口put_fs_context -> deactivate_super -> deactivate_locked_super -> fs->kill_sb(如果超级块引用计数为0)释放超级块
}
if (flags & MNT_DETACH) { // umount -l走这里,lazy模式,可以递归卸载mnt下的child装载实例
if (!list_empty(&mnt->mnt_list))
umount_tree(mnt, UMOUNT_PROPAGATE); // 从文件系统树移除mnt实例,umount操作会在peer group和slave group传播
retval = 0;
} else {
retval = -EBUSY;
if (!propagate_mount_busy(mnt, 2)) { // 装载实例有child会返回EBUSY,所以对于有child的装载实例如果umount需要umount -R,用户态工具会遍历其所有child并逐个执行umount
if (!list_empty(&mnt->mnt_list))
umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); // 从文件系统树移除mnt实例,umount操作会在peer group和slave group传播
retval = 0;
}
}
}
umount_tree:从文件系统树移除装载实例Ref
static void umount_tree(struct mount *mnt, enum umount_tree_flags how) // 从文件系统树移除mnt实例,umount操作会根据传播属性在多个装载实例下传播
{
for (p = mnt; p; p = next_mnt(p, mnt)) { // 搜集所有要被卸载的装载实例,包括mnt及其递归的child mount实例
p->mnt.mnt_flags |= MNT_UMOUNT;
list_move(&p->mnt_list, &tmp_list); // 搜集齐的要被卸载mount实例放在tmp_list链表
}
list_for_each_entry(p, &tmp_list, mnt_list) { // 这些要被卸载的装载实例不再是任何人的child
list_del_init(&p->mnt_child);
}
if (how & UMOUNT_PROPAGATE)
propagate_umount(&tmp_list); // 传播umount操作到tmp_list每个装载实例parent的slave group和peer group
while (!list_empty(&tmp_list)) {
p = list_first_entry(&tmp_list, struct mount, mnt_list);
list_del_init(&p->mnt_list); // 从命名空间链表中移除要被卸载的装载实例
ns = p->mnt_ns;
if (ns) {
ns->mounts--; // 对应命名空间的装载实例数量-1
}
disconnect = disconnect_mount(p, how); // 卸载操作是同步还是异步执行
if (mnt_has_parent(p)) { // 装载实例如果有parent
mnt_add_count(p->mnt_parent, -1); // parent引用计数-1
if (!disconnect) {
/* Don't forget about p */
list_add_tail(&p->mnt_child, &p->mnt_parent->mnt_mounts);// 恢复parent的child链表
} else { // 同步执行卸载操作
umount_mnt(p); // 从全局哈希表删除装载实例,释放p->mnt_mp
}
}
change_mnt_propagation(p, MS_PRIVATE); // 对于要被卸载的装载实例应当被隔离出来,不要再受mount/umount操作影响其child链表
if (disconnect)
hlist_add_head(&p->mnt_umount, &unmounted); // 异步执行卸载操作
}
}
int propagate_umount(struct list_head *list)
{
list_for_each_entry_reverse(mnt, list, mnt_list) { // 对于list中每个装载实例mnt
struct mount *parent = mnt->mnt_parent; // 针对mnt的parent做umount传播
for (m = propagation_next(parent, parent); m;
m = propagation_next(m, parent)) { // 对于parent的所有peer group和slave group装载实例
if (!list_empty(&child->mnt_umounting)) {
/*
* If the child has already been visited it is
* know that it's entire peer group and all of
* their slaves in the propgation tree for the
* mountpoint has already been visited and there
* is no need to visit this subtree again.
*/
m = skip_propagation_subtree(m, parent);
continue;
} else if (child->mnt.mnt_flags & MNT_UMOUNT) {
/*
* We have come accross an partially unmounted
* mount in list that has not been visited yet.
* Remember it has been visited and continue
* about our merry way.
*/
list_add_tail(&child->mnt_umounting, &visited);
continue;
}
}
}
}
$ fallocate -l 100M sde(n)
$ mkfs.ext4 sde(n)
$ mkdir -p root/var/lib # 针对每个新装载的文件系统
$ mkdir bind(n)
$ mount sde3 root
$ mount sde4 root/var
$ mount sde5 root/var/lib
$ mount --rbind root/ bind1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
80 78 7:1 / /root/root/var rw,relatime shared:30 - ext4 /dev/loop1 rw
82 80 7:2 / /root/root/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
84 19 7:0 / /root/bind1 rw,relatime shared:29 - ext4 /dev/loop0 rw
85 84 7:1 / /root/bind1/var rw,relatime shared:30 - ext4 /dev/loop1 rw
86 85 7:2 / /root/bind1/var/lib rw,relatime shared:31 - ext4 /dev/loop2 rw
$ umount -l bind1 # 或者umount -R bind1
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
a. 装载实例的卸载操作的传播仅针对装载实例的parent及其对应的slave group和peer group,所以对应bind1/var、bind1/var/lib的装载实例的parent bind1、bind1/var和所在的peer group(root/var、root/var/lib)会受到umount影响
b. bind1被umount但root却没受到影响,因为bind1的parent和root是同一个,如果bind1的parent和root的parent是peer group关系则root也会受到影响被umount掉,但现在bind1的parent发现peer group和slave group都为空,所以不会传播umount操作
$ mount sde3 root
$ cat /proc/self/mountinfo
78 19 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
$ unshare -m --propagation unchanged
(在新的命名空间中运行)$ cat /proc/self/mountinfo
110 81 7:0 / /root/root rw,relatime shared:29 - ext4 /dev/loop0 rw
(在新的命名空间中运行)$ umount root
(在新的命名空间中运行)$ cat /proc/self/mountinfo
# nothing
(在原命名空间中运行)$ cat /proc/self/mountinfo
# nothing
a. 装载实例的卸载操作的传播仅针对装载实例的parent及其对应的slave group和peer group,所以对应新命名空间root的装载实例的parent /所在的peer group(旧命名空间/)会受到umount传播影响,旧命名空间的root也被umount掉