umount 如何sync dir的

1. static int do_umount(struct mount *mnt, int flags) 代码中
event++;
	if (flags & MNT_DETACH) {
		if (!list_empty(&mnt->mnt_list))
			umount_tree(mnt, UMOUNT_PROPAGATE);
		retval = 0;
	} else {
	        1.走到这里
		shrink_submounts(mnt);
		retval = -EBUSY;
		if (!propagate_mount_busy(mnt, 2)) {
			if (!list_empty(&mnt->mnt_list))
			        2. 这里
				umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
			retval = 0;
		}
	}
out:
	unlock_mount_hash();
	namespace_unlock();
	return retval;
}

2. 函数static void umount_tree(struct mount *mnt, enum umount_tree_flags how)

		disconnect = disconnect_mount(p, how);
		if (mnt_has_parent(p)) {
			mnt_add_count(p->mnt_parent, -1);
			if (!disconnect) {
				/* Don't forget about p */
				list_add_tail(&p->mnt_child, &p->mnt_parent->mnt_mounts);
			} else {
			        //这里
				umount_mnt(p); // 仅释放 mountpoint,无 mnt_kill 无sync
			}
		}
		change_mnt_propagation(p, MS_PRIVATE);
		if (disconnect)
			hlist_add_head(&p->mnt_umount, &unmounted); // 关键:丢进 unmounted 链表
	}


3.
static void umount_mnt(struct mount *mnt)
{
	put_mountpoint(unhash_mnt(mnt));
}


4.
static void __put_mountpoint(struct mountpoint *mp, struct list_head *list)
{
	if (!--mp->m_count) {
		struct dentry *dentry = mp->m_dentry;
		BUG_ON(!hlist_empty(&mp->m_list));
		spin_lock(&dentry->d_lock);
		dentry->d_flags &= ~DCACHE_MOUNTED;
		spin_unlock(&dentry->d_lock);
		dput_to_list(dentry, list);
		hlist_del(&mp->m_hash);
		kfree(mp);
	}
}

5. 异步任务

static void cleanup_mnt(struct mount *mnt)
{
	struct hlist_node *p;
	struct mount *m;
	/*
	 * The warning here probably indicates that somebody messed
	 * up a mnt_want/drop_write() pair.  If this happens, the
	 * filesystem was probably unable to make r/w->r/o transitions.
	 * The locking used to deal with mnt_count decrement provides barriers,
	 * so mnt_get_writers() below is safe.
	 */
	WARN_ON(mnt_get_writers(mnt));
	if (unlikely(mnt->mnt_pins.first))
		mnt_pin_kill(mnt);
	hlist_for_each_entry_safe(m, p, &mnt->mnt_stuck_children, mnt_umount) {
		hlist_del(&m->mnt_umount);
		mntput(&m->mnt);
	}
	fsnotify_vfsmount_delete(&mnt->mnt);
	dput(mnt->mnt.mnt_root);
	//这里
	deactivate_super(mnt->mnt.mnt_sb);
	mnt_free_id(mnt);
	call_rcu(&mnt->mnt_rcu, delayed_free_vfsmnt);
}

//这里/
如果有人用就不执行deactivate_locked_super
void deactivate_super(struct super_block *s)
{
	if (!atomic_add_unless(&s->s_active, -1, 1)) {
		down_write(&s->s_umount);
		deactivate_locked_super(s);
	}
}


具体实现
void deactivate_locked_super(struct super_block *s)
{
	struct file_system_type *fs = s->s_type;
	if (atomic_dec_and_test(&s->s_active)) {
		unregister_shrinker(&s->s_shrink);
		fs->kill_sb(s);  实际调用 kill_anon_super

		/*
		 * Since list_lru_destroy() may sleep, we cannot call it from
		 * put_super(), where we hold the sb_lock. Therefore we destroy
		 * the lru lists right now.
		 */
		list_lru_destroy(&s->s_dentry_lru);
		list_lru_destroy(&s->s_inode_lru);

		put_filesystem(fs);
		put_super(s);
	} else {
		up_write(&s->s_umount);
	}
}



调用
void kill_anon_super(struct super_block *sb)
{
	dev_t dev = sb->s_dev;
	//这里
	generic_shutdown_super(sb);
	free_anon_bdev(dev);
}
EXPORT_SYMBOL(kill_anon_super);

再调用

