linux虚拟文件系统是设备驱动程序的之上的一个抽象层,致力于提供给应用程序一个统一的操作文件的接口。虚拟文件系统的各个数据结构之间的关系比较复杂,画了一张各个数据结构之间的关系图在 http://download.csdn.net/detail/lonewolfxw/4588935,这个清晰的给出了各结构的关系。

1. 目录查找

linux的虚拟文件系统最核心的结构就是dentry缓存,每次查找一个路径时,先在dentry缓存中查找是否有对应的项,例如cd /home/lonewolf则先解析目录结构,查找对应于home的dentry是否在缓存中,若不在则从底层设备中读取,然后查找lonewolf对应的dentry,依次进行,找到了dentry就找到文件对应的inode。因此,dentry缓存和文件目录树相同,按照树的方式组织,并且将已经得到的dentry项加入到dentry_hashtable缓存起来,避免每次都从低速的底层设备中读取,哈希表用来快速的查找。这当然只是一个查找的简略的过程,实际的过程相当复杂,需要处理很多情况,包括:

  • 检测符号链接,是否要跟踪符号链接
  • 检测挂载点,并且重定向到新的文件系统查找
  • 检查路径上每个目录的权限
  • 解析各种复杂的用户输入的路径,如/./home///..//.

linux中的路径查找工作由namei.c中的kern_path操作完成,完成这个函数的主要操作又集中在link_path_walk中:

[cpp] view plain copy
  1. /*
  2. * Name resolution.
  3. * This is the basic name resolution function, turning a pathname into
  4. * the final dentry. We expect 'base' to be positive and a directory.
  5. *
  6. * Returns 0 and nd will have valid dentry and mnt on success.
  7. * Returns error and drops reference to input namei data on failure.
  8. */
  9. static int link_path_walk(const char *name, struct nameidata *nd)<span style="white-space:pre">                     </span>   /*struct nameidata只是一个查找结果的传送定义的一个结构体,查找成功则nd->path就是找到的结果,在执行这个函数之前,先对@nd进行初始化,表示路径查找的起点,若@name是以'/'开始,怎@nd->path就初始化fs_struct->root表示从跟目录开始查找,若@name不是以'/'开始,则初始化fs_struct->pwd表示从进程的当前目录开始查找*/
  10. {
  11. struct path next;
  12. int err;
  13. while (*name=='/') //若name是以/开始,则跳过,然后解析真正的路径的各个部分
  14. name++;
  15. if (!*name)
  16. return 0;
  17. /* At this point we know we have a real path component. */
  18. for(;;) {
  19. unsigned long hash;
  20. struct qstr this;
  21. unsigned int c;
  22. int type;
  23. err = may_lookup(nd); //检查权限,检查@nd对应inode的目录是否有执行权限,访问目录需要目录的执行权限
  24. if (err)
  25. break;
  26. /*下面就是解析目录的各个部分, this表示解析的当前部分的结果*/
  27. this.name = name;
  28. c = *(const unsigned char *)name;
  29. hash = init_name_hash();
  30. do {
  31. name++;
  32. hash = partial_name_hash(c, hash);//一个一个字符累加的哈希值,计算遍历到的部分的哈希值
  33. c = *(const unsigned char *)name;
  34. } while (c && (c != '/')); //字符串结束或者碰到下个部分的开始'/',则停止
  35. this.len = name - (const char *) this.name;
  36. this.hash = end_name_hash(hash);
  37. /*type表示当前部分的类型,用来处理特殊目录'.'和'..'*/
  38. type = LAST_NORM;
  39. if (this.name[0] == '.') switch (this.len) {
  40. case 2:
  41. if (this.name[1] == '.') {
  42. type = LAST_DOTDOT;
  43. nd->flags |= LOOKUP_JUMPED;
  44. }
  45. break;
  46. case 1:
  47. type = LAST_DOT;
  48. }
  49. if (likely(type == LAST_NORM)) {
  50. struct dentry *parent = nd->path.dentry;
  51. nd->flags &= ~LOOKUP_JUMPED;
  52. if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
  53. err = parent->d_op->d_hash(parent, nd->inode,
  54. &this);
  55. if (err < 0)
  56. break;
  57. }
  58. }
  59. /*如果字符串已经结束,或者当前解析之后全是'/',则当前部分是路径的最后一部分,没有进过walk_component处理,因为有可能最后一个部分不是目录,从而跳到last_component处理,由调用link_path_walk出来最后一个部分*/
  60. if (!c)
  61. goto last_component;
  62. while (*++name == '/');
  63. if (!*name)
  64. goto last_component;
  65. err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);/*这个函数执行真正的dentry缓存的查找,在dentry_hashtable中查找@this的名字,找不到则使用inode的inode_operations->lookup中的操作从底层设备载入进来,这个哈希表用父目录的dentry和当前目录名字的hash值也就是this->hash作为键值。而且这个函数还处理目录的权限以及目录是装载点的情况,由于一个目录下可以装载多个文件系统,最新装载的文件系统隐藏以前的装载,若是装载点,则顺着装载点一直查找,直到最上层的装载点也就是当前可以看到的文件系统,当这个函数返回1,则表示这个目录是符号链接,下面进行特殊处理。函数调用成功则 @nd->path 表示this.name这个名字所表示的目录,也是就当前解析成功的目录,然后下一次循环解析下一个部分时候,这个目录就当做父目录在dentry缓存中查找,直至所有的部分全部完成*/
  66. if (err < 0)
  67. return err;
  68. if (err) {
  69. err = nested_symlink(&next, nd);//如果err是1,则处理符号链接
  70. if (err)
  71. return err;
  72. }
  73. if (can_lookup(nd->inode)) /*检查这个部分是否可以查找,也就是说检查这个部分是否是目录,由于除了最后一部分之外,中间的部分必须是目录,不是目录则出错。是最后一项会跳过此处的检查,直接跳到last_component*/
  74. continue;
  75. err = -ENOTDIR;
  76. break;
  77. /* here ends the main loop */
  78. last_component:
  79. nd->last = this;
  80. nd->last_type = type;
  81. return 0;
  82. }
  83. terminate_walk(nd);
  84. return err;
  85. }
