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
观测现象:
- tracepoint 立刻捕获
sync_filesystem调用; - 内核把
/tmp/disk.img里所有脏页、元数据、日志全部刷入 loop 磁盘; - 走完
deactivate_super→down_write→kill_sb→generic_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) // 全局刷所有脏页

浙公网安备 33010602011771号