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
之间的分离很重要。