前言

几乎所有Linux的文件操作,例,read、write、mkdir等都会涉及到路径名查找操作。而文件查找对Linux内核来说,主要指的是找到文件路径对应的Dentry节点。其主要过程就是对路径字符串进行一级级解析(以路径名中的.. , . , /等字符作为解析依据),找到路径的最后一级目录。若传入的路径字符串是以/开始的,那么查找会从系统根目录开始。否则,从当前工作目录开始查找。

然而,查找过程并非仅仅是对路径名一级级解析和匹配,其中还要考虑到如下细节:

用户可能对要查找的路径没有访问权限

路径名查找是系统中频繁且常见的操作,如何保证查找的效率

多个进程 or 用户会同时操作到同一个路径,查找过程中需要做必要的保护

查找过程中可能经过符号链接,同时还要考虑避免符号链接的循环引用,造成无限查找

查找过程可能会跨越多个文件系统类别

…….

引用内核文档对VFS路径查找的介绍:

The most obvious aspect of pathname lookup, which very little exploration is needed to discover, is that it is complex. There are many rules, special cases, and implementation alternatives that all combine to confuse the unwary reader.

上篇文章中介绍了Mount系统调用,其中do_mount函数会进行文件系统挂载点的查找,代码如下:

long do_mount(const char dev_name, const char __user *dir_name,

const char *type_page, unsigned long flags, void *data_page)

{

……..

/ … and get the mountpoint */

retval = user_path(dir_name, &path)

……..

}

user_path()函数传入要查找的路径名,返回struct path结构体类型供后续使用。user_path()最终会调用到filename_lookup(),执行文件查找工作。本章就对filename_lookup()函数进行深入剖析(以Linux 4.4内核为基础)。

数据结构

与路径查找相关的数据结构是struct nameidata,其具体字段及主要作用如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22struct nameidata {

struct path path; //记录路径查找的结果

struct qstr last; //路径名最后一个分量

struct path root; //路径查找的根目录信息,可能会在查找开始时由调用者传入

struct inode    *inode; /* path.dentry.d_inode */

unsigned int    flags; //路径名查找标志

unsigned    seq, m_seq; //

int     last_type; //记录当前查找到目录的类别Normal/Dot/DotDot/Root/Bind

unsigned    depth; //查找过程中跨越的符号链接深度

int     total_link_count; //查找过程中经过的符号链接总数

struct saved {

struct path link;

void *cookie;

const char *name;

struct inode *inode;

unsigned seq;

} *stack, internal[EMBEDDED_LEVELS]; //用来记录查找过程中碰到的符号链接

struct filename *name; //

struct nameidata *saved;//

unsigned    root_seq; //

int     dfd; //

};

filename_lookup

函数filename_lookup解析如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24static int filename_lookup(int dfd, struct filename *name, unsigned flags,

struct path *path, struct path *root)

{

int retval;

struct nameidata nd;

if (IS_ERR(name))

return PTR_ERR(name);

if (unlikely(root)) {

nd.root = *root;

flags |= LOOKUP_ROOT;

}

set_nameidata(&nd, dfd, name);

retval = path_lookupat(&nd, flags | LOOKUP_RCU, path);

if (unlikely(retval == -ECHILD))

retval = path_lookupat(&nd, flags, path);

if (unlikely(retval == -ESTALE))

retval = path_lookupat(&nd, flags | LOOKUP_REVAL, path);

if (likely(!retval))

audit_inode(name, path->dentry, flags & LOOKUP_PARENT);

restore_nameidata();

putname(name);

return retval;

}

Kernel路径名查找的方式有两种:

REF-Walk方式:指的是路径查找过程中,使用Spinlock(dentryàd_lock)并发使用或者修改目录项,来保证系统最终目录项内容的正确性。但是Spinlock因为会引发阻塞,所以效率会低于RCU-Walk.Kernel路径名查找的方式有两种:

RCU-Walk方式:采用RCU锁的方式进行查找,并发查找过程中并不会因为等待spinlock而阻塞,因此速度相对更快。但是它并不能保证所有情况下都能查找成功

