Linux内核源码分析-卸载文件系统-sys_umount

来源:互联网 发布:乐视下载软件 编辑:程序博客网 时间:2024/06/06 20:10

本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析内核文件子系统中的卸载文件系统函数。

注意:

1、不描述内核同步、错误处理、参数合法性验证相关的内容

2、源码摘自Linux内核2.6.11.1版

3、阅读本文请结合《深入理解Linux内核》第三版相关章节

4、本文会不定时更新

1、sys_umount

函数源码:

/*
* Now umount can handle mount points as well as block devices.
* This is important for filesystems which use unnamed block devices.
*
* We now support a flag for forced unmount like the other 'big iron'
* unixes. Our API is identical to OSF/1 to avoid making a mess of AMD
*/

asmlinkage long sys_umount(char __user * name, int flags)
{
struct nameidata nd;
int retval;

retval = __user_walk(name, LOOKUP_FOLLOW, &nd);
if (retval)
goto out;
retval = -EINVAL;
if (nd.dentry != nd.mnt->mnt_root)
goto dput_and_out;
if (!check_mnt(nd.mnt))
goto dput_and_out;

retval = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto dput_and_out;

retval = do_umount(nd.mnt, flags);
dput_and_out:
path_release_on_umount(&nd);
out:
return retval;
}

函数处理流程:

1、调用__user_walk函数(path_lookup封装函数)查找路径名信息nameidata对象,并存入局部变量nd中

2、判断查找到的目录是否是安装点目录(nd.dentry 和nd.mnt->mnt_root是否相等),不是则退出

3、调用函数check_mnt验证要卸载的文件系统是否在当前进程的命名空间中,没有则退出

4、调用函数capable验证是否有卸载文件系统的权限

5、调用do_umount卸载文件系统,具体分析见下文

2、do_umount

函数源码:

static int do_umount(struct vfsmount *mnt, int flags)
{
struct super_block * sb = mnt->mnt_sb;
int retval;

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]
*/
if (flags & MNT_EXPIRE) {
if (mnt == current->fs->rootmnt ||
flags & (MNT_FORCE | MNT_DETACH))
return -EINVAL;

if (atomic_read(&mnt->mnt_count) != 2)
return -EBUSY;

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.
*/

lock_kernel();
if( (flags&MNT_FORCE) && sb->s_op->umount_begin)
sb->s_op->umount_begin(sb);
unlock_kernel();

/*
* 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.
*/
if (mnt == current->fs->rootmnt && !(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)) {
lock_kernel();
DQUOT_OFF(sb);
retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
unlock_kernel();
}
up_write(&sb->s_umount);
return retval;
}

down_write(¤t->namespace->sem);
spin_lock(&vfsmount_lock);

if (atomic_read(&sb->s_active) == 1) {
/* last instance - try to be smart */
spin_unlock(&vfsmount_lock);
lock_kernel();
DQUOT_OFF(sb);
acct_auto_close(sb);
unlock_kernel();
security_sb_umount_close(mnt);
spin_lock(&vfsmount_lock);
}
retval = -EBUSY;
if (atomic_read(&mnt->mnt_count) == 2 || flags & MNT_DETACH) {
if (!list_empty(&mnt->mnt_list))
umount_tree(mnt);
retval = 0;
}
spin_unlock(&vfsmount_lock);
if (retval)
security_sb_umount_busy(mnt);
up_write(¤t->namespace->sem);
return retval;
}

函数处理流程:

1、把mnt对象的mnt_sb存入类型为super_block*的局部变量sb中

2、调用安全验证函数security_sb_umount验证安全性

3、如果以参数MNT_EXPIRE卸载文件系统:

(1) 如果是卸载当前进程的根文件系统、卸载参数中包MNT_FORCE或 MNT_DETACH标志,则返回EINVAL

(2) 如果mnt->mnt_count 不等于2,即除了父mnt和该函数外,有其他引用,标志mnt非空闲,返回EBUSY

(3) 调用函数xchg,如果mnt->mnt_expiry_mark为0,即非过期,设置为过期并返回EAGAIN;已过期则执行下一步

4、如果是强制卸载且sb->s_op->umount_begin非空,这调用该函数,用于网络文件系统

5、卸载根文件系统,且卸载标志不含MNT_DETACH(即非懒卸载),如果超级块不是以只读方式安装,以只读方式重新安装

6、如果超级块的引用计数器为1,处理acct相关的信息

7、如果文件系统已为空闲或是懒卸载,调用函数umount_tree卸载以该文件系统根的文件系统树

3、umount_tree

函数源码:

void umount_tree(struct vfsmount *mnt)
{
struct vfsmount *p;
LIST_HEAD(kill);

for (p = mnt; p; p = next_mnt(p, mnt)) {
list_del(&p->mnt_list);
list_add(&p->mnt_list, &kill);
}

while (!list_empty(&kill)) {
mnt = list_entry(kill.next, struct vfsmount, mnt_list);
list_del_init(&mnt->mnt_list);
list_del_init(&mnt->mnt_fslink);
if (mnt->mnt_parent == mnt) {
spin_unlock(&vfsmount_lock);
} else {
struct nameidata old_nd;
detach_mnt(mnt, &old_nd);
spin_unlock(&vfsmount_lock);
path_release(&old_nd);
}
mntput(mnt);
spin_lock(&vfsmount_lock);
}
}

函数处理流程:

1、循环调用函数next_mnt,对以mnt为根的文件系统对象树进行深度优先搜索,每次返回一个文件系统对象,把该对象从文件系统对象链表中删除,然后加入kill链表中、

2、对kill链表中的每个文件系统对象,把该对象从kill链表和文件系统到期链表中删除;如果文件系统不是命名空间的根文件系统,调用函数detach_mnt释放该文件对象相关的数据结构,返回文件系统的安装点信息,调用函数path_release释放安装点目录

4、next_mnt

函数源码:

static struct vfsmount *next_mnt(struct vfsmount *p, struct vfsmount *root)
{
struct list_head *next = p->mnt_mounts.next;
if (next == &p->mnt_mounts) {
while (1) {
if (p == root)
return NULL;
next = p->mnt_child.next;
if (next != &p->mnt_parent->mnt_mounts)
break;
p = p->mnt_parent;
}
}
return list_entry(next, struct vfsmount, mnt_child);
}

函数处理流程:

1、取出当前文件系统对象的子链表的第一个元素存入局部变量next中

2、如果当前文件系统的子文件系统链表为空,循环执行下列步骤

(1) 如果当前文件系统是根,返回NULL

(2) 否则,把当前文件系统对象的兄弟链表中下一个元素存入next

(3) 如果next不是当前文件系统对象的父文件系统对象的子链表的头,即父文件系统子链表未处理完成,退出循环

(4) 如果当前文件系统的父文件系统的子文件系统链表已处理完,把当前文件系统设置为当前文件系统的父文件系统

3、返回包含next的文件系统对象

5、detach_mnt

函数源码:

static void detach_mnt(struct vfsmount *mnt, struct nameidata *old_nd)
{
old_nd->dentry = mnt->mnt_mountpoint; //安装点目录项对象
old_nd->mnt = mnt->mnt_parent; //父文件对象
mnt->mnt_parent = mnt; //把文件对象从文件对象树中移除
mnt->mnt_mountpoint = mnt->mnt_root; //安装点目录项对象
list_del_init(&mnt->mnt_child); //把文件对象从兄弟链表中删除
list_del_init(&mnt->mnt_hash);//把文件对象从文件对象hash表中删除
old_nd->dentry->d_mounted--; //减少原安装点的安装文件系统数
}


0 0