免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1667 | 回复: 0
打印 上一主题 下一主题

[文件系统] Linux内核源码分析-安装普通文件系统-sys_mount [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2016-10-20 14:34 |只看该作者 |倒序浏览
Linux内核源码分析-安装普通文件系统-sys_mount
本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析内核文件子系统中的安装普通文件系统函数。
注意:
1、不描述内核同步、错误处理、参数合法性验证相关的内容
2、源码摘自Linux内核2.6.11.1版
3、阅读本文请结合《深入理解Linux内核》第三版相关章节
4、本文会不定时更新
1sys_mount
函数源码:
  1. asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
  2.                           char __user * type, unsigned long flags,
  3.                           void __user * data)
  4. {
  5.         int retval;
  6.         unsigned long data_page;
  7.         unsigned long type_page;
  8.         unsigned long dev_page;
  9.         char *dir_page;
  10.     //从用户空间复制文件系统类型字符串到type_page指向的内存地址
  11.         retval = copy_mount_options (type, &type_page);
  12.         if (retval < 0)
  13.                 return retval;
  14.     //从用户空间获取路径名
  15.         dir_page = getname(dir_name);
  16.         retval = PTR_ERR(dir_page);
  17.         if (IS_ERR(dir_page))
  18.                 goto out1;
  19.     //从用户空间复制块设备文件名到内核空间
  20.         retval = copy_mount_options (dev_name, &dev_page);
  21.         if (retval < 0)
  22.                 goto out2;
  23.    //复制与文件系统相关的数据结构的地址
  24.         retval = copy_mount_options (data, &data_page);
  25.         if (retval < 0)
  26.                 goto out3;

  27.         lock_kernel();
  28.     //分析见下文
  29.         retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
  30.                           flags, (void*)data_page);
  31.         unlock_kernel();
  32.         free_page(data_page);

  33. out3:
  34.         free_page(dev_page);
  35. out2:
  36.         putname(dir_page);
  37. out1:
  38.         free_page(type_page);
  39.         return retval;
  40. }
复制代码
函数处理流程:
从用户空间复制数据到内核空间(准备参数)、获取大内核锁、调用do_mount函数
2copy_mount_options
函数源码:
  1. int copy_mount_options(const void __user *data, unsigned long *where)
  2. {
  3.         int i;
  4.         unsigned long page;
  5.         unsigned long size;
  6.        
  7.         *where = 0;
  8.         if (!data)
  9.                 return 0;

  10.         if (!(page = __get_free_page(GFP_KERNEL)))
  11.                 return -ENOMEM;

  12.         /* We only care that *some* data at the address the user
  13.          * gave us is valid.  Just in case, we'll zero
  14.          * the remainder of the page.
  15.          */
  16.         /* copy_from_user cannot cross TASK_SIZE ! */
  17.         size = TASK_SIZE - (unsigned long)data;
  18.         if (size > PAGE_SIZE)
  19.                 size = PAGE_SIZE;

  20.         i = size - exact_copy_from_user((void *)page, data, size);
  21.         if (!i) {
  22.                 free_page(page);
  23.                 return -EFAULT;
  24.         }
  25.         if (i != PAGE_SIZE)
  26.                 memset((char *)page + i, 0, PAGE_SIZE - i);
  27.         *where = page;
  28.         return 0;
  29. }
复制代码
函数处理流程:
1、调用函数__get_free_page分配一个空闲页框并返回页框的线性地址
2、调用函数exact_copy_from_user把用户空间的数据从data复制到该页框中
3、把页剩余的空间置0,并用*where返回页框的起始线性地址
3exact_copy_from_user
函数源码:
  1. /*
  2. * Some copy_from_user() implementations do not return the exact number of
  3. * bytes remaining to copy on a fault.  But copy_mount_options() requires that.
  4. * Note that this function differs from copy_from_user() in that it will oops
  5. * on bad values of `to', rather than returning a short copy.
  6. */
  7. static long
  8. exact_copy_from_user(void *to, const void __user *from, unsigned long n)
  9. {
  10.         char *t = to;
  11.         const char __user *f = from;
  12.         char c;

  13.         if (!access_ok(VERIFY_READ, from, n))
  14.                 return n;

  15.         while (n) {
  16.                 if (__get_user(c, f)) {
  17.                         memset(t, 0, n);
  18.                         break;
  19.                 }
  20.                 *t++ = c;
  21.                 f++;
  22.                 n--;
  23.         }
  24.         return n;
  25. }
