红联Linux门户
Linux帮助

Linux虚拟文件系统之文件系统卸载(sys_umount())

发布时间:2014-11-24 15:24:18来源:linux网站作者:bullbat

Linux中卸载文件系统由umount系统调用实现,入口函数为sys_umount()。较于文件系统的安装较为简单,下面是具体的实现。


/*sys_umont系统调用*/ 
SYSCALL_DEFINE2(umount, char __user *, name, int, flags) 

struct path path; 
int retval; 
/*找到装载点的vfsmount实例和dentry实例,二者包装
在一个nameidata结构中*/ 
retval = user_path(name, &path); 
if (retval) 
goto out; 
retval = -EINVAL; 
/*如果查找的最终目录不是文件系统的挂载点*/ 
if (path.dentry != path.mnt->mnt_root) 
goto dput_and_out; 
/*如果要卸载的文件系统还没有安装在命名空间中*/ 
if (!check_mnt(path.mnt)) 
goto dput_and_out; 
 
retval = -EPERM; 
/*如果用户不具有卸载文件系统的特权*/ 
if (!capable(CAP_SYS_ADMIN)) 
goto dput_and_out; 
/*实际umount工作*/ 
retval = do_umount(path.mnt, flags); 
dput_and_out: 
/* we mustn't call path_put() as that would clear mnt_expiry_mark */ 
dput(path.dentry); 
mntput_no_expire(path.mnt); 
out: 
return retval; 
}


卸载实际工作

static int do_umount(struct vfsmount *mnt, int flags) 

/*从vfsmount对象的mnt_sb字段检索超级块对象sb的地址*/ 
struct super_block *sb = mnt->mnt_sb; 
int retval; 
/*初始化umount_list,该链表在后面的释放中会做临时链表
用*/ 
LIST_HEAD(umount_list); 
 
retval = security_sb_umount(mnt, flags); 
if (retval) 
return retval; 
 