[cpp] view plain copy
  1. static inline int walk_component(struct nameidata *nd, struct path *path,
  2. struct qstr *name, int type, int follow)
  3. {
  4. struct inode *inode;
  5. int err;
  6. /*
  7. * "." and ".." are special - ".." especially so because it has
  8. * to be able to know about the current root directory and
  9. * parent relationships.
  10. */
  11. if (unlikely(type != LAST_NORM))
  12. return handle_dots(nd, type); /*处理目录是'.'和'..’的情况,'.'很好处理,直接跳过就可以了,'..'稍微麻烦,因为当前目录有可能是一个装载点,跳到上一级目录就要切换文件系统*/
  13. err = do_lookup(nd, name, path, &inode);/*这个从dentry缓存中查找,找不到就从底层设备中找,并且会处理装载点的情况*/
  14. if (unlikely(err)) {
  15. terminate_walk(nd);
  16. return err;
  17. }
  18. if (!inode) {//没有找到dentry,则表示文件不存在
  19. path_to_nameidata(path, nd);
  20. terminate_walk(nd);
  21. return -ENOENT;
  22. }
  23. if (should_follow_link(inode, follow)) {//检查是否要跟踪符号链接,若是返回1,有nested_symlink处理
  24. if (nd->flags & LOOKUP_RCU) {
  25. if (unlikely(unlazy_walk(nd, path->dentry))) {
  26. terminate_walk(nd);
  27. return -ECHILD;
  28. }
  29. }
  30. BUG_ON(inode != path->dentry->d_inode);
  31. return 1;
  32. }
  33. path_to_nameidata(path, nd);//将找到的path放到nd中返回
  34. nd->inode = inode;
  35. return 0;
  36. }
