8.12 Code: Path names
Path name lookup involves a succession of calls to
dirlookup
,
one for each path component.
namei
(kernel/fs.c:687)
evaluates
path
and returns the corresponding
inode
.
The function
nameiparent
is a variant: it stops before the last element, returning the
inode of the parent directory and copying the final element into
name
.
Both call the generalized function
namex
to do the real work.
namex
(kernel/fs.c:652)
starts by deciding where the path evaluation begins.
If the path begins with a slash, evaluation begins at the root;
otherwise, the current directory
(kernel/fs.c:656-659).
Then it uses
skipelem
to consider each element of the path in turn
(kernel/fs.c:661).
Each iteration of the loop must look up
name
in the current inode
ip
.
The iteration begins by locking
ip
and checking that it is a directory.
If not, the lookup fails
(kernel/fs.c:662-666).
(Locking
ip
is necessary not because
ip->type
can change underfoot—it can’t—but because
until
ilock
runs,
ip->type
is not guaranteed to have been loaded from disk.)
If the call is
nameiparent
and this is the last path element, the loop stops early,
as per the definition of
nameiparent
;
the final path element has already been copied
into
name
,
so
namex
need only
return the unlocked
ip
(kernel/fs.c:667-671).
Finally, the loop looks for the path element using
dirlookup
and prepares for the next iteration by setting
ip = next
(kernel/fs.c:672-677).
When the loop runs out of path elements, it returns
ip
.
The procedure
namex
may take a long time to complete: it could involve several disk operations to
read inodes and directory blocks for the directories traversed in the pathname
(if they are not in the buffer cache). Xv6 is carefully designed so that if an
invocation of
namex
by one kernel thread is blocked on a disk I/O, another kernel thread looking up
a different pathname can proceed concurrently.
namex
locks each directory in the path separately so that lookups in different
directories can proceed in parallel.
This concurrency introduces some challenges. For example, while one kernel thread is looking up a pathname another kernel thread may be changing the directory tree by unlinking a directory. A potential risk is that a lookup may be searching a directory that has been deleted by another kernel thread and its blocks have been re-used for another directory or file.
Xv6 avoids such races. For example, when executing
dirlookup
in
namex
,
the lookup thread holds the lock on the directory and
dirlookup
returns an inode that was obtained using
iget
.
iget
increases the reference count of the inode. Only after receiving the
inode from
dirlookup
does
namex
release the lock on the directory. Now another thread may unlink the inode from
the directory but xv6 will not delete the inode yet, because the reference count
of the inode is still larger than zero.
Another risk is deadlock. For example,
next
points to the same inode as
ip
when looking up ".".
Locking
next
before releasing the lock on
ip
would result in a deadlock.
To avoid this deadlock,
namex
unlocks the directory before obtaining a lock on
next
.
Here again we see why the separation between
iget
and
ilock
is important.