xv6, line by line
test yourself

Test yourself · category 18 of 20

Inodes, directories and path lookup

The inode table, ref versus nlink, iget/ilock/iput, directories as arrays of dirents, namex’s one-lock-at-a-time walk, and create, link and unlink.

1warm-upChoose one

A struct inode in memory has both ref and nlink. What does each one count?

kernel/file.h
16// in-memory copy of an inode
17struct inode {
18 uint dev; // Device number
19 uint inum; // Inode number
20 int ref; // Reference count
21 struct sleeplock lock; // protects everything below here
22 int valid; // inode has been read from disk?
24 short type; // copy of disk inode
25 short major;
26 short minor;
27 short nlink;
30};
2warm-upType a number

How many on-disk inodes (dinode) fit in one disk block in this file system (the value of IPB)?

kernel/fs.h
31// On-disk inode structure
32struct dinode {
33 short type; // File type
34 short major; // Major device number (T_DEVICE only)
35 short minor; // Minor device number (T_DEVICE only)
36 short nlink; // Number of links to inode in file system
37 uint size; // Size of file (bytes)
38 uint addrs[NDIRECT + 1]; // Data block addresses
39};
41// Inodes per block.
42#define IPB (BSIZE / sizeof(struct dinode))
44// Block containing inode i
45#define IBLOCK(i, sb) ((i) / IPB + sb.inodestart)
decimal, 0x hex or 0b binary
3warm-upChoose one

What does the content of a directory (its data blocks) consist of in this file system?

kernel/fs.h
53// Directory is a file containing a sequence of dirent structures.
54#define DIRSIZ 14
56// The name field may have DIRSIZ characters and not end in a NUL
57// character.
58struct dirent {
60 char name[DIRSIZ] __attribute__((nonstring));
61};
4warm-upTrue or false, and why

True or false: when rm removes the only name of a file that another process still has open, the file’s data blocks are freed immediately.

Why?

5warm-upChoose one

iget returns an inode table entry with valid = 0 and does not read the inode from disk; ilock does that later. Why not read it in iget?

kernel/fs.c
245// Find the inode with number inum on device dev
246// and return the in-memory copy. Does not lock
247// the inode and does not read it from disk.
248static struct inode *
251 struct inode *ip, *empty;
255 // Is the inode already in the table?
256 empty = 0;
257 for (ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++) {
258 if (ip->ref > 0 && ip->dev == dev && ip->inum == inum) {
259 ip->ref++;
261 return ip;
262 }
263 if (empty == 0 && ip->ref == 0) // Remember empty slot.
265 }
267 // Recycle an inode entry.
268 if (empty == 0)
269 panic("iget: no inodes");
274 ip->ref = 1;
275 ip->valid = 0;
278 return ip;
6warm-upMatch the pairs

Match each function with its job.

7warm-upClick the line

In namex, click the line that gives up the current directory (its lock and its reference) after finding the next element, before the loop locks that element.

kernel/fs.c
691static struct inode *
692namex(char *path, int nameiparent, char *name)
694 struct inode *ip, *next;
696 if (*path == '/')
698 else
701 while ((path = skipelem(path, name)) != 0) {
703 if (ip->type != T_DIR) {
705 return 0;
706 }
707 if (ip->nlink == 0) {
709 return 0;
710 }
711 if (nameiparent && *path == '\0') {
712 // Stop one level early.
714 return ip;
715 }
716 if ((next = dirlookup(ip, name, 0)) == 0) {
718 return 0;
719 }
722 }
725 return 0;
726 }
727 return ip;

Your pick: none yet (click a line in the code)

9solidType a number

In this image the superblock says inodestart = 33. Which disk block holds inode 24?

kernel/fs.h
41// Inodes per block.
42#define IPB (BSIZE / sizeof(struct dinode))
44// Block containing inode i
45#define IBLOCK(i, sb) ((i) / IPB + sb.inodestart)
decimal, 0x hex or 0b binary
10solidChoose one