[cpp] view plain copy
  1. static inline int handle_dots(struct nameidata *nd, int type)
  2. {
  3. if (type == LAST_DOTDOT) {
  4. if (nd->flags & LOOKUP_RCU) {
  5. if (follow_dotdot_rcu(nd))
  6. return -ECHILD;
  7. } else
  8. follow_dotdot(nd);
  9. }
  10. return 0;
  11. }
  12. static int follow_dotdot_rcu(struct nameidata *nd)
  13. {
  14. set_root_rcu(nd); //获得当前进程的根文件系统的path
  15. while (1) {
  16. if (nd->path.dentry == nd->root.dentry &&
  17. nd->path.mnt == nd->root.mnt) { /*如果在根路径上执行"..",没有意义直接跳过就可以了*/
  18. break;
  19. }
  20. if (nd->path.dentry != nd->path.mnt->mnt_root) { /*不是装载点的根目录,就直接获得dentry的parent就可以了*/
  21. struct dentry *old = nd->path.dentry;
  22. struct dentry *parent = old->d_parent;
  23. unsigned seq;
  24. seq = read_seqcount_begin(&parent->d_seq);
  25. if (read_seqcount_retry(&old->d_seq, nd->seq))
  26. goto failed;
  27. nd->path.dentry = parent;
  28. nd->seq = seq;
  29. break;
  30. }
  31. if (!follow_up_rcu(&nd->path))
  32. break;
  33. nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
  34. }
  35. follow_mount_rcu(nd);
  36. nd->inode = nd->path.dentry->d_inode;
  37. return 0;
  38. failed:
  39. nd->flags &= ~LOOKUP_RCU;
  40. if (!(nd->flags & LOOKUP_ROOT))
  41. nd->root.mnt = NULL;
  42. rcu_read_unlock();
  43. br_read_unlock(vfsmount_lock);
  44. return -ECHILD;
  45. }

查找操作比较麻烦,处理的情况很多,沿着目录一步一步一直找到最后一个目录,然后dentry缓存起到很好的加速作用,不用每次都从设备中读取,在解析目录各个部分考虑符号链接和装载点就可以了。

2. 文件系统装载

文件系统装载大概的过程就是先查找文件系统要装载的目录的dentry和vfsmount,然后新建一个vfsmount表示新的装载点,调用文件系统的mount操作,将其装载,并且将新的vfsmount加入vfsmount树中,相应的dentry项设置相关的flag。文件系统的装载由do_mount完成
[cpp] view plain copy
  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 path path;
  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 (data_page) //特定文件系统的私有项,大小为一页
  28. ((char *)data_page)[PAGE_SIZE - 1] = 0;
  29. /* ... and get the mountpoint */
  30. retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); //上文讲的查找装载点的路径
  31. if (retval)
  32. return retval;
  33. retval = security_sb_mount(dev_name, &path,
  34. type_page, flags, data_page); //直接调用security_ops->sb_mount,若成功直接返回
  35. if (retval)
  36. goto dput_out;
  37. /*通过flag配置装载选项,下面是一个多路选择器,根据不同的装载选项调用不同的函数*/
  38. /* Default to relatime unless overriden */
  39. if (!(flags & MS_NOATIME))
  40. mnt_flags |= MNT_RELATIME;
  41. /* Separate the per-mountpoint flags */
  42. if (flags & MS_NOSUID)
  43. mnt_flags |= MNT_NOSUID;
  44. if (flags & MS_NODEV)
  45. mnt_flags |= MNT_NODEV;
  46. if (flags & MS_NOEXEC)
  47. mnt_flags |= MNT_NOEXEC;
  48. if (flags & MS_NOATIME)
  49. mnt_flags |= MNT_NOATIME;
  50. if (flags & MS_NODIRATIME)
  51. mnt_flags |= MNT_NODIRATIME;
  52. if (flags & MS_STRICTATIME)
  53. mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
  54. if (flags & MS_RDONLY)
  55. mnt_flags |= MNT_READONLY;
  56. flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |
  57. MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
  58. MS_STRICTATIME);
  59. if (flags & MS_REMOUNT)
  60. retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
  61. data_page);
  62. else if (flags & MS_BIND)
  63. retval = do_loopback(&path, dev_name, flags & MS_REC);
  64. else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
  65. retval = do_change_type(&path, flags);
  66. else if (flags & MS_MOVE)
  67. retval = do_move_mount(&path, dev_name);
  68. else
  69. retval = do_new_mount(&path, type_page, flags, mnt_flags,
  70. dev_name, data_page);
  71. dput_out:
  72. path_put(&path);
  73. return retval;
  74. }