复制代码
函数处理流程:
调用函数__get_user一次从用户空间复制一个字符,返回未完成复制的字节数,分析参见后续文章。
4do_mount
函数源码:
  1. /*
  2. * Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
  3. * be given to the mount() call (ie: read-only, no-dev, no-suid etc).
  4. *
  5. * data is a (void *) that can point to any structure up to
  6. * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
  7. * information (or be NULL).
  8. *
  9. * Pre-0.97 versions of mount() didn't have a flags word.
  10. * When the flags word was introduced its top half was required
  11. * to have the magic value 0xC0ED, and this remained so until 2.4.0-test9.
  12. * Therefore, if this magic number is present, it carries no information
  13. * and must be discarded.
  14. */
  15. long do_mount(char * dev_name, char * dir_name, char *type_page,
  16.                   unsigned long flags, void *data_page)
  17. {
  18.         struct nameidata nd;
  19.         int retval = 0;
  20.         int mnt_flags = 0;

  21.         /* Discard magic */
  22.         if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
  23.                 flags &= ~MS_MGC_MSK;

  24.         /* Basic sanity checks */
  25.         if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
  26.                 return -EINVAL;
  27.         if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
  28.                 return -EINVAL;

  29.         if (data_page)
  30.                 ((char *)data_page)[PAGE_SIZE - 1] = 0;

  31.         /* Separate the per-mountpoint flags */
  32.         if (flags & MS_NOSUID)
  33.                 mnt_flags |= MNT_NOSUID;
  34.         if (flags & MS_NODEV)
  35.                 mnt_flags |= MNT_NODEV;
  36.         if (flags & MS_NOEXEC)
  37.                 mnt_flags |= MNT_NOEXEC;
  38.         flags &= ~(MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_ACTIVE);

  39.         /* ... and get the mountpoint */
  40.         retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
  41.         if (retval)
  42.                 return retval;

  43.         retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
  44.         if (retval)
  45.                 goto dput_out;

  46.         if (flags & MS_REMOUNT)
  47.                 retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
  48.                                     data_page);
  49.         else if (flags & MS_BIND)
  50.                 retval = do_loopback(&nd, dev_name, flags & MS_REC);
  51.         else if (flags & MS_MOVE)
  52.                 retval = do_move_mount(&nd, dev_name);
  53.         else
  54.                 retval = do_new_mount(&nd, type_page, flags, mnt_flags,
  55.                                       dev_name, data_page);
  56. dput_out:
  57.         path_release(&nd);
  58.         return retval;
  59. }
复制代码
函数处理流程:
1、把mount安装标识中和安装文件系统描述相关的标志进行转化
2、调用path_lookup函数对安装点路径名进行查找,查找结果存放在nameidata类型的nd局部变量中
3、根据安装标识是重新安装、绑定安装、移动安装、新安装分别调用不同的函数进行处理,本文仅描述新安装,即调用do_new_mount
5do_new_mount
函数源码:
  1. /*
  2. * create a new mount for userspace and request it to be added into the
  3. * namespace's tree
  4. */
  5. static int do_new_mount(struct nameidata *nd, char *type, int flags,
  6.                         int mnt_flags, char *name, void *data)
  7. {
  8.         struct vfsmount *mnt;

  9.         if (!type || !memchr(type, 0, PAGE_SIZE))
  10.                 return -EINVAL;

  11.         /* we need capabilities... */
  12.         if (!capable(CAP_SYS_ADMIN))
  13.                 return -EPERM;

  14.         mnt = do_kern_mount(type, flags, name, data);
  15.         if (IS_ERR(mnt))
  16.                 return PTR_ERR(mnt);

  17.         return do_add_mount(mnt, nd, mnt_flags, NULL);
  18. }
