Lab: mount/umount

In this lab you will add support for mounting/unmounting of file systems to xv6. This lab will expose you to many parts of the xv6 file system, including pathname lookup, inodes, logging/recovery, and concurrency.

Your job is modify xv6 so that your modified kernel passes mounttest, crashtest, and usertests. You will have to implement two system calls: mount(char *source, char *target) and umount(char *target). Mount attaches the device referenced by source (e.g., /disk1) at the location specified by target. For example, mount("/disk1", "/m") will attach disk1 at the directory /m. After this mount call, users can use pathnames such as /m/README to read the file README stored in the root directory on disk1. Umount removes the attachment. For example, umount("/m") unmounts disk1 from /m.

Start a new branch for this lab, forking from your last branch.

  $ git checkout -b mount

There are several major challenges in implementing the mount/umount system calls:

The rest of this assignment provides some hints how you might go about the above challenges.

Adding system calls

We provided the user-level support for mount and umount calls. Run mounttest and it will fail on the first call to mount:

$ mounttest
test0 start
3 mounttest: unknown sys call 24
mount failed
Your job is to implement the kernel part of the system calls. Start with mount first.

To mount a second disk, you need to have a second disk. We have done most of the work for you by generalizing kernel/virtio_disk.c and kernel/log.c to support an array of two disks and an array of two logs, respectively. Modify the file Makefile to tell qemu to provide a second disk: add $(QEMUEXTRA) to the end of QEMUOPTS. Create a second disk image fs1.img by copying fs.img. You might want to add rules to the Makefile to make this image and remove it on make clean.

Now you have a second disk, call in virtio_disk_init and fsinit in your implementation of mount. (Hint: you can get the minor number of the disk from the inode for the first argument of mount.)

Pathname lookup

Modify namex to traverse mount points: when namex sees an inode to which a file system is attached, it should traverse to the root inode of that file system. Hint: modify the in-memory inode in file.h to keep some additional state, and initialize that state in the mount system call. Note that the inode already has a field for disk number (i.e., dev), which is initialized and passed to reads and writes to the driver. dev corresponds to the minor number for disk devices.

Your modified xv6 should be able to pass the first tests in mounttest (i.e., stat). This is likely to be challenging, however, because now your kernel will be reading from the second disk for the first time, and you may run into many issues.

Even though stat may return correctly, your code is likely to be incorrect, because in namex iunlockput may modify the second disk (e.g., if another process removes the file or directory) and those modifications must be written to the second disk. Your job is to fix the calls to begin_op and end_op to take the right device. One challenge is that begin_op is called at the beginning of a system call but then you don't know the device that will be involved; you will have to postpone this call until you know which inode is involved (which tells you will which device is involved). Another challenge is that you cannot postpone calling begin_op passed ilock because that violates lock ordering in xv6; you should not be calling begin_op while holding locks on inodes. (The log system allows a few system calls to run; if a system call that holds an inode lock isn't admitted and one of the admitted system calls needs that inode to complete, then xv6 will deadlock.)

Once you have implemented a plan for begin_op and end_op, see if your kernel can pass test0. It is likely that you will have to modify your implementation of the mount system call to handle several corner cases. See the tests in test0.

Run usertests to see if you didn't break anything else. Since you modified namex and begin/end_op, which are at the core of the xv6 file system, you might have introduced bugs, perhaps including deadlocks. Deadlocks manifest themselves as no output being produced because all processes are sleeping (hit ctrl-p a few times). Your kernel might also suffer kernel panics, because your changes violate invariants. You may have to iterate a few times to get a good design and implementation.

umount

Once your kernel passes usertests and test0 of mounttest, implement umount. The main challenge is that umount of a file system should fail if the file system is still in use; that is, if there is an inode on the mounted device that has a ref > 0. Furthermore, this test and unmounting should be an atomic operation. (Hint: lock the inode cache.) Make sure your kernel passes test1 of mounttest.

Test2 of mounttest stresses namex more; if you have done everything right above, your kernel should pass it. Test3 tests concurrent mount/unmounts with file creation.

crash safety

One of the main goals of the file system is to provide crash safety: if there is a power failure during a file system operation, xv6 should recover correctly. It is difficult to introduce power failure at the critical steps of logging; instead, we added a system call that causes a kernel panic after committing an operation but before installing the operation. Test4 with crashtest tests if your xv6 recovers the mounted disk correctly.

This completes the lab. Commit your changes and type make handin in the lab directory to hand in your lab.

Optional challenges

Modify xv6 so that init mounts the first disk on the root inode. This will allow you to remove some code specific for the first disk from the kernel.

Support mounts on top of mounts.