void generic_shutdown_super(struct super_block *sb)
{
	const struct super_operations *sop = sb->s_op;

	if (sb->s_root) {
		shrink_dcache_for_umount(sb);
		//这里
		sync_filesystem(sb);
		sb->s_flags &= ~SB_ACTIVE;

		cgroup_writeback_umount();

		/* Evict all inodes with zero refcount. */
		evict_inodes(sb);

		/*
		 * Clean up and evict any inodes that still have references due
		 * to fsnotify or the security policy.
		 */
		fsnotify_sb_delete(sb);
		security_sb_delete(sb);

		/*
		 * Now that all potentially-encrypted inodes have been evicted,
		 * the fscrypt keyring can be destroyed.
		 */
		fscrypt_destroy_keyring(sb);

		if (sb->s_dio_done_wq) {
			destroy_workqueue(sb->s_dio_done_wq);
			sb->s_dio_done_wq = NULL;
		}

		if (sop->put_super)
			sop->put_super(sb);

		if (CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes),
				"VFS: Busy inodes after unmount of %s (%s)",
				sb->s_id, sb->s_type->name)) {
			/*
			 * Adding a proper bailout path here would be hard, but
			 * we can at least make it more likely that a later
			 * iput_final() or such crashes cleanly.
			 */
			struct inode *inode;

			spin_lock(&sb->s_inode_list_lock);
			list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
				inode->i_op = VFS_PTR_POISON;
				inode->i_sb = VFS_PTR_POISON;
				inode->i_mapping = VFS_PTR_POISON;
			}
			spin_unlock(&sb->s_inode_list_lock);
		}
	}
	spin_lock(&sb_lock);
	/* should be initialized for __put_super_and_need_restart() */
	hlist_del_init(&sb->s_instances);
	spin_unlock(&sb_lock);
	up_write(&sb->s_umount);
	if (sb->s_bdi != &noop_backing_dev_info) {
		if (sb->s_iflags & SB_I_PERSB_BDI)
			bdi_unregister(sb->s_bdi);
		bdi_put(sb->s_bdi);
		sb->s_bdi = &noop_backing_dev_info;
	}
}

kill_sb






  deactivate_super
  cleanup_mnt
  task_work_run
  exit_to_user_mode_loop
  exit_to_user_mode_prepare
  syscall_exit_to_user_mode
  do_syscall_64
  entry_SYSCALL_64_after_hwframe
  [unknown]
    b'umount' [18095]

  

 

 

一、先理清:只有卸载真实块设备挂载根、且 super_block 无其他引用时,才会触发 sync_filesystem 全局刷脏

mount --bind 只是别名,umount bind 目标不会刷;下面构造能真实触发 umount 时刷脏的完整可复现例子(XFS/ext4 通用)。

前置准备:临时 loop 磁盘(独立文件系统,拥有独立 super_block)

bash
 
运行
 
 
 
# 1. 创建1G空镜像文件
dd if=/dev/zero of=/tmp/disk.img bs=1M count=1024

# 2. 格式化为xfs(换成mkfs.ext4也可以)
mkfs.xfs /tmp/disk.img

# 3. 创建两个目录,一个原始挂载点,一个bind别名
mkdir -p /mnt/orig /mnt/bind
 

场景流程:会触发 umount 刷脏的完整步骤

步骤 1:挂载真实文件系统(独立 sb)

bash
 
运行
 
 
 
mount -o loop /tmp/disk.img /mnt/orig
 
此时 /mnt/orig 对应一个全新 super_block,s->s_active = 1

步骤 2:制造脏页(写文件不 sync)

bash
 
运行
 
 
 
# 写入大文件,只留在page cache,不刷磁盘
dd if=/dev/urandom of=/mnt/orig/test.dat bs=1M count=200
# 不执行sync/syncfs,数据现在是脏页
 

步骤 3:关键 —— 不保留任何 bind、不打开文件(保证 sb->s_active 始终 = 1)

  • 不要打开 /mnt/orig 下任何文件、不要 cd 进去;
  • 这里我们先不创建 bind,保证只有原始挂载一条引用。

步骤 4:umount 原始挂载点,观察自动刷脏

bash
 
运行
 
 
 
# 开另一个终端监控sync_filesystem
/usr/share/bcc/tools/tracepoint 't:fs:sync_filesystem'

# 执行卸载
umount /mnt/orig
 
观测现象:
  1. tracepoint 立刻捕获 sync_filesystem 调用;
  2. 内核把 /tmp/disk.img 里所有脏页、元数据、日志全部刷入 loop 磁盘;
  3. 走完 deactivate_superdown_writekill_sbgeneric_shutdown_super

二、对比反例:加 bind 后 umount bind 不会刷脏(印证之前结论)

复现不刷脏流程

bash
 
运行
 
 
 
# 重新挂载
mount -o loop /tmp/disk.img /mnt/orig

# 创建bind别名
mount --bind /mnt/orig /mnt/bind

# 写脏数据
dd if=/dev/urandom of=/mnt/orig/test2.dat bs=1M count=200

# 监控sync_filesystem
/usr/share/bcc/tools/tracepoint 't:fs:sync_filesystem'

# 卸载bind别名
umount /mnt/bind
 
现象:无任何 sync_filesystem 输出,脏页还留在内存。
 
原因:
 
umount /mnt/bind 只删除 vfsmount 别名,底层 sb 依然被 /mnt/orig 持有,s->s_active=2,不会进入 deactivate_super 的销毁分支。
此时再执行:
bash
 
运行
 
 
 
umount /mnt/orig
 
才会触发 sync_filesystem 刷脏。

三、内核链路对照(能刷脏的那条路径)

plaintext
 
 
umount /mnt/orig
    do_umount
        deactivate_super(sb)
            s->s_active == 1
            atomic_add_unless返回false
            down_write(&s->s_umount) // 获取卸载锁
            deactivate_locked_super(sb)
                atomic_dec_and_test(&s->s_active) → 0
                    fs->kill_sb(sb)
                        generic_shutdown_super(sb)
                            sync_filesystem(sb) // 全局刷所有脏页
 
 
posted @ 2026-06-13 16:36  rincloud  阅读(4)  评论(0)    收藏  举报