<html> <head> <title>Lab: mount/umount</title> <link rel="stylesheet" href="homework.css" type="text/css" /> </head> <body> <h1>Lab: mount/umount</h1> <p>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, disk driver, concurrency, etc. <p>Your job is modify xv6 so that your modified kernel passes the tests in mounttest. You will have to implement two system calls: <tt>mount(char *source, char *target)</tt> and <tt>umount(char *target)</tt>. Mount attaches the device referenced by <tt>source</tt> (e.g., <tt>/disk1</tt>) at the location specified by <tt>target</tt>. For example, <tt>mount("/disk1", "/m")</tt> will attach <tt>disk1</tt> at the directory <tt>/m</tt>. After this mount call, users can use pathnames such as <tt>/m/README</tt> to read the file <tt>README</tt> stored in the root directory on <tt>disk1</tt>. <tt>Umount</tt> removes the attachment. For example, <tt>umount("/m")</tt> unmounts disk1 from <tt>/m</tt>. <p>There are several major challenges in implementing the mount system calls: <ul> <li>Adding the actual system calls so that user programs can call them. This is similar to previous labs in which you added systems calls xv6. <li>Supporting several disks. You will have generalize to virtio_disk.c to support at least two disks. <li>Logging file system modifications to the right disk. xv6 assumes there is only disk and file system calls typically start with <tt>begin_op</tt> and end with <tt>end_op</tt>, logging all modifications between these two calls to the log on the one disk. With mount, modifications to the file system on the second disk must be logged to the second disk. <li>Modifying pathname lookup (<tt>namex</tt>) so that when a lookup cross a mount point, it continues at the root inode of the attached disk. </ul> <p>The rest of this assignment provides some hints how you might go about the above challenges. <h2>Adding system calls</h2> <p>Add the stubs for the two systems calls to xv6 so that you can compile mounttest and add two empty functions for the two system calls to sysfile.c. Run mounttest and it will fail on the first call to <tt>mount</tt>. <h2>Adding a second disk</h2> <p>To be able to mount another disk, you need to extend xv6 to support at least two disks. Modify virtio_disk.c to support an array of two disks instead of a single disk. The address of the second disk is <tt>0x10002000</tt>; modify the macro <tt>R</tt> to take a disk number (0, 1,..) and read/write to the memory address for that disk. <p>All functions in <tt>virtio_disk.c</tt> need to take the disk number as an argument to update the state of the disk that is read/written to or to receive an interrupt from the disk. Modify <tt>virtio_disk_init</tt> to take a disk number as an argument and update is to that it initializes that disk. Similar, go through the other functions; make these changes should be most mechanical (i.e., text substitutions). <p>The second disk interrupts at IRQ 2; modify trap.c to receive that interrupt and <tt>virtio_disk_intr</tt> with the number of the disk that generated the interrupt. <p>Modify the file Makefile to tell qemu to provide a second disk. Define the variable <tt>QEMUEXTRA = -drive file=fs1.img,if=none,format=raw,id=x1 -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1</tt> and add <tt>$(QEMUEXTRA)</tt> to the end of <tt>QEMUOPTS</tt>. <p>Create a second disk image <tt>fs1.img</tt>. Easiest thing to do is just copy the file <tt>fs.img</tt>. You might want to add rules to the Makefile to make this image and remove it on <tt>make clean</tt>. <p>Add to the user program init a call to create a device for the new disk. For example, add the line <tt>mknod("disk1", DISK, 1);</tt> to init.c. This will create an inode of type device in the root directory with major number <tt>DISK</tt> and minor number 1. <p>The first argument of the <tt>mount</tt> system call ("disk1") will refer to the device you created using <tt>mknod</tt> above. In your implementation of the mount system call, call <tt>virtio_disk_init</tt> with the minor number as the argument to initialize the second disk. (We reserve minor number 0 for the first disk.) <p>Boot xv6, run mounttest, and make sure <tt>virtio_disk_init</tt> gets called (e.g., add print statement). You won't know if your changes are correct, but your code should compile and invoke the driver for the second disk. <h2>Modify the logging system</h2> <p>After calling <tt>virtio_disk_init</tt>, you need to also call <tt>loginit</tt> to initialize the logging system for the second disk (and restore the second disk if a power failure happened while modifying the second disk). Generalize the logging system to support to two logs, one on disk 0 and one disk 1. These changes are mostly mechanical (e.g., <tt>log.</tt> changes to <tt>log[n].</tt>), similar to generalizing the disk driver to support two disks. <p>To make xv6 compile, you need to provide a disk number to <tt>begin_op</tt> and <tt>end_op</tt>. It will be a challenge to figure out what the right value is; for now just specify the first disk (i.e., 0). This isn't correct, since modifications to the second disk should be logged on the second disk, but we have no way yet to read/write the second disk. Come back to this later when you have a better idea how things will fit together, but make sure that xv6 compiles and still runs. <h2>Pathname lookup</h2> <p>Modify <tt>namex</tt> to traverse mount points: when <tt>namex</tt> 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., <tt>dev</tt>), which is initialized and passed to reads and writes to the driver. <tt>dev</tt> corresponds to the minor number for disk devices. <p>Your modified xv6 should be able to pass the first tests in mounttest (i.e., <tt>stat</tt>). 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. <p>Even though <tt>stat</tt> may return correctly, your code is likely to be incorrect, because in <tt>namex</tt> because <tt>iunlockput</tt> 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 <tt>begin_op</tt> and <tt>end_op</tt> to take the right device. One challenge is that <tt>begin_op</tt> 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 <tt>begin_op</tt> passed <tt>ilock</tt> because that violates lock ordering in xv6; you should not be calling <tt>begin_op</tt> while holding locks on inodes. (The log system allows a few systems 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.) <p>Once you have implemented a plan for <tt>begin_op</tt> and <tt>end_op</tt>, see if your kernel can pass <tt>test0</tt>. 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 <tt>test0</tt>. <p>Run usertests to see if you didn't break anything else. Since you modified <tt>namex</tt> and <tt>begin/end_op</tt>, 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. <h2>umount</h2> <p>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 <tt>ref > 0</tt>. Furthermore, this test and unmounting should be an atomic operation. (Hint: lock the inode cache.) Make sure your kernel passes test1 of mounttest. <p>Test2 of mounttest stresses <tt>namex</tt> more; if you have done everything right above, your kernel should pass it. Test3 tests concurrent mount/unmounts with file creation. <h2>crash safety</h2> <p>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. </body> </html> <h2>Optional challenges</h2> <p>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. <p>Support mounts on top of mounts.