前言
Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象。在此,对文件名解析过程,并且如何找到对应inode的过程进行源码分析。分析代码基于Linux-3.2版本。
关键函数分析
不管是通过应用层的API函数还是在内核中打开一个文件,最终都需要调用filp_open函数,该函数的主要职责就是解析文件名,找到文件对应的inode对象,然后分配内存创建file对象,最后执行该文件对应的file->open函数。
filp_open的核心处理函数是path_openat,该函数分析如下:
  1. static struct file *path_openat(int dfd, const char *pathname,
  2. struct nameidata *nd, const struct open_flags *op, int flags)
  3. {
  4. struct file *base = NULL;
  5. struct file *filp;
  6. struct path path;
  7. int error;
  8. /* 创建一个file对象 */
  9. filp = get_empty_filp();
  10. if (!filp)
  11. return ERR_PTR(-ENFILE);
  12. filp->f_flags = op->open_flag;
  13. nd->intent.open.file = filp;
  14. nd->intent.open.flags = open_to_namei_flags(op->open_flag);
  15. nd->intent.open.create_mode = op->mode;
  16. /* 初始化检索的起始目录,判断起始目录是根目录还是当前目录,并且初始化nd->inode对象,为link_path_walk函数的解析处理做准备。 */
  17. error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);
  18. if (unlikely(error))
  19. goto out_filp;
  20. current->total_link_count = 0;
  21. /* 关键的字符串解析处理函数,其核心思想是分级解析字符串,通过字符串对应的目录项找到下一级目录的inode节点。该函数的具体分析如下。 */
  22. error = link_path_walk(pathname, nd);
  23. if (unlikely(error))
  24. goto out_filp;
  25. /* do_last函数创建或者获取文件对应的inode对象,并且初始化file对象,至此一个表示打开文件的内存对象filp诞生 */
  26. filp = do_last(nd, &path, op, pathname);
  27. while (unlikely(!filp)) { /* trailing symlink */
  28. struct path link = path;
  29. void *cookie;
  30. if (!(nd->flags & LOOKUP_FOLLOW)) {
  31. path_put_conditional(&path, nd);
  32. path_put(&nd->path);
  33. filp = ERR_PTR(-ELOOP);
  34. break;
  35. }
  36. nd->flags |= LOOKUP_PARENT;
  37. nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
  38. error = follow_link(&link, nd, &cookie);
  39. if (unlikely(error))
  40. filp = ERR_PTR(error);
  41. else
  42. filp = do_last(nd, &path, op, pathname);
  43. put_link(nd, &link, cookie);
  44. }
  45. out:
  46. if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT))
  47. path_put(&nd->root);
  48. if (base)
  49. fput(base);
  50. release_open_intent(nd);
  51. return filp;
  52. out_filp:
  53. filp = ERR_PTR(error);
  54. goto out;
  55. }