复制代码
函数处理流程:
1、    调用函数do_kern_mount进行实际安装操作,返回新安装文件系统描述符的地址
2、    调用函数do_add_mount把新安装文件系统描述符插入到相关数据结构中
6do_kern_mount
函数源码:
  1. struct vfsmount *
  2. do_kern_mount(const char *fstype, int flags, const char *name, void *data)
  3. {
  4.         struct file_system_type *type = get_fs_type(fstype);
  5.         struct super_block *sb = ERR_PTR(-ENOMEM);
  6.         struct vfsmount *mnt;
  7.         int error;
  8.         char *secdata = NULL;

  9.         if (!type)
  10.                 return ERR_PTR(-ENODEV);

  11.         mnt = alloc_vfsmnt(name);
  12.         if (!mnt)
  13.                 goto out;

  14.         if (data) {
  15.                 secdata = alloc_secdata();
  16.                 if (!secdata) {
  17.                         sb = ERR_PTR(-ENOMEM);
  18.                         goto out_mnt;
  19.                 }

  20.                 error = security_sb_copy_data(type, data, secdata);
  21.                 if (error) {
  22.                         sb = ERR_PTR(error);
  23.                         goto out_free_secdata;
  24.                 }
  25.         }

  26.         sb = type->get_sb(type, flags, name, data);
  27.         if (IS_ERR(sb))
  28.                 goto out_free_secdata;
  29.         error = security_sb_kern_mount(sb, secdata);
  30.         if (error)
  31.                 goto out_sb;
  32.         mnt->mnt_sb = sb; //超级块
  33.         mnt->mnt_root = dget(sb->s_root); //根文件系统
  34.         mnt->mnt_mountpoint = sb->s_root; //挂载点
  35.         mnt->mnt_parent = mnt; //父文件系统
  36.         mnt->mnt_namespace = current->namespace; //命名空间
  37.         up_write(&sb->s_umount);
  38.         put_filesystem(type);
  39.         return mnt;
  40. out_sb:
  41.         up_write(&sb->s_umount);
  42.         deactivate_super(sb);
  43.         sb = ERR_PTR(error);
  44. out_free_secdata:
  45.         free_secdata(secdata);
  46. out_mnt:
  47.         free_vfsmnt(mnt);
  48. out:
  49.         put_filesystem(type);
  50.         return (struct vfsmount *)sb;
  51. }
复制代码
函数处理流程:
1、根据文件系统类型名称,调用函数get_fs_type获得类型为file_system_type的文件系统类型对象的地址,存入局部变量type中
2、调用alloc_vfsmnt从mnt_cache slab高速缓存中分配一个新的超级块对象
3、调用依赖于文件系统的type->get_sb函数分配并初始化一个超级块,具体分析参加后续文章“Linux内核源码分析-ext2分配初始化超级块-ext2_get_sb”
4、初始化mnt相关字段,具体参见注释
7do_add_mount
函数源码:
  1. /*
  2. * add a mount into a namespace's mount tree
  3. * - provide the option of adding the new mount to an expiration list
  4. */
  5. int do_add_mount(struct vfsmount *newmnt, struct nameidata *nd,
  6.         int mnt_flags, struct list_head *fslist)
  7. {
  8.     int err;

  9.     down_write(¤t->namespace->sem);
  10.     /* Something was mounted here while we slept */
  11.     while(d_mountpoint(nd->dentry) && follow_down(&nd->mnt, &nd->dentry))
  12.        ;
  13.     err = -EINVAL;
  14.     if (!check_mnt(nd->mnt))
  15.        goto unlock;

  16.     /* Refuse the same filesystem on the same mount point */
  17.     err = -EBUSY;
  18.     if (nd->mnt->mnt_sb == newmnt->mnt_sb &&
  19.         nd->mnt->mnt_root == nd->dentry)
  20.        goto unlock;

  21.     err = -EINVAL;
  22.     if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
  23.        goto unlock;

  24.     newmnt->mnt_flags = mnt_flags;
  25.     err = graft_tree(newmnt, nd);

  26.     if (err == 0 && fslist) {
  27.        /* add to the specified expiration list */
  28.        spin_lock(&vfsmount_lock);
  29.        list_add_tail(&newmnt->mnt_fslink, fslist);
  30.        spin_unlock(&vfsmount_lock);
  31.     }

  32. unlock:
  33.     up_write(¤t->namespace->sem);
  34.     mntput(newmnt);
  35.     return err;
  36. }
复制代码

