Linux中文件名解析解决源码分析
发布时间:2021-11-21 21:33:13 所属栏目:教程 来源:互联网
导读:前言 Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象。在此,对文件名解析过程,并且如何找到对应inode的过程进行源码分析。分析代码基于Linux-3.2版本。 关键函数分析
前言 Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象。在此,对文件名解析过程,并且如何找到对应inode的过程进行源码分析。分析代码基于Linux-3.2版本。 关键函数分析 不管是通过应用层的API函数还是在内核中打开一个文件,最终都需要调用filp_open函数,该函数的主要职责就是解析文件名,找到文件对应的inode对象,然后分配内存创建file对象,最后执行该文件对应的file->open函数。 filp_open的核心处理函数是path_openat,该函数分析如下: static struct file *path_openat(int dfd, const char *pathname, struct nameidata *nd, const struct open_flags *op, int flags) { struct file *base = NULL; struct file *filp; struct path path; int error; /* 创建一个file对象 */ filp = get_empty_filp(); if (!filp) return ERR_PTR(-ENFILE); .filp->f_flags = op->open_flag; nd->intent.open.file = filp; nd->intent.open.flags = open_to_namei_flags(op->open_flag); nd->intent.open.create_mode = op->mode; /* 初始化检索的起始目录,判断起始目录是根目录还是当前目录,并且初始化nd->inode对象,为link_path_walk函数的解析处理做准备。 */ error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base); if (unlikely(error)) goto out_filp; .current->total_link_count = 0; /* 关键的字符串解析处理函数,其核心思想是分级解析字符串,通过字符串对应的目录项找到下一级目录的inode节点。该函数的具体分析如下。 */ error = link_path_walk(pathname, nd); if (unlikely(error)) goto out_filp; /* do_last函数创建或者获取文件对应的inode对象,并且初始化file对象,至此一个表示打开文件的内存对象filp诞生 */ filp = do_last(nd, &path, op, pathname); while (unlikely(!filp)) { /* trailing symlink */ struct path link = path; void *cookie; if (!(nd->flags & LOOKUP_FOLLOW)) { path_put_conditional(&path, nd); path_put(&nd->path); filp = ERR_PTR(-ELOOP); break; } nd->flags |= LOOKUP_PARENT; nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); error = follow_link(&link, nd, &cookie); if (unlikely(error)) filp = ERR_PTR(error); else filp = do_last(nd, &path, op, pathname); put_link(nd, &link, cookie); } out: if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) path_put(&nd->root); if (base) fput(base); release_open_intent(nd); return filp; .out_filp: filp = ERR_PTR(error); 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函数分析如下: static int link_path_walk(const char *name, struct nameidata *nd) { struct path next; int err; /* 移除’/’字符 */ while (*name=='/') name++; /* 如果解析已经完成,直接返回 */ if (!*name) return 0; ./* At this point we know we have a real path component. */ for(;;) { unsigned long hash; struct qstr this; unsigned int c; int type; /* inode访问的permission检查 */ err = may_lookup(nd); if (err) break; .this.name = name; c = *(const unsigned char *)name; /* 初始化hash值 */ hash = init_name_hash(); do { name++; /* 累计计算名字字符串的hash值 */ hash = partial_name_hash(c, hash); c = *(const unsigned char *)name; /* 如果遇到’/’字符,结束一次hash计算统计 */ } while (c && (c != '/')); /* 得到字符串长度和hash结果 */ this.len = name - (const char *) this.name; this.hash = end_name_hash(hash); .type = LAST_NORM; /* LAST_DOT和LAST_DOTDOT情形判断 */ if (this.name[0] == '.') switch (this.len) { case 2: /* LAST_DOTDOT是上级目录 */ if (this.name[1] == '.') { type = LAST_DOTDOT; nd->flags |= LOOKUP_JUMPED; } break; case 1: /* LAST_DOT是当前目录 */ type = LAST_DOT; } if (likely(type == LAST_NORM)) { /* LAST_NORM标记说明是需要通过本地目录进行字符串解析 */ struct dentry *parent = nd->path.dentry; nd->flags &= ~LOOKUP_JUMPED; if (unlikely(parent->d_flags & DCACHE_OP_HASH)) { /* 如果该标记有效,需要重新计算hash值 */ err = parent->d_op->d_hash(parent, nd->inode, &this); if (err <0) break; } } /* 如果字符串已经解析完毕,直接跳转到last_component */ /* remove trailing slashes? */ if (!c) goto last_component; while (*++name == '/'); if (!*name) goto last_component; /* 通过walk_component函数找到解析字符串对应的inode,并且将nd->inode改称最新inode,准备继续解析后面的字符串信息。因为目录项所管理的inode在系统中通过hash表进行维护,因此,通过hash值可以很容易的找到inode。如果内存中还不存在inode对象,对于ext3文件系统会通过ext3_lookup函数从磁盘上获取inode的元数据信息,并且构造目录项中所有的inode对象。 */ err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW); if (err <0) return err; .if (err) { err = nested_symlink(&next, nd); if (err) return err; } if (can_lookup(nd->inode)) continue; /* 字符串还没有解析完毕,但是当前的inode已经继续不允许解析处理了,所以,返回错误码 */ err = -ENOTDIR; break; /* here ends the main loop */ .last_component: /* 最后一个字符串不需要解析处理,需要由do_last函数来处理,此处结束解析,正确返回 */ nd->last = this; nd->last_type = type; return 0; } terminate_walk(nd); return err; } 小结 文件名解析处理是文件系统的必备功能,通过文件名的解析索引到表示文件的inode内存对象,并且创建文件对象file。在文件名解析的过程中,首先需要确定的是检索起始点,然后通过hash table查找目录项以及检索文件。在查找的过程中,需要考虑文件访问的权限以及符号连接等问题。总体来说这些代码难度不是很大,但是需要有一个整体的思路,就可以更好的理解分析代码了,这里只是对名字解析过程中的几个关键函数进行抛砖引玉式的分析。不正之处,敬请指出。 (编辑:江门站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |