xv6-65oo2/labs/fs1.html
Frans Kaashoek a9953236cc x
2019-08-17 12:52:25 -04:00

216 lines
9.6 KiB
HTML

<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.