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)

Example 1. 对一个rbind到一个dest_mnt传播属性为shared的文件系统进行umount

$ 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操作

Example 2. 以shared共享命名空间后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掉

⚠️ **GitHub.com Fallback** ⚠️