装载新的文件系统通过do_new_mount来处理:

[cpp] view plain copy
  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 path *path, char *type, int flags,
  6. int mnt_flags, char *name, void *data)
  7. {
  8. struct vfsmount *mnt;
  9. int err;
  10. if (!type)
  11. return -EINVAL;
  12. /* we need capabilities... */
  13. if (!capable(CAP_SYS_ADMIN)) //权限检查
  14. return -EPERM;
  15. mnt = do_kern_mount(type, flags, name, data); /*新建一个vfsmount实例,并通过特定文件系统的操作装载到系统系统中,返回装载点的根目录*/
  16. if (IS_ERR(mnt))
  17. return PTR_ERR(mnt);
  18. err = do_add_mount(mnt, path, mnt_flags); /*将vfsmount加入到vfsmount树中, 设置相关的数据结构的选项*/
  19. if (err)
  20. mntput(mnt);
  21. return err;
  22. }

do_add_mount通过各种检查,例如一个挂载点不能重复挂载其自身在相同的挂载点、挂载点必须是目录等,最后执行namespace.c中的attach_recursive_mnt处理各种数据结构:

[cpp] view plain copy
  1. static int attach_recursive_mnt(struct vfsmount *source_mnt,
  2. struct path *path, struct path *parent_path)
  3. {
  4. LIST_HEAD(tree_list);
  5. struct vfsmount *dest_mnt = path->mnt;
  6. struct dentry *dest_dentry = path->dentry;
  7. struct vfsmount *child, *p;
  8. int err;
  9. if (IS_MNT_SHARED(dest_mnt)) {
  10. err = invent_group_ids(source_mnt, true);
  11. if (err)
  12. goto out;
  13. }
  14. err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list); /*处理从属装载和共享装载,将相关的vfsmount通过tree_list返回*/
  15. if (err)
  16. goto out_cleanup_ids;
  17. br_write_lock(vfsmount_lock);
  18. if (IS_MNT_SHARED(dest_mnt)) {
  19. for (p = source_mnt; p; p = next_mnt(p, source_mnt))
  20. set_mnt_shared(p);
  21. }
  22. if (parent_path) { /*如果source_mnt之前装载在@parent_path,要迁移到@path上,则先从parent_path中移除,然后增加到@path路径上,移除包括从parent_path的vfsmount的子装载点中移除和从mount_hashtable中移除,因为mount_hashtable是通过父装载点的vfsmount和子装载的dentry来计算哈希值的*/
  23. detach_mnt(source_mnt, parent_path);
  24. attach_mnt(source_mnt, path);
  25. touch_mnt_namespace(parent_path->mnt->mnt_ns);
  26. } else { /*parent_path为空表示新挂载项,则设置source_mnt的mnt_parent、mnt_root、mnt_mountpoint,然后将其增加到父装载点的子装载点链表中,并将其加入哈希表*/
  27. mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
  28. commit_tree(source_mnt);
  29. }
  30. /*处理所有的从属装载和共享装载*/
  31. list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
  32. list_del_init(&child->mnt_hash);
  33. commit_tree(child);
  34. }
  35. br_write_unlock(vfsmount_lock);
  36. return 0;
  37. out_cleanup_ids:
  38. if (IS_MNT_SHARED(dest_mnt))
  39. cleanup_group_ids(source_mnt, NULL);
  40. out:
  41. return err;
  42. }

至此,装载文件系统基本完成了。

