Linux中文件名解析处理源码分析

 

前言
 
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.  
  13.     filp->f_flags = op->open_flag;  
  14.     nd->intent.open.file = filp;  
  15.     nd->intent.open.flags = open_to_namei_flags(op->open_flag);  
  16.     nd->intent.open.create_mode = op->mode;  
  17.     /* 初始化检索的起始目录,判断起始目录是根目录还是当前目录,并且初始化nd->inode对象,为link_path_walk函数的解析处理做准备。 */  
  18.     error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);  
  19.     if (unlikely(error))  
  20.         goto out_filp;  
  21.  
  22.     current->total_link_count = 0;  
  23.     /* 关键的字符串解析处理函数,其核心思想是分级解析字符串,通过字符串对应的目录项找到下一级目录的inode节点。该函数的具体分析如下。 */  
  24.     error = link_path_walk(pathname, nd);  
  25.     if (unlikely(error))  
  26.         goto out_filp;  
  27.     /* do_last函数创建或者获取文件对应的inode对象,并且初始化file对象,至此一个表示打开文件的内存对象filp诞生 */  
  28.     filp = do_last(nd, &path, op, pathname);  
  29.     while (unlikely(!filp)) { /* trailing symlink */  
  30.         struct path link = path;  
  31.         void *cookie;  
  32.         if (!(nd->flags & LOOKUP_FOLLOW)) {  
  33.             path_put_conditional(&path, nd);  
  34.             path_put(&nd->path);  
  35.             filp = ERR_PTR(-ELOOP);  
  36.             break;  
  37.         }  
  38.         nd->flags |= LOOKUP_PARENT;  
  39.         nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);  
  40.         error = follow_link(&link, nd, &cookie);  
  41.         if (unlikely(error))  
  42.             filp = ERR_PTR(error);  
  43.         else  
  44.             filp = do_last(nd, &path, op, pathname);  
  45.         put_link(nd, &link, cookie);  
  46.     }  
  47. out:  
  48.     if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT))  
  49.         path_put(&nd->root);  
  50.     if (base)  
  51.         fput(base);  
  52.     release_open_intent(nd);  
  53.     return filp;  
  54.  
  55. out_filp:  
  56.     filp = ERR_PTR(error);  
  57.     goto out;  

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

小结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值