Linux文件路径查找的基本策略,是从查找根(一般是根目录或当前目录)开始,逐级向下查找。具体到代码中,每个查找的节点被表示为path,path的定义如下。
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
我们可以看到,linux用挂载点和目录项来唯一表示一个路径节点。
目录查找,就是不断的从父路径节点找到子目录节点的过程。在路径的查找中,可能会遇到一些跳转,比如遇到挂载点,遇到符号链接,遇到"..",就需要跳转到相应的位置继续查询。
如果没有遇到需要跳转的情况,则主要依赖dentry中保存的信息进行路径查找。dentry中的信息,一个是inode、superblock等文件系统相关的信息,这些信息可以用来从磁盘中查询子目录。除了文件系统信息,dentry也维护了一个Cache,用来存储之前查询过的目录项。
一般来说,dentry有很大的机率被同时访问。并发访问的时候,为了维护dentry结构的内部一致性,每次查找子目录项,都需要对父目录加锁。在高并发的情形下,因为'/'之类的目录很容易被访问文,锁冲突的概率比较大,路径查找的性能就会降低。
linux的解决方案是采用RCU算法,dentry的数据结构被设计为读安全的,即在不加锁的情况下读,可能读到老数据,但不会造成飞指针指之类的恶性事故。linux在每个dentry记录一个版本号,在查询之前会将这个版本号记录下来,等查询操作完成之后,再对比一个版本号有无变化,没有变化,说明这次读操作访问的数据结构是一致的,结果有效。
因为每次路径查找,往往都是对最后一个节点进行修改,最容易冲突的dentry节点往往是最不常被修改的,因此这种算法可以比较有效的解决锁冲突问题。不过这里存在一个问题,如果读操作失败该如何处理?难道要接着重试么?这种重试会不会造成死循环?