函数处理流程:
1、当安装点路径目录项的的安装文件系统数不为0时,调用follow_down更新安装的目录项对象和安装点对应的文件系统对象,具体分析见后续文章(路径名查找)
2、验证安装点的命名空间是否还是当前命名空间,如果不是,返回错误
3、如果文件系统已被安装或安装点是一个符号链接,返回错误
4、初始化do_kern_mount分配的vfsmount 对象newmnt的mnt_flags

5、调用函数graft_tree把新分配的文件对象插入到namespace链表、散列表、父文件系统的子链表中
8alloc_vfsmnt
函数功能:
从mnt_cache slab高速缓存分配一个vfsmount对象,并初始化相关字段,具体信息见注释
函数源码:
  1. struct vfsmount *alloc_vfsmnt(const char *name)
  2. {
  3.         struct vfsmount *mnt = kmem_cache_alloc(mnt_cache, GFP_KERNEL);
  4.         if (mnt) {
  5.                 memset(mnt, 0, sizeof(struct vfsmount));
  6.                 atomic_set(&mnt->mnt_count,1); //引用计数器
  7.                 INIT_LIST_HEAD(&mnt->mnt_hash); //mount_hashtable哈希表链接指针
  8.                 INIT_LIST_HEAD(&mnt->mnt_child); //子文件系统链接指针
  9.                 INIT_LIST_HEAD(&mnt->mnt_mounts); //子文件系统链表头
  10.                 INIT_LIST_HEAD(&mnt->mnt_list);  //namespace链接指针
  11.                 INIT_LIST_HEAD(&mnt->mnt_fslink); //到期文件系统链接指针
  12.                 if (name) {
  13.                         int size = strlen(name)+1;
  14.                         char *newname = kmalloc(size, GFP_KERNEL);
  15.                         if (newname) {
  16.                                 memcpy(newname, name, size);
  17.                                 mnt->mnt_devname = newname; //文件系统设备文件名
  18.                         }
  19.                 }
  20.         }
  21.         return mnt;
  22. }
复制代码
9graft_tree
函数源码(分析参见注释):
  1. static int graft_tree(struct vfsmount *mnt, struct nameidata *nd)
  2. {
  3.         int err;
  4.         if (mnt->mnt_sb->s_flags & MS_NOUSER)
  5.                 return -EINVAL;

  6.         if (S_ISDIR(nd->dentry->d_inode->i_mode) !=
  7.               S_ISDIR(mnt->mnt_root->d_inode->i_mode))
  8.                 return -ENOTDIR;

  9.         err = -ENOENT;
  10.         down(&nd->dentry->d_inode->i_sem);
  11.         if (IS_DEADDIR(nd->dentry->d_inode))
  12.                 goto out_unlock;

  13.         err = security_sb_check_sb(mnt, nd);
  14.         if (err)
  15.                 goto out_unlock;

  16.         err = -ENOENT;
  17.         spin_lock(&vfsmount_lock);
  18.         if (IS_ROOT(nd->dentry) || !d_unhashed(nd->dentry)) {
  19.                 struct list_head head;
  20.                 attach_mnt(mnt, nd); //见下面代码注释
  21.                 list_add_tail(&head, &mnt->mnt_list);
  22.                 list_splice(&head, current->namespace->list.prev);
  23.                 mntget(mnt); //把文件对象插入命名空间链表并增加引用计数器
  24.                 err = 0;
  25.         }
  26.         spin_unlock(&vfsmount_lock);
  27. out_unlock:
  28.         up(&nd->dentry->d_inode->i_sem);
  29.         if (!err)
  30.                 security_sb_post_addmount(mnt, nd);
  31.         return err;
  32. }
  33. static void attach_mnt(struct vfsmount *mnt, struct nameidata *nd)
  34. {
  35.         mnt->mnt_parent = mntget(nd->mnt); //父文件系统
  36.         mnt->mnt_mountpoint = dget(nd->dentry); //安装点目录项
  37.         list_add(&mnt->mnt_hash, mount_hashtable+hash(nd->mnt, nd->dentry)); //哈希表mount_hashtable
  38.         list_add_tail(&mnt->mnt_child, &nd->mnt->mnt_mounts);//父文件系统的子文件系统链表
  39.         nd->dentry->d_mounted++; //安装点安装文件系统数
  40. }
复制代码


您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP