天天看點

虛拟檔案系統之檔案系統的安裝與解除安裝

努力成為linux kernel hacker的人李萬鵬原創作品,為夢而戰。轉載請标明出處

http://blog.csdn.net/woshixingaaa/archive/2011/05/14/6420104.aspx

安裝普通檔案系統

mount系統調用被用來安裝一個普通的檔案系統;它的服務例程是sys_mount()。這個函數首先把參數的值拷貝到臨時核心緩沖區,也就是位于核心棧的這個函數的那些局部變量。擷取大核心鎖,并調用do_mount()函數。

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data) { int retval; unsigned long data_page; unsigned long type_page; unsigned long dev_page; char *dir_page; retval = copy_mount_options(type, &type_page); if (retval < 0) return retval; dir_page = getname(dir_name); retval = PTR_ERR(dir_page); if (IS_ERR(dir_page)) goto out1; retval = copy_mount_options(dev_name, &dev_page); if (retval < 0) goto out2; retval = copy_mount_options(data, &data_page); if (retval < 0) goto out3; lock_kernel(); retval = do_mount((char *)dev_page, dir_page, (char *)type_page, flags, (void *)data_page); unlock_kernel(); free_page(data_page); out3: free_page(dev_page); out2: putname(dir_page); out1: free_page(type_page); return retval; }

long do_mount(char *dev_name, char *dir_name, char *type_page, unsigned long flags, void *data_page) { struct path path; int retval = 0; int mnt_flags = 0; if ((flags & MS_MGC_MSK) == MS_MGC_VAL) flags &= ~MS_MGC_MSK; if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) return -EINVAL; if (dev_name && !memchr(dev_name, 0, PAGE_SIZE)) return -EINVAL; if (data_page) ((char *)data_page)[PAGE_SIZE - 1] = 0; if (!(flags & MS_NOATIME)) mnt_flags |= MNT_RELATIME; if (flags & MS_NOSUID) mnt_flags |= MNT_NOSUID; if (flags & MS_NODEV) mnt_flags |= MNT_NODEV; if (flags & MS_NOEXEC) mnt_flags |= MNT_NOEXEC; if (flags & MS_NOATIME) mnt_flags |= MNT_NOATIME; if (flags & MS_NODIRATIME) mnt_flags |= MNT_NODIRATIME; if (flags & MS_STRICTATIME) mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME); if (flags & MS_RDONLY) mnt_flags |= MNT_READONLY; flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | MS_STRICTATIME); retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); if (retval) return retval; retval = security_sb_mount(dev_name, &path, type_page, flags, data_page); if (retval) goto dput_out; if (flags & MS_REMOUNT) retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, data_page); else if (flags & MS_BIND) retval = do_loopback(&path, dev_name, flags & MS_REC); else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) retval = do_change_type(&path, flags); else if (flags & MS_MOVE) retval = do_move_mount(&path, dev_name); else retval = do_new_mount(&path, type_page, flags, mnt_flags, dev_name, data_page); dput_out: path_put(&path); return retval; }

static int do_new_mount(struct path *path, char *type, int flags, int mnt_flags, char *name, void *data) { struct vfsmount *mnt; if (!type || !memchr(type, 0, PAGE_SIZE)) return -EINVAL; if (!capable(CAP_SYS_ADMIN)) return -EPERM; mnt = do_kern_mount(type, flags, name, data); if (IS_ERR(mnt)) return PTR_ERR(mnt); return do_add_mount(mnt, path, mnt_flags, NULL); }

它調用do_kern_mount函數,給它傳遞的參數為檔案系統類型,安裝标志以及塊裝置名。

do_kern_mount傳回一個新的安裝檔案系統描述符的位址。

struct vfsmount * do_kern_mount(const char *fstype, int flags, const char *name, void *data) { struct file_system_type *type = get_fs_type(fstype); struct vfsmount *mnt; if (!type) return ERR_PTR(-ENODEV); mnt = vfs_kern_mount(type, flags, name, data); if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) && !mnt->mnt_sb->s_subtype) mnt = fs_set_subtype(mnt, fstype); put_filesystem(type); return mnt; } do_kern_mount調用了vfs_kern_mount。 struct vfsmount * vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data) { struct vfsmount *mnt; char *secdata = NULL; int error; if (!type) return ERR_PTR(-ENODEV); error = -ENOMEM; mnt = alloc_vfsmnt(name); if (!mnt) goto out; if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) { secdata = alloc_secdata(); if (!secdata) goto out_mnt; error = security_sb_copy_data(data, secdata); if (error) goto out_free_secdata; } error = type->get_sb(type, flags, name, data, mnt); if (error < 0) goto out_free_secdata; BUG_ON(!mnt->mnt_sb); error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata); if (error) goto out_sb; mnt->mnt_mountpoint = mnt->mnt_root; mnt->mnt_parent = mnt; up_write(&mnt->mnt_sb->s_umount); free_secdata(secdata); return mnt; out_sb: dput(mnt->mnt_root); deactivate_locked_super(mnt->mnt_sb); out_free_secdata: free_secdata(secdata); out_mnt: free_vfsmnt(mnt); out: return ERR_PTR(error); } int do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags, struct list_head *fslist) { int err; down_write(&namespace_sem); while (d_mountpoint(path->dentry) && follow_down(&path->mnt, &path->dentry)) ; err = -EINVAL; if (!check_mnt(path->mnt)) goto unlock; err = -EBUSY; if (path->mnt->mnt_sb == newmnt->mnt_sb && path->mnt->mnt_root == path->dentry) goto unlock; err = -EINVAL; if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode)) goto unlock; newmnt->mnt_flags = mnt_flags; if ((err = graft_tree(newmnt, path))) goto unlock; if (fslist) list_add_tail(&newmnt->mnt_expire, fslist); up_write(&namespace_sem); return 0; unlock: up_write(&namespace_sem); mntput(newmnt); return err; }

do_add_mount實作的基本功能:

1)獲得目前程序的寫信号量namespace_sem,因為函數要更改namespace結構。

2)do_kern_mount()函數可能讓目前程序睡眠;同時,另一個程序可能在完全相同的安裝點上安裝檔案系統或者甚至更改根檔案系統(current->namespace->root)。驗證在該檔案系統安裝點上最近安裝的檔案系統是否仍指向目前的namespace;如果不是,則釋放讀/寫信号量并傳回一個錯誤碼。

3)如果要安裝的檔案系統已經被安裝在由系統調用的參數所指定的安裝點上,或該安裝點是一個符号連結,則釋放讀/寫信号量并傳回一個錯誤碼。

4)初始化由do_kern_mount()配置設定的新安裝檔案系統對象的mnt_flags字段的标志。

5)調用graft_tree()把新安裝的檔案系統對象插入到namespace連結清單,散清單及父檔案系統的子連結清單中。

6)釋放namespace_sem讀/寫信号量并傳回

這裡說明一下真正實作檔案系統安裝的是do_add_mount函數,而不是do_kern_mount函數。do_kern_mount函數隻是得到一個vfsmount,并初始化超級塊之類的。真正的挂載在do_add_mount函數中調用的graft_tree,graft_tree調用的attach_recursive_mnt,attach_recursive_mnt調mnt_set_mountpoint(dest_mnt,dest_dentry,source_mnt)完成的:

void mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry, struct vfsmount *child_mnt) { child_mnt->mnt_parent = mntget(mnt); child_mnt->mnt_mountpoint = dget(dentry); dentry->d_mounted++; }

配置設定超級塊對象

檔案系統對象的get_sb方法通常是由單行函數實作,例如在Ext2檔案系統中該方法的實作如下:

static int ext2_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) { return get_sb_bdev(fs_type, flags, dev_name, data, ext2_fill_super, mnt); }

get_sb_bdev()VFS函數配置設定并初始化一個新的适合于磁盤的檔案系統的超級塊;它接受ext2_fill_super()函數的位址,該函數從Ext2磁盤分區讀取磁盤超級塊。