linux内核装载vfs过程相关推荐

  1. Bochs调试Linux内核6 - 启动过程调试 - 跳到bootsect引导程序执行

    接此,​​​​​​Bochs调试Linux内核5 - 启动过程调试 - 认识Bootsect.S_bcbobo21cn的专栏-CSDN博客 看一下,0x00007c11 这里是重复执行串传送:而后一条 ...

  2. linux内核的配置过程,linux内核的配置机制及其编译过程

    linux内核的配置机制及其编译过程. 一.配置系统的基本结构 Linux内核的配置系统由三个部分组成,分别是: 1.Makefile:分布在 Linux 内核源代码根目录及各层目录中,定义 Linu ...

  3. 实验三:跟踪分析Linux内核的启动过程 ----- 20135108 李泽源

    实验要求: 使用gdb跟踪调试内核从start_kernel到init进程启动 详细分析从start_kernel到init进程启动的过程并结合实验截图撰写一篇署名博客,并在博客文章中注明" ...

  4. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  5. 编译linux内核生成.ko,Linux内核编译完整过程

    通过网上的资料我自己的实际内核编译,我把对Linux内核编译的过程写在这里,也许对其他的Linux爱好者的编译学习有些帮助,其中很大部分是网上的资料,另外就是我在实际编译过程中的一些实际经验. 内核简 ...

  6. 实验三 Linux的启动与关闭,实验三:跟踪分析Linux内核的启动过程

    Ubuntu 16.04下搭建MenuOS的过程: 1.下载内核源代码编译内核 1 # 下载内核源代码编译内核 2 cd ~/LinuxKernel/ 3 wget https://www.kerne ...

  7. linux内核分析作业3:跟踪分析Linux内核的启动过程

    内核源码目录 1. arch:录下x86重点关注 2. init:目录下main.c中的start_kernel是启动内核的起点 3. ipc:进程间通信的目录 实验 使用实验楼的虚拟机打开shell ...

  8. linux进程上下文切换的具体过程,Linux实验三 结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程...

    fork系统调?创建?进程,也就?个进程变成了两个进程,两个进程执?相同的代码,只是fork系统调?在?进程和?进程中的返回值不同. 打开linux-5.4.34/arch/x86/entry/sys ...

  9. 通过gdb调试分析Linux内核的启动过程

    作者:吴乐 山东师范大学 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验流程 1.打开环境 执 ...

  10. linux内核make执行过程

    本篇基于上一篇<<linux内核make menuconfig执行过程>>基础上,追溯make执行过程. make 1. 与make menuconfig相同的部分 这部分内容 ...

最新文章

  1. iOS实现动态区域裁剪图片
  2. FE.ES-异步编程进化史
  3. matlab 林智仁,机器学习:林智仁libsvm 工具箱 在matlab下的应用总结
  4. android 集成同一interface不同泛型_C# 基础知识系列- 10 反射和泛型(二)
  5. sphinx使用小记之使用小结
  6. STM32 中断详解
  7. python类型检测最终指南--Typing的使用
  8. Linux这么多命令怎么记住?
  9. debian的甘特图工具
  10. Linux时间子系统
  11. java设置窗口图标
  12. requests下载多张图片
  13. Zerotier+Microsoft远程桌面 实现内网穿透搭建异地局域网内远程连接控制桌面
  14. python软件安装链接电视_Python爬虫程序:电视剧琅琊榜全集的自动化处理
  15. 一文搞懂supervisor进程管理
  16. 组合数据类型练习,英文词频统计实例上
  17. Dockerfile指令详解镜像构建实例说明
  18. EOS智能合约开发系列(19): 合约应当开源
  19. 专访 IJCAI 17 杰出青年科学家夏立荣博士:以人为本,是群体决策的必由之路
  20. 电路(第三章、线性直流电路一般分析方法)

热门文章

  1. mysql 分页_MySQL如何实现分页查询
  2. 2017-2018-1 20155229 《信息安全系统设计基础》第八周学习总结
  3. excel数据导入到 mysql 中
  4. [jQuery]20+ Brilliant and Advanced jQuery Effects
  5. 如何在VM ware虚拟环境下建立纯软双机热备
  6. “Error:(1, 1) java: 非法字符: '\ufeff'”错误解决办法
  7. 吴恩达机器学习作业(1)
  8. EUV光刻!宇宙最强DDR4内存造出
  9. java基础(十四)-----详解匿名内部类——Java高级开发必须懂的
  10. mysql 8.0.11 Windows安装