Draft mount/umount lab as an alternative lab for fs.html
fs.html should perhaps be split in small homeworks as in previous years in preparation for lectures and/or as demos during lecture.
This commit is contained in:
parent
e398a9815c
commit
5498ee2e92
198
labs/fs1.html
Normal file
198
labs/fs1.html
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
<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, 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. Make sure your kernel can pass test1 of mounttest.
|
||||||
|
|
||||||
|
<p>Test2 of mounttest stresses <namex> more; if you have done
|
||||||
|
everything right above, your kernel may be able to pass it.
|
||||||
|
|
||||||
|
</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.
|
Loading…
Reference in a new issue