int get_sb_bdev(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, int (*fill_super)(struct super_block *, void *, int), struct vfsmount *mnt) { struct block_device *bdev; struct super_block *s; fmode_t mode = FMODE_READ; int error = 0; if (!(flags & MS_RDONLY)) mode |= FMODE_WRITE; bdev = open_bdev_exclusive(dev_name, mode, fs_type); if (IS_ERR(bdev)) return PTR_ERR(bdev); down(&bdev->bd_mount_sem); s = sget(fs_type, test_bdev_super, set_bdev_super, bdev); up(&bdev->bd_mount_sem); if (IS_ERR(s)) goto error_s; if (s->s_root) { if ((flags ^ s->s_flags) & MS_RDONLY) { deactivate_locked_super(s); error = -EBUSY; goto error_bdev; } close_bdev_exclusive(bdev, mode); } else { char b[BDEVNAME_SIZE]; s->s_flags = flags; s->s_mode = mode; strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id)); sb_set_blocksize(s, block_size(bdev)); error = fill_super(s, data, flags & MS_SILENT ? 1 : 0); if (error) { deactivate_locked_super(s); goto error; } s->s_flags |= MS_ACTIVE; bdev->bd_super = s; } simple_set_mnt(mnt, s); return 0; error_s: error = PTR_ERR(s); error_bdev: close_bdev_exclusive(bdev, mode); error: return error; }

安裝檔案系統

安裝檔案系統分兩個階段:

1.核心安裝特殊rootfs檔案系統,該檔案系統僅提供一個作為初始安裝點的空目錄。

2.核心在空目錄上安裝實際根檔案系統。

階段一:安裝rootfs檔案系統

第一階段由init_rootfs()和init_mount_tree()函數完成的,它們在系統初始化過程中完成。

static struct file_system_type rootfs_fs_type = { .name = "rootfs", .get_sb = rootfs_get_sb, .kill_sb= kill_litter_super, }; init_rootfs()函數注冊特殊檔案系統類型rootfs: int __init init_rootfs(void) { int err; err = bdi_init(&ramfs_backing_dev_info); if (err) return err; err = register_filesystem(&rootfs_fs_type); if (err) bdi_destroy(&ramfs_backing_dev_info); return err; } init_mount_tree()函數執行如下操作: static void __init init_mount_tree(void) { struct vfsmount *mnt; struct mnt_namespace *ns; struct path root; mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); if (IS_ERR(mnt)) panic("Can't create rootfs"); ns = kmalloc(sizeof(*ns), GFP_KERNEL); if (!ns) panic("Can't allocate initial namespace"); atomic_set(&ns->count, 1); INIT_LIST_HEAD(&ns->list); init_waitqueue_head(&ns->poll); ns->event = 0; list_add(&mnt->mnt_list, &ns->list); ns->root = mnt; mnt->mnt_ns = ns; init_task.nsproxy->mnt_ns = ns; get_mnt_ns(ns); root.mnt = ns->root; root.dentry = ns->root->mnt_root; set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root); }

1.調用do_kern_mount函數,把字元串"rootfs"作為檔案系統類型參數傳遞給它,并把該函數傳回的新安裝檔案系統描述符的位址儲存在mnt局部變量中。do_kern_mount()最終調用rootfs檔案系統的get_sb方法,也即rootfs_get_sb()函數:

static int rootfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) { return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super, mnt); }

get_sb_nodev()函數執行如下步驟:

int get_sb_nodev(struct file_system_type *fs_type, int flags, void *data, int (*fill_super)(struct super_block *, void *, int), struct vfsmount *mnt) { int error; struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL); if (IS_ERR(s)) return PTR_ERR(s); s->s_flags = flags; error = fill_super(s, data, flags & MS_SILENT ? 1 : 0); if (error) { deactivate_locked_super(s); return error; } s->s_flags |= MS_ACTIVE; simple_set_mnt(mnt, s); return 0; }

a.調用sget()函數配置設定新的超級塊,傳遞set_anon_super()函數的位址作為參數。接下來,用合适的方式設定超級塊的s_dev字段:主裝置号為0,次裝置号不同于其他已安裝的特殊檔案系統的次裝置号。

b.将flags參數的值拷貝到超級塊的s_flags字段中。

c.調用ram_fill_super()函數配置設定索引節點和對應的目錄項對象,并填充超級塊字段值。由于rootfs是一種特殊檔案系統,沒有磁盤超級塊,是以隻需執行兩個超級塊操作。

d.傳回新超級塊的位址。

2.為程序0的命名空間配置設定一個namespace對象,并将新配置設定的已安裝檔案系統描述符插入到namespace的連結清單中。

3.将系統中每個程序的namespace字段設定為namespace對象的位址;同時初始化引用計數器namespace->count(預設情況下,所有程序共享同一個初始namespace)。

4.将程序0的根目錄和目前工作目錄設定為根檔案系統。

階段二:安裝實際根檔案系統

prepare_namespace()函數執行如下:

void __init prepare_namespace(void) { ............... if (saved_root_name[0]) { root_device_name = saved_root_name; if (!strncmp(root_device_name, "mtd", 3) || !strncmp(root_device_name, "ubi", 3)) { mount_block_root(root_device_name, root_mountflags); goto out; } ROOT_DEV = name_to_dev_t(root_device_name); if (strncmp(root_device_name, "/dev/", 5) == 0) root_device_name += 5; } if (initrd_load()) goto out; ................ mount_root(); out: sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); } void __init mount_root(void) { .................. #ifdef CONFIG_BLOCK create_dev("/dev/root", ROOT_DEV); mount_block_root("/dev/root", root_mountflags); #endif } void __init mount_block_root(char *name, int flags) { char *fs_names = __getname(); char *p; #ifdef CONFIG_BLOCK char b[BDEVNAME_SIZE]; #else const char *b = name; #endif get_fs_names(fs_names); retry: for (p = fs_names; *p; p += strlen(p)+1) { int err = do_mount_root(name, p, flags, root_mount_data); ............... } .............. } static int __init do_mount_root(char *name, char *fs, int flags, void *data) { int err = sys_mount(name, "/root", fs, flags, data); if (err) return err; sys_chdir("/root"); ........................ return 0; }

解除安裝檔案系統

umount()系統調用用來解除安裝一個檔案系統。

SYSCALL_DEFINE2(umount, char __user *, name, int, flags) { struct path path; int retval; 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; retval = do_umount(path.mnt, flags); dput_and_out: dput(path.dentry); mntput_no_expire(path.mnt); out: return retval; }

該函數執行如下操作:

1.調用user_path()查找安裝點路徑名;該函數把傳回的查找結果存放在nameidata類型的局部變量nd中。

2.如果查找的最終目錄不是檔案系統的安裝點,則設定retval傳回碼為-EINVAL并跑到第6步。這種檢查是通過驗證path.mnt->mnt_root進行的。

3.如果要解除安裝的檔案系統還沒有安裝在命名空間中,則設定retval傳回碼為-EINVAL并跳到第6步,這種檢查是通過驗在path.mnt上調用check_mnt()函數進行的。

4.如果使用者不具有解除安裝檔案系統的特權,則設定retval傳回碼為-EPERM并跳到第6步。

5.調用do_umount(),傳遞給他的參數為path.mnt和flags。

a.從已安裝檔案系統對象的mnt_sb字段檢查超級塊對象的sb的位址。

b.如果使用者要求強制拆卸操作,則調用umount_begin超級塊操作中斷任何正在進行的安裝操作。

c.如果要解除安裝的檔案系統是根檔案系統,且使用者并不要求真正的把它解除安裝下來,則調用do_remount_sb()重新安裝根檔案系統為隻讀并終止。

d.為進行寫操作而擷取目前程序的namespace_sem讀/寫信号量和vfsmount_lock自旋鎖。

e.如果已安裝檔案系統不包含任何子安裝檔案系統的安裝點,或者使用者要求強制解除安裝檔案系統,則調用umount_tree()解除安裝檔案系統(及其所有子檔案系統);

f.釋放vfsmount_lock自旋鎖和目前程序的namespace_sem讀/寫信号量。

6.減少相應檔案系統根目錄的目錄項對象和已安裝檔案系統描述符的引用計數器的值;這些計數器值由path_lookup()增加。

7.傳回retval的值。

繼續閱讀