link_path_walk函数完成了基本的名字解析功能,是名字字符串解析处理实现的核心。该函数的实现基于分级解析处理的思想。例如,当需要解析“/dev/mapper/map0”字符串时,其首先需要判断从何处开始解析?根目录还是当前目录?案例是从根目录开始解析,那么获取根目录的dentry对象并开始分析后继字符串。以’/’字符为界按序提取字符串,首先我们可以提取”dev”字符串,并且计算该字符串的hash值,通过该hash值查找detry下的inode hash表,就可以得到/dev/目录的inode对象。依次类推,最后解析得到”/dev/mapper/”目录的inode对象以及文件名”map0”。至此,link_path_walk函数的使命完成,最后可以通过do_last函数获取或者创建文件inode。link_path_walk函数分析如下:

  1. static int link_path_walk(const char *name, struct nameidata *nd)
  2. {
  3. struct path next;
  4. int err;
  5. /* 移除’/’字符 */
  6. while (*name=='/')
  7. name++;
  8. /* 如果解析已经完成,直接返回 */
  9. if (!*name)
  10. return 0;
  11. /* At this point we know we have a real path component. */
  12. for(;;) {
  13. unsigned long hash;
  14. struct qstr this;
  15. unsigned int c;
  16. int type;
  17. /* inode访问的permission检查 */
  18. err = may_lookup(nd);
  19. if (err)
  20. break;
  21. this.name = name;
  22. c = *(const unsigned char *)name;
  23. /* 初始化hash值 */
  24. hash = init_name_hash();
  25. do {
  26. name++;
  27. /* 累计计算名字字符串的hash值 */
  28. hash = partial_name_hash(c, hash);
  29. c = *(const unsigned char *)name;
  30. /* 如果遇到’/’字符,结束一次hash计算统计 */
  31. } while (c && (c != '/'));
  32. /* 得到字符串长度和hash结果 */
  33. this.len = name - (const char *) this.name;
  34. this.hash = end_name_hash(hash);
  35. type = LAST_NORM;
  36. /* LAST_DOT和LAST_DOTDOT情形判断 */
  37. if (this.name[0] == '.') switch (this.len) {
  38. case 2:  /* LAST_DOTDOT是上级目录 */
  39. if (this.name[1] == '.') {
  40. type = LAST_DOTDOT;
  41. nd->flags |= LOOKUP_JUMPED;
  42. }
  43. break;
  44. case 1: /* LAST_DOT是当前目录 */
  45. type = LAST_DOT;
  46. }
  47. if (likely(type == LAST_NORM)) {
  48. /* LAST_NORM标记说明是需要通过本地目录进行字符串解析 */
  49. struct dentry *parent = nd->path.dentry;
  50. nd->flags &= ~LOOKUP_JUMPED;
  51. if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
  52. /* 如果该标记有效,需要重新计算hash值 */
  53. err = parent->d_op->d_hash(parent, nd->inode,
  54. &this);
  55. if (err < 0)
  56. break;
  57. }
  58. }
  59. /* 如果字符串已经解析完毕,直接跳转到last_component */
  60. /* remove trailing slashes? */
  61. if (!c)
  62. goto last_component;
  63. while (*++name == '/');
  64. if (!*name)
  65. goto last_component;
  66. /* 通过walk_component函数找到解析字符串对应的inode,并且将nd->inode改称最新inode,准备继续解析后面的字符串信息。因为目录项所管理的inode在系统中通过hash表进行维护,因此,通过hash值可以很容易的找到inode。如果内存中还不存在inode对象,对于ext3文件系统会通过ext3_lookup函数从磁盘上获取inode的元数据信息,并且构造目录项中所有的inode对象。 */
  67. err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);
  68. if (err < 0)
  69. return err;
  70. if (err) {
  71. err = nested_symlink(&next, nd);
  72. if (err)
  73. return err;
  74. }
  75. if (can_lookup(nd->inode))
  76. continue;
  77. /* 字符串还没有解析完毕,但是当前的inode已经继续不允许解析处理了,所以,返回错误码 */
  78. err = -ENOTDIR;
  79. break;
  80. /* here ends the main loop */
  81. last_component:
  82. /* 最后一个字符串不需要解析处理,需要由do_last函数来处理,此处结束解析,正确返回 */
  83. nd->last = this;
  84. nd->last_type = type;
  85. return 0;
  86. }
  87. terminate_walk(nd);
  88. return err;
  89. }

小结

文件名解析处理是文件系统的必备功能,通过文件名的解析索引到表示文件的inode内存对象,并且创建文件对象file。在文件名解析的过程中,首先需要确定的是检索起始点,然后通过hash table查找目录项以及检索文件。在查找的过程中,需要考虑文件访问的权限以及符号连接等问题。总体来说这些代码难度不是很大,但是需要有一个整体的思路,就可以更好的理解分析代码了,这里只是对名字解析过程中的几个关键函数进行抛砖引玉式的分析。不正之处,敬请指出。

转载于:https://blog.51cto.com/alanwu/1120652

