努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处
http://blog.youkuaiyun.com/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; /* Discard magic */ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) flags &= ~MS_MGC_MSK; /* Basic sanity checks */ 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; /* Default to relatime unless overriden */ if (!(flags & MS_NOATIME)) mnt_flags |= MNT_RELATIME; /* Separate the per-mountpoint flags */ 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; /*如果已安装文件系统对象中的安装标志MS_NOSUID,MS_NODEV,MS_NODEV,MS_ACTIVE,MS_NOATIME,MS_NODIRATIME,MS_RELATIME,MS_KERNMOUNT,MS_STRICTATIME中任一个被设置,则清除它们,并在已安装文件系统对象中设置相应的标志(MNT_NOSUID,MNT_NODEV,MNT_NOEXEC,MNT_NOATIME,MNT_NODIRATIME,MNT_READONLY),如果设置了MS_STRICTATIME,则清除MNT_RELATIME,MNT_NOATIME标志*/ flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | MS_STRICTATIME); /*调用path_lookup查找安装点的路径名;该函数把路径名查找的结构存放在nameidata类型的局部变量nd中*/ /* ... and get the mountpoint */ 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; /*如果MS_REMOUNT标志被指定,其目的通常是改变超级块对象s_flags字段的安装标志,以及安装文件系统对象mnt_flags字段的安装文件系统标志,do_remount()函数执行这些改变*/ if (flags & MS_REMOUNT) retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, data_page); /*检查MS_BIND标志,如果他被指定,则用户要求在系统目录树的另一个安装点上的文件或目录能够可见*/ 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); /*检查MS_MOVE标志,如果他被指定,则用户要求改变已安装文件系统的安装点,do_move_mount函数原子的完成这一任务*/ 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; /* we need capabilities... */ 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; /*分配一个vfsmount对象*/ 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; } /*使用type文件系统类型的get_sb方法获得超级块对象,用新超级块的地址初始化mnt->mnt_sb字段*/ 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字段初始化为新安装的文件系统的根目录的dentry*/ mnt->mnt_mountpoint = mnt->mnt_root; /*把mnt->mnt-parent字段初始化为自己*/ 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); /* Something was mounted here while we slept */ while (d_mountpoint(path->dentry) && follow_down(&path->mnt, &path->dentry)) ; err = -EINVAL; if (!check_mnt(path->mnt)) goto unlock; /* Refuse the same filesystem on the same mount point */ 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) /* add to the specified expiration list */ 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; /*调用open_bdev_exclusive()打开设备名为dev_name的块设备*/ bdev = open_bdev_exclusive(dev_name, mode, fs_type); if (IS_ERR(bdev)) return PTR_ERR(bdev); /* * once the super is inserted into the list by sget, s_umount * will protect the lockfs code from trying to start a snapshot * while we are mounting */ down(&bdev->bd_mount_sem); /*调用sget()搜索文件系统的超级块链表(type->fs_fs_supers)。如果找到一个与块设备相关的超级块,则返回它的地址。否则,分配并初始化一个新的超级块对象,把它插入到文件系统链表和超级块全局链表中,并返回其地址*/ s = sget(fs_type, test_bdev_super, set_bdev_super, bdev); up(&bdev->bd_mount_sem); if (IS_ERR(s)) goto error_s; /*查看s->s_root是否为NULL,判断是否为新的超级块*/ 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]; /*把参数flags中的值拷贝到超级块的s_flags字段,并将s_id,s_blocksize字段设置为块设备的合适值*/ 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)); /*调用依赖文件系统的函数(该函数作为传递给get_sb_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变量置为从启动参数"root"中获得的设备文件名*/ 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变量置为同一设备文件的主设备号和次设备号*/ 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: /* *mount_root()函数中完成的是将/dev/root挂载到了/root下,do_mount_root中改变当前目录为/root, *也就是说.就是/root,我们要达到的目的是把/dev/root挂载到/下,所以进行移动挂载,把/dev/root从 *挂载在/root下,改到挂载在/下。 */ sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); } void __init mount_root(void) { .................. #ifdef CONFIG_BLOCK /*调用sys_mknod()在rootfs初始根文件系统中创建设备文件/dev/root,其主次设备号与存放在ROOT_DEV中的一样*/ 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) { /* *扫描上一步建立的文件系统类型名链表。对每个名字,调用sys_mount()试图在根设备上安装给定的文件系统类型。 *由于每个特定于文件系统的方法使用不同的魔数,因此,对get_sb()的调用大多会失败,但有一个例外,那就是用 *根设备上实际使用过的文件系统的函数来填充超级块的那个调用,该文件系统被安装在rootfs文件系统的/root目录上 */ 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: /* we mustn't call path_put() as that would clear mnt_expiry_mark */ 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的值。