filename_lookup函数首先初始化上文介绍的nameidata结构体,接着进行RCU方式查找指定路径。若RCU查找失败,则退回传统的查找方式(REF-Walk)。正因为Linux路径查找穿插了两种查找方式的代码,所以读起来比较困难。本文接下来试图将两种查找方式分开进行介绍。

REF-Walk

path_lookupat主要代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path)

{

const char *s = path_init(nd, flags);

while (!(err = link_path_walk(s, nd))

&& ((err = lookup_last(nd)) > 0)) {

s = trailing_symlink(nd);

if (IS_ERR(s)) {

err = PTR_ERR(s);

break;

}

}

if (!err)

err = complete_walk(nd);

if (!err && nd->flags & LOOKUP_DIRECTORY)

if (!d_can_lookup(nd->path.dentry))

err = -ENOTDIR;

if (!err) {

*path = nd->path;

nd->path.mnt = NULL;

nd->path.dentry = NULL;

}

terminate_walk(nd);

return err;

}

首先path_init对nameidata做必要的初始赋值并返回路径字符串:

如果执行路径查找函数的函数传入了root参数,那么会置起LOOKUP_ROOT标志,此时会对当前用户的对应root目录访问权限进行检查,若不允许,则返回错误。

如果未置起LOOKUP_ROOT,则判断路径名查找从根目录,当前目录或者对应文件描述符对应的目录开始查找,并修改nameidata的path字段,最后修改nameidata的inode字段作为查找起始点的inode。

其次循环执行link_path_walk(),做真正的路径查找,直到碰到查找错误或者查找结束。接下来一节深入剖析。 之后执行complete_walk(),主要为再次确认要访问的dentry是否仍然有效(这取决于对应dentry的文件系统的d_weak_revalidate函数,一般情况下为NULL,且对REF-WALK模式来讲不会被调用到)。 最后执行terminate_walk()前,将nameidata的查找结果(即path)赋值给调用者传入的参数。而terminate_walk()则会将对路径的引用释放,同时将查找过程中跨越的符号链接引用释放掉并将nameidata的深度置为0。

link_path_walk

link_path_walk()首先跳过路径名中的斜杠/,接下来执行其核心:一个for循环。主要做如下事情:

检查当前要查找目录是否有查找的权限(当前目录对应inode是否有EXEC权限),若没有则退出查找。

对当前查找目录计算其Hash长度

若当前查找目录为”..”,则置起来LOOKUP_JUMP标记,表示查找跳过该目录。否则开始路径查找walk_component()

若第三步查找返回为0,则进行下一路径分量的查找(Walk componet过程中会修改nameidata的path以及last等字段)

若第三步查找返回不为0且不为负数,则表示查找过程中碰到符号链接,修改符号链接对应节点的访问时间等信息,并将相关信息记录在nameidata的stack字段。

重新开始执行for循环,直到路径遍历结束(关键变量name为NULL)

walk_component

walk_component主要做如下事情:

处理点符号(即.或者..),这里主要处理的是..返回上一级目录,这里要特别处理的是上级目录可能与当前在两个不同的系统挂载点上。

若当前要查找的路径是普通路径,则进行路径的快查找lookup_fast,其会执行dentry = __d_lookup(parent, &nd->last),即从dentry高速缓存中查找。

若第2步查找失败则进行慢查找lookup_slow(),其调用__lookup_hash进行查找,这会执行lookup_real到对应文件系统的i_op到磁盘上去进行查找。

判断查找到的目录分量是否是符号链接,若是符号链接,则看当前遍历的符号链接数量是否超过系统中允许的最大数量(避免循环遍历),如超过则返回错误。否则,在nameidata中的stack字段为当前遍历到的符号链接分配空间并做相应赋值。注意,nameidata字段默认已经预留了储存符号链接的stack空间,只有当遍历过程经过的符号链接超过数量时,才需要重新分配。

将查找的目录分量信息记录在nameidata里,供下个查找循环使用

RCU-Walk

待添加