Linux中文件名解析处理源码分析相关推荐

  1. linux源码文件名,Linux中文件名解析处理源码分析

    Linux中文件名解析处理源码分析 前言 Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象.在此,对文件名 ...

  2. (转)Spring对注解(Annotation)处理源码分析1——扫描和读取Bean定义

    1.从Spring2.0以后的版本中,Spring也引入了基于注解(Annotation)方式的配置,注解(Annotation)是JDK1.5中引入的一个新特性,用于简化Bean的配置,某些场合可以 ...

  3. Go语言中间件框架 Negroni 的静态文件处理源码分析

    Negroni是一个非常棒的中间件,尤其是其中间件调用链优雅的设计,以及对GO HTTP 原生处理器的兼容.我以前写过两篇文章,对Negroni进行了专门的分析,没有看过的朋友可以在看下. Go语言经 ...

  4. Hbase 预写日志WAL处理源码分析之 LogCleaner

    Hlog WALs和oldWALs 这里先介绍一下Hlog失效和Hlog删除的规则 HLog失效:写入数据一旦从MemStore中刷新到磁盘,HLog(默认存储目录在/hbase/WALs下)就会自动 ...

  5. Hbase 预写日志WAL处理源码分析之 LogCleaner

    目录 Hlog  WALs和oldWALs 整体流程 HMaster 初始化 定时执行 LogCleaner 日志清理类 ReplicationLogCleaner 日志清理类 总结 Hlog  WA ...

  6. Linux内核 eBPF基础:kprobe原理源码分析:源码分析

    Linux内核 eBPF基础 kprobe原理源码分析:源码分析 荣涛 2021年5月11日 在 <Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用>中已经介绍了kp ...

  7. Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例

    Linux内核 eBPF基础 kprobe原理源码分析:基本介绍与使用示例 荣涛 2021年5月11日 kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术. 利用kpro ...

  8. Linux内核 eBPF基础:Tracepoint原理源码分析

    Linux内核 eBPF基础 Tracepoint原理源码分析 荣涛 2021年5月10日 1. 基本原理 需要注意的几点: 本文将从sched_switch相关的tracepoint展开: 关于st ...

  9. 动态代理原理源码分析

    看了这篇文章非常不错转载:https://www.jianshu.com/p/4e14dd223897 Java设计模式(14)----------动态代理原理源码分析 上篇文章<Java设计模 ...

  10. Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现(1)

    一.综述 HDFS写文件是整个Hadoop中最为复杂的流程之一,它涉及到HDFS中NameNode.DataNode.DFSClient等众多角色的分工与合作. 首先上一段代码,客户端是如何写文件的: ...

最新文章

  1. MySQL 唯一索引 UNIQUE KEY 会导致死锁?
  2. java与json,java与json
  3. 【Python】matplotlib基础:数据可视化
  4. linux distribution timeline
  5. Ross《随机过程》(第二版)装填问题Python模拟实验
  6. 机器学习——决策树的三种学习方法
  7. 用python编辑word_使用PYTHON编辑和读取WORD文档
  8. 今天看到头条好多新手说摆摊不挣钱
  9. centos 6.8安装git_Centos(6/7)安装GitLab超详细教程
  10. Spark函数讲解: combineByKey
  11. 点云的无序性_三维点云分类与分割-PointNet
  12. java允许跨域设置
  13. 数学建模——粒子群优化算法(PSO)【有详细样例 + 工具:matlab】(万字总结)
  14. JQuery提交表单
  15. citypicker城市选择+高德定位,城市编码统一设置
  16. python哈希类型_Python散列类型和运算符
  17. 07年中国企业500强名单
  18. MATLAB与STK互联46:在场景中加入某个国家作为Area Target对象(GIS命令使用)
  19. 利用python与requests爬取猫眼上的电影数据
  20. java字符串转数组(JAVA把字符串转化为数组)

热门文章

  1. oracle 基本dos命令,Oracle 常用 Dos命令
  2. js 双引号一行两行_一行注释也能影响运行结果?
  3. 六下计算机教学总结,六年级信息技术教师教学工作总结
  4. Go语言中正则表达式的使用
  5. linux内核阻塞IO
  6. Spring的Bean有哪些作用域?
  7. nsupdate处理ns注意事项
  8. Spring+Mybatis+SpringMVC后台与前台分页展示实例(附工程)(转)
  9. Symantec 赛门铁克招聘 Security Response Manager
  10. 迭代求解最优化问题——步长确定