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

 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);
	}
}

你可能感兴趣的:(linux,struct,list,kill,Security,Path)