linux path_lookup,Linux虚拟文件系统(4)-- 路径名查找相关推荐

  1. linux内核之虚拟文件系统

    一.虚拟文件系统概述 虚拟文件系统VFS(也成虚拟文件交换)作为内核子系统,为用户空间程序提供了文件和文件系统相关的统一接口.通过VFS,应用程序可以使用相同接口完成不同介质上不同文件系统的数据读写操 ...

  2. Linux(一) VFS虚拟文件系统

    一.先了解一下什么是挂载 Linux有自己的一套文件系统,例如Ext2.Ext3,但是外部其他文件系统时,由于各个文件系统都各自有一套的文件管理体系,是无法通过Linux本身访问文件的方式直接访问的, ...

  3. 深入linux内核架构--虚拟文件系统VFS

    [推荐阅读] Linux内核源码分析--内核启动之zImage自解压过程 你应该知道的Linux内核基础及内核编译 深入理解LINUX内核堆栈 [零声教育]vico老师教你怎么学习Linux内核 值得 ...

  4. linux 内核 虚拟文件系统VFS 路径查找 path_lookup

    路径查找是VFS的一个主要操作:给定一个文件名,获取该文件名的inode.路径查找是VFS中相当繁琐的一部分,主要是符号链接,文件系统装载点,以及. ..和//等奇怪路径 引入了复杂性. nameid ...

  5. linux 进入文件系统路径,Linux虚拟文件系统--文件路径名的解析(1)--整体过程

    注意之前传递进来的dfd为AT_FDCWD,因此path_init中只有可能出现前两种情况:1.路径以绝对路径的方式给出 2.路径以相对路径的方式给出.此时nd中的path保存了查找的起始目录,对于第 ...

  6. linux文件体系结构和虚拟文件系统

    linux中的虚拟文件系统(Virtual File System, VFS)是一种采用面向对象编程策略(尽管书写操作系统的C语言本身不支持面向对象编程,但是思想还是可以借鉴的),是对该操作系统所支持 ...

  7. 十九.Linux开发之根文件系统移植——根文件系统的原理

    有道云笔记地址: 详情看这里链接,记录太多,就不一一排版了. http://note.youdao.com/noteshare?id=f9c7c1b589233d7b6ed661c3749f1ce8& ...

  8. Linux学习-Linux系统及编程基础笔记

    useradd zhangsan passwd zhangsan visudo往/etc/sudoers文件中添加zhangsan #visudo 找到如下的行 root ALL=(ALL) ALL ...

  9. Linux虚拟文件系统

    从文件 I/O 看 Linux 的虚拟文件系统 1 引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux ...

最新文章

  1. Java 洛谷 P1059 [NOIP2006 普及组] 明明的随机数
  2. Ajax(form表单文件上传、请求头之contentType、Ajax传递json数据、Ajax文件上传)
  3. $sanitize和$sce服务的使用方法
  4. 代码生成技术--CodeDom VS T4
  5. 每天一个Linux命令之ps-查看系统进程信息
  6. 一种替代的多生产者方法
  7. apk提取加密素材_从apk包中提取unity资源
  8. Django:ORM基本操作-CRUD,管理器对象objects,----->聚合查询、原生数据库操作
  9. Zend 创始人提议创建 PHP 方言,暂命名为 P++
  10. flutter 禁止冒泡_【Flutter】Switch开关组件
  11. JAVA中获取工程路径的方法
  12. 13.深入分布式缓存:从原理到实践 --- 缓存在社交网络Feed系统中的架构实践
  13. 24套JAVA企业实战项目教程资源分享
  14. iOS:关于加载GIF图片的思考
  15. 数据恢复工具 winhex使用教程
  16. Python 入门基础
  17. 关于Kaggle入门Titanic的一次简单尝试Part 2 -- Dive into ML
  18. 移动端web设计尺寸_移动端之Web及app设计尺寸
  19. linux从零基础开始
  20. 红牛农场JAVA_JAVA内部类与异常类

热门文章

  1. \n 屏幕换行 源码换行
  2. 16 树的子结构(这题多复习)
  3. 自学it18大数据笔记-第二阶段Hive-day4——会持续更新……
  4. 预备作业02 1501 李俊
  5. Android网络请求通信之Volley
  6. inner/left/right inner
  7. [转载] AttributeError: ‘numpy.ndarray‘ object has no attribute ‘insert‘的解决方法
  8. [转载] 使用python 中的numpy创建数组
  9. 冒泡排序、选择排序、二分查找排序
  10. 通俗易懂的理解 Redux(知乎)