Why does namex unlock the current directory before it locks the next path element, instead of locking the child first and then letting go of the parent?

kernel/fs.c
691static struct inode *
692namex(char *path, int nameiparent, char *name)
694 struct inode *ip, *next;
696 if (*path == '/')
698 else
701 while ((path = skipelem(path, name)) != 0) {
703 if (ip->type != T_DIR) {
705 return 0;
706 }
707 if (ip->nlink == 0) {
709 return 0;
710 }
711 if (nameiparent && *path == '\0') {
712 // Stop one level early.
714 return ip;
715 }
716 if ((next = dirlookup(ip, name, 0)) == 0) {
718 return 0;
719 }
722 }
725 return 0;
726 }
727 return ip;
11solidPut in order

open("newfile", O_CREATE|O_WRONLY) calls create for a name that does not exist yet. Put the steps of create in order.

kernel/sysfile.c
258static struct inode *
259create(char *path, short type, short major, short minor)
261 struct inode *ip, *dp;
262 char name[DIRSIZ];
264 if ((dp = nameiparent(path, name)) == 0)
265 return 0;
269 if (dp->nlink == 0) {
271 return 0;
272 }
274 // a new directory's ".." would push dp->nlink past its maximum
275 if (type == T_DIR && dp->nlink >= NLINK_MAX) {
277 return 0;
278 }
280 if ((ip = dirlookup(dp, name, 0)) != 0) {
283 if (type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE))
284 return ip;
286 return 0;
287 }
289 if ((ip = ialloc(dp->dev, type)) == 0) {
291 return 0;
292 }
297 ip->nlink = 1;
300 if (type == T_DIR) { // Create . and .. entries.
301 // No ip->nlink++ for ".": avoid cyclic ref count.
302 if (dirlink(ip, ".", ip->inum) < 0 || dirlink(ip, "..", dp->inum) < 0)
303 goto fail;
304 }
306 if (dirlink(dp, name, ip->inum) < 0)
307 goto fail;
309 if (type == T_DIR) {
310 // now that success is guaranteed:
311 dp->nlink++; // for ".."
313 }
317 return ip;
  1. iunlockput(dp) and return the new inode, still locked
  2. ialloc claims a free dinode and gives it type T_FILE
  3. dirlink writes the entry (name, inum) into the parent
  4. dirlookup finds no entry with that name
  5. nameiparent returns the parent directory, referenced but unlocked, and the last name
  6. ilock(dp), then check that dp->nlink is not 0
  7. ilock(ip), set nlink = 1, iupdate
12solidChoose all that apply

Which of these code paths hold two inode sleep-locks at the same time?

13solidType a number

Starting from nothing, a user runs mkdir /d, mkdir /d/x, mkdir /d/y and echo hi > /d/f. What is the nlink of directory d afterwards?

decimal, 0x hex or 0b binary
14solidChoose one

sys_unlink refuses the names . and .. (line 221) before it calls dirlookup and ilock(ip). Apart from keeping the tree intact, what would go wrong with the locks if unlink("d/.") were allowed through?

kernel/sysfile.c
204 struct inode *ip, *dp;
205 struct dirent de;
209 if (argstr(0, path, MAXPATH) < 0)
210 return -1;
213 if ((dp = nameiparent(path, name)) == 0) {
215 return -1;
216 }
220 // Cannot unlink "." or "..".
221 if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0)
222 goto bad;
224 if ((ip = dirlookup(dp, name, &off)) == 0)
225 goto bad;
228 if (ip->nlink < 1)
229 panic("unlink: nlink < 1");
230 if (ip->type == T_DIR && !isdirempty(ip)) {
232 goto bad;
233 }
15solidChoose one

A program creates abcdefghijklmnop (16 characters) and then calls open("abcdefghijklmnXY", O_RDONLY) in the same directory. What happens?

kernel/fs.c
662static char *
663skipelem(char *path, char *name)
665 char *s;
666 int len;
668 while (*path == '/')
670 if (*path == 0)
671 return 0;
673 while (*path != '/' && *path != 0)
675 len = path - s;
676 if (len >= DIRSIZ)
678 else {
680 name[len] = 0;
681 }
682 while (*path == '/')
684 return path;
16solidPut in order

iput is dropping the last reference to an inode whose nlink is 0. Put its steps in order.

kernel/fs.c
349void
350iput(struct inode *ip)
354 // Last reference of an unlinked inode? Capture dev/inum before ref--,
355 // since once ref hits 0, ip may be recycled by a concurrent iget()
356 // for a different inum.
357 int last = (ip->ref == 1 && ip->valid && ip->nlink == 0);
360 if (last) {
361 // ip->ref == 1 means no other process can have ip locked.
365 itrunc(ip); // free the data blocks (type stays nonzero on disk)
366 ip->valid = 0;
371 }
373 ip->ref--;
376 if (last)
377 ifree(dev, inum); // now clear type on disk: inum becomes allocatable
  1. acquire itable.lock; compute last and copy dev and inum into locals
  2. itrunc: free the data blocks, set size 0, iupdate
  3. acquiresleep(&ip->lock) while still holding itable.lock
  4. ifree(dev, inum): write type 0 on disk
  5. release itable.lock
  6. re-acquire itable.lock, ref-- (to 0), release it
  7. valid = 0, then release the sleep-lock
17solidChoose one

fileclose wraps the iput of an inode in begin_op and end_op, even though closing a file usually writes nothing. Why?

kernel/file.c
58// Close file f. (Decrement ref count, close when reaches 0.)
59void
60fileclose(struct file *f)
62 struct file ff;
65 if (f->ref < 1)
66 panic("fileclose");
67 if (--f->ref > 0) {
69 return;
70 }
71 ff = *f;
72 f->ref = 0;
76 if (ff.type == FD_PIPE) {
78 } else if (ff.type == FD_INODE || ff.type == FD_DEVICE) {
82 }
18solidDecode the bits

gdb shows the first 12 bytes of inode 24’s dinode (little-endian: type, major, minor, nlink as 2-byte shorts, then size as a 4-byte uint): 01 00 00 00 00 00 03 00 40 00 00 00. Decode it.

Value: 0100 0000 0000 0300 40000000

19deepChoose all that apply

rm /x removes a regular file /x that has 3 data blocks, nlink 1, and is not open anywhere. Which blocks does this one transaction log (pass to log_write)?

20solidClick the line

In iput, click the line after which ialloc on another hart may hand this inode’s number to a brand-new file.

kernel/fs.c
349void
350iput(struct inode *ip)
354 // Last reference of an unlinked inode? Capture dev/inum before ref--,
355 // since once ref hits 0, ip may be recycled by a concurrent iget()
356 // for a different inum.
357 int last = (ip->ref == 1 && ip->valid && ip->nlink == 0);
360 if (last) {
361 // ip->ref == 1 means no other process can have ip locked.
365 itrunc(ip); // free the data blocks (type stays nonzero on disk)
366 ip->valid = 0;
371 }
373 ip->ref--;
376 if (last)
377 ifree(dev, inum); // now clear type on disk: inum becomes allocatable

Your pick: none yet (click a line in the code)

21deepFill in the machine state

cat calls close(3) on a file that rm already unlinked. Inside fileclose → iput, line 362’s acquiresleep has just returned and line 363 has not run yet. What is the state of the hart running cat?

kernel/fs.c
349void
350iput(struct inode *ip)
354 // Last reference of an unlinked inode? Capture dev/inum before ref--,
355 // since once ref hits 0, ip may be recycled by a concurrent iget()
356 // for a different inum.
357 int last = (ip->ref == 1 && ip->valid && ip->nlink == 0);
360 if (last) {
361 // ip->ref == 1 means no other process can have ip locked.
365 itrunc(ip); // free the data blocks (type stays nonzero on disk)
366 ip->valid = 0;
371 }
373 ip->ref--;
376 if (last)
377 ifree(dev, inum); // now clear type on disk: inum becomes allocatable
22deepChoose one

iput copies ip->dev and ip->inum into the locals dev and inum (line 358), and line 377 calls ifree(dev, inum) rather than ifree(ip->dev, ip->inum). Why?

kernel/fs.c
349void
350iput(struct inode *ip)
354 // Last reference of an unlinked inode? Capture dev/inum before ref--,
355 // since once ref hits 0, ip may be recycled by a concurrent iget()
356 // for a different inum.
357 int last = (ip->ref == 1 && ip->valid && ip->nlink == 0);
360 if (last) {
361 // ip->ref == 1 means no other process can have ip locked.
365 itrunc(ip); // free the data blocks (type stays nonzero on disk)
366 ip->valid = 0;
371 }
373 ip->ref--;
376 if (last)
377 ifree(dev, inum); // now clear type on disk: inum becomes allocatable
23deepChoose one

On a fresh disk a user runs mkdir /a, mkdir /a/b, cd /a/b, /rm /a/b, /rm /a, and then /ls ... What happens in this kernel, and which check is responsible?

kernel/fs.c
691static struct inode *
692namex(char *path, int nameiparent, char *name)
694 struct inode *ip, *next;
696 if (*path == '/')
698 else
701 while ((path = skipelem(path, name)) != 0) {
703 if (ip->type != T_DIR) {
705 return 0;
706 }
707 if (ip->nlink == 0) {
709 return 0;
710 }
711 if (nameiparent && *path == '\0') {
712 // Stop one level early.
714 return ip;
715 }
716 if ((next = dirlookup(ip, name, 0)) == 0) {
718 return 0;
719 }
722 }
725 return 0;
726 }
727 return ip;
24deepChoose all that apply

Two processes on two harts call open("same", O_CREATE|O_WRONLY) in / at nearly the same moment; same does not exist yet. Which statements are true afterwards?

kernel/sysfile.c
258static struct inode *
259create(char *path, short type, short major, short minor)
261 struct inode *ip, *dp;
262 char name[DIRSIZ];
264 if ((dp = nameiparent(path, name)) == 0)
265 return 0;
269 if (dp->nlink == 0) {
271 return 0;
272 }
274 // a new directory's ".." would push dp->nlink past its maximum
275 if (type == T_DIR && dp->nlink >= NLINK_MAX) {
277 return 0;
278 }
280 if ((ip = dirlookup(dp, name, 0)) != 0) {
283 if (type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE))
284 return ip;
286 return 0;
287 }
289 if ((ip = ialloc(dp->dev, type)) == 0) {
291 return 0;
292 }
25deepChoose one

sys_link increments the file’s nlink, then unlocks the file (line 153) before calling nameiparent and locking the target directory. Why not keep the file locked until the new entry is written?

kernel/sysfile.c
122// Create the path new as a link to the same inode as old.
127 struct inode *dp, *ip;
129 if (argstr(0, old, MAXPATH) < 0 || argstr(1, new, MAXPATH) < 0)
130 return -1;
133 if ((ip = namei(old)) == 0) {
135 return -1;
136 }
139 if (ip->type == T_DIR) {
142 return -1;
143 }
145 if (ip->nlink >= NLINK_MAX) {
148 return -1;
149 }
155 if ((dp = nameiparent(new, name)) == 0)
156 goto bad;
158 // dp may have been unlinked while we resolved it; linking into an
159 // orphaned directory leaks ip (itrunc discards the record without
160 // dropping ip->nlink). create() has the same guard.
161 if (dp->nlink == 0) {
163 goto bad;
164 }
165 if (dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0) {
167 goto bad;
168 }