# 8.12 代码：路径名

路径名查找涉及一系列对`dirlookup`的调用，每个路径组件调用一个。`Namei`（***kernel/fs.c***:661）计算`path`并返回相应的inode。函数`nameiparent`是一个变体：它在最后一个元素之前停止，返回父目录的inode并将最后一个元素复制到`name`中。两者都调用通用函数`namex`来完成实际工作。

`Namex`（***kernel/fs.c***:626）首先决定路径计算的开始位置。如果路径以斜线开始，则计算从根目录开始；否则，从当前目录开始（***kernel/fs.c***:630-633）。然后，它使用`skipelem`依次考察路径的每个元素（***kernel/fs.c***:635）。循环的每次迭代都必须在当前索引结点`ip`中查找`name`。迭代首先给`ip`上锁并检查它是否是一个目录。如果不是，则查找失败（***kernel/fs.c***:636-640）(锁定`ip`是必要的，不是因为`ip->type`可以被更改，而是因为在`ilock`运行之前，`ip->type`不能保证已从磁盘加载。）如果调用是`nameiparent`，并且这是最后一个路径元素，则根据`nameiparent`的定义，循环会提前停止；最后一个路径元素已经复制到`name`中，因此`namex`只需返回解锁的`ip`（***kernel/fs.c***:641-645）。最后，循环将使用`dirlookup`查找路径元素，并通过设置`ip = next`（***kernel/fs.c***:646-651）为下一次迭代做准备。当循环用完路径元素时，它返回`ip`。

`namex`过程可能需要很长时间才能完成：它可能涉及多个磁盘操作来读取路径名中所遍历目录的索引节点和目录块（如果它们不在buffer cache中）。Xv6经过精心设计，如果一个内核线程对`namex`的调用在磁盘I/O上阻塞，另一个查找不同路径名的内核线程可以同时进行。`Namex`分别锁定路径中的每个目录，以便在不同目录中进行并行查找。

这种并发性带来了一些挑战。例如，当一个内核线程正在查找路径名时，另一个内核线程可能正在通过取消目录链接来更改目录树。一个潜在的风险是，查找可能正在搜索已被另一个内核线程删除且其块已被重新用于另一个目录或文件的目录。

Xv6避免了这种竞争。例如，在`namex`中执行`dirlookup`时，lookup线程持有目录上的锁，`dirlookup`返回使用`iget`获得的inode。`Iget`增加索引节点的引用计数。只有在从`dirlookup`接收inode之后，`namex`才会释放目录上的锁。现在，另一个线程可以从目录中取消inode的链接，但是xv6还不会删除inode，因为inode的引用计数仍然大于零。

另一个风险是死锁。例如，查找“`.`”时，`next`指向与`ip`相同的inode。在释放`ip`上的锁之前锁定`next`将导致死锁。为了避免这种死锁，`namex`在获得下一个目录的锁之前解锁该目录。这里我们再次看到为什么`iget`和`ilock`之间的分离很重要。