/*
* Allow userspace to request a mountpoint be expired rather than
* unmounting unconditionally. Unmount only happens if:
*  (1) the mark is already set (the mark is cleared by mntput())
*  (2) the usage count == 1 [parent vfsmount] + 1 [sys_umount]
*/ 
/*如果设置了MNT_EXPIRE标志,即要标记挂载点“到期”*/ 
if (flags & MNT_EXPIRE) { 
/*若要卸载的文件系统是根文件系统或者同时设置了
MNT_FORCE或MNT_DETACH,则返回-EINVAL*/ 
if (mnt == current->fs->root.mnt || 
flags & (MNT_FORCE | MNT_DETACH)) 
return -EINVAL; 
/*检查vfsmount的引用计数,若不为2,则返回-EBUSY,
要卸载的文件系统在卸载的时候不能有引用者,
这个2代表vfsmount的父vfsmount和sys_umount()对本对象的引用*/ 
if (atomic_read(&mnt->mnt_count) != 2) 
return -EBUSY; 
/*设置vfsmount对象的mnt_expiry_mark字段为1。*/ 
if (!xchg(&mnt->mnt_expiry_mark, 1)) 
return -EAGAIN; 

 
/*
* If we may have to abort operations to get out of this
* mount, and they will themselves hold resources we must
* allow the fs to do things. In the Unix tradition of
* 'Gee thats tricky lets do it in userspace' the umount_begin
* might fail to complete on the first run through as other tasks
* must return, and the like. Thats for the mount program to worry
* about for the moment.
*/ 
/*如果用户要求强制卸载操作,则调用umount_begin
超级块操作中断任何正在进行的安装操作*/ 
/*当然如果特定的文件系统定义了下面函数则调用它*/ 
if (flags & MNT_FORCE && sb->s_op->umount_begin) { 
sb->s_op->umount_begin(sb); 

 
/*
* No sense to grab the lock for this test, but test itself looks
* somewhat bogus. Suggestions for better replacement?
* Ho-hum... In principle, we might treat that as umount + switch
* to rootfs. GC would eventually take care of the old vfsmount.
* Actually it makes sense, especially if rootfs would contain a
* /reboot - static binary that would close all descriptors and
* call reboot(9). Then init(8) could umount root and exec /reboot.
*/ 
/*如果要卸载的文件系统是根文件系统,且用户
并不要求真正地把它卸载下来(即设置了MNT_DETACH标志,
这个标志仅仅标记挂载点为不能再访问,知道挂载不busy
时才卸载),则调用do_remount_sb()重新安装根文件系统为只
读并终止,并返回do_remount_sb()的返回值。*/ 
if (mnt == current->fs->root.mnt && !(flags & MNT_DETACH)) { 
/*
* Special case for "unmounting" root ...
* we just try to remount it readonly.
*/ 
down_write(&sb->s_umount); 
if (!(sb->s_flags & MS_RDONLY)) 
retval = do_remount_sb(sb, MS_RDONLY, NULL, 0); 
up_write(&sb->s_umount); 
return retval; 

 
down_write(&namespace_sem); 
/*为进行写操作而获取当前进程的namespace_sem读/写信号量和vfsmount_lock自旋锁*/ 
spin_unlock(&vfsmount_lock); 
spin_lock(&vfsmount_lock); 
event++; 
 
if (!(flags & MNT_DETACH)) 
shrink_submounts(mnt, &umount_list); 
 
retval = -EBUSY; 
/*如果已安装文件系统不包含任何子安装文件系统的安装点,或者用户要求强制
卸载文件系统,则调用umount_tree()卸载文件系统(及其所有子文件系统)。*/ 
if (flags & MNT_DETACH || !propagate_mount_busy(mnt, 2)) { 
if (!list_empty(&mnt->mnt_list)) 
/*完成实际的底层的卸载文件系统的任务。首先他将mnt的所有孩子移动至kill链表中,
也就是传递进去的umount_list,然后将kill链表中的所有的vfsmount对象的一些字段设为无效状态。
*/ 
umount_tree(mnt, 1, &umount_list); 
retval = 0; 

 
if (retval) 
security_sb_umount_busy(mnt); 
/*释放vfsmount_lock自旋锁和当前进程的namespace_sem读/写信号量*/ 
up_write(&namespace_sem); 
/*减小相应文件系统根目录的目录项对象和已经安装文件系统
描述符的引用计数器值,这些计数器值由path_lookup()增加*/ 
release_mounts(&umount_list); 
return retval; 
}


从内核链表中脱离

/*完成实际的底层的卸载文件系统的任务。首先他将mnt的所有子移动至kill链表中,
也就是传递进去的umount_list,然后将kill链表中的所有的vfsmount对象的一些字段设为无效状态。
*/ 
void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill) 

struct vfsmount *p; 
 
for (p = mnt; p; p = next_mnt(p, mnt)) 
list_move(&p->mnt_hash, kill); 
 
if (propagate) 
propagate_umount(kill); 
 
list_for_each_entry(p, kill, mnt_hash) { 
list_del_init(&p->mnt_expire); 
list_del_init(&p->mnt_list); 
__touch_mnt_namespace(p->mnt_ns); 
p->mnt_ns = NULL; 
list_del_init(&p->mnt_child); 
if (p->mnt_parent != p) { 
p->mnt_parent->mnt_ghosts++; 
p->mnt_mountpoint->d_mounted--; 

change_mnt_propagation(p, MS_PRIVATE); 

}
 

释放引用计数

void release_mounts(struct list_head *head) 

struct vfsmount *mnt; 
while (!list_empty(head)) { 
mnt = list_first_entry(head, struct vfsmount, mnt_hash); 
list_del_init(&mnt->mnt_hash); 
if (mnt->mnt_parent != mnt) { 
struct dentry *dentry; 
struct vfsmount *m; 
spin_lock(&vfsmount_lock); 
dentry = mnt->mnt_mountpoint; 
m = mnt->mnt_parent; 
mnt->mnt_mountpoint = mnt->mnt_root; 
mnt->mnt_parent = mnt; 
m->mnt_ghosts--; 
spin_unlock(&vfsmount_lock); 
/*下面两个函数为减小引用计数,减到0时释放*/ 
dput(dentry);
mntput(m); 

/*vfsmount对象所占的内存空间最终在mntput()函数中释放*/ 
mntput(mnt); 

}