Appearance
代码:路径名
路径名查找涉及一系列对 dirlookup
的调用,每个路径组件一次。namei
评估 path
并返回相应的 inode
。函数 nameiparent
是一个变体:它在最后一个元素之前停止,返回父目录的 inode 并将最后一个元素复制到 name
中。两者都调用通用函数 namex
来完成实际工作。
namex
首先决定路径评估从哪里开始。如果路径以斜杠开头,评估从根目录开始;否则,从当前目录开始。然后它使用 skipelem
依次考虑路径的每个元素。循环的每次迭代都必须在当前 inode ip
中查找 name
。迭代开始时锁定 ip
并检查它是否是一个目录。如果不是,查找失败。(锁定 ip
是必要的,不是因为 ip->type
会发生变化——它不会——而是因为直到 ilock
运行,ip->type
不保证已从磁盘加载。)如果调用是 nameiparent
并且这是最后一个路径元素,则循环提前停止,根据 nameiparent
的定义;最后一个路径元素已经复制到 name
中,所以 namex
只需要返回未锁定的 ip
。最后,循环使用 dirlookup
查找路径元素并设置 ip = next
为下一次迭代做准备。当循环用完路径元素时,它返回 ip
。
过程 namex
可能需要很长时间才能完成:它可能涉及多次磁盘操作来读取路径中遍历的目录的 inode 和目录块(如果它们不在缓冲区缓存中)。Xv6 经过精心设计,以便如果一个内核线程的 namex
调用被磁盘 I/O 阻塞,另一个查找不同路径名的内核线程可以并发进行。namex
分别锁定路径中的每个目录,以便在不同目录中的查找可以并行进行。
这种并发性带来了一些挑战。例如,当一个内核线程正在查找一个路径名时,另一个内核线程可能正在通过取消链接一个目录来更改目录树。一个潜在的风险是,查找可能正在搜索一个已被另一个内核线程删除的目录,并且其块已被重用于另一个目录或文件。
Xv6 避免了这种竞争。例如,在 namex
中执行 dirlookup
时,查找线程持有目录的锁,并且 dirlookup
返回一个使用 iget
获得的 inode。iget
增加了 inode 的引用计数。只有在从 dirlookup
接收到 inode 之后,namex
才会释放目录上的锁。现在另一个线程可能会从目录中取消链接该 inode,但 xv6 不会删除该 inode,因为该 inode 的引用计数仍然大于零。
另一个风险是死锁。例如,当查找“.”时,next
指向与 ip
相同的 inode。在释放 ip
上的锁之前锁定 next
将导致死锁。为了避免这种死锁,namex
在获取 next
上的锁之前解锁目录。这里我们再次看到为什么 iget
和 ilock
之间的分离很重要。