summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrans Kaashoek <[email protected]>2019-08-20 20:23:18 -0400
committerFrans Kaashoek <[email protected]>2019-08-20 20:23:18 -0400
commit7241838b4cecefb32bad4698e748fc31d008d94d (patch)
tree7b52f0c55604ee8801d2a6edfe46c4d931e35273
parentc612d452fdb03080c77517f6c1b32b8d11c6c40a (diff)
downloadxv6-labs-7241838b4cecefb32bad4698e748fc31d008d94d.tar.gz
xv6-labs-7241838b4cecefb32bad4698e748fc31d008d94d.tar.bz2
xv6-labs-7241838b4cecefb32bad4698e748fc31d008d94d.zip
Move labs into 6.828 repo. The lab text isn't dependent on specific
xv6 code. Lab submission instructions etc. are likely going to be more MIT 6.828 specific.
-rw-r--r--labs/cow.html109
-rw-r--r--labs/fs.html360
-rw-r--r--labs/fs1.html215
-rw-r--r--labs/lazy.html132
-rw-r--r--labs/lock.html148
-rw-r--r--labs/mmap.html171
-rw-r--r--labs/syscall.html443
-rw-r--r--labs/xv6.html238
8 files changed, 0 insertions, 1816 deletions
diff --git a/labs/cow.html b/labs/cow.html
deleted file mode 100644
index 2cc18fa..0000000
--- a/labs/cow.html
+++ /dev/null
@@ -1,109 +0,0 @@
-<html>
-<head>
-<title>Lab: Copy-on-Write Fork for xv6</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: Copy-on-Write Fork for xv6</h2>
-
-<p>
-Your task is implement copy-on-write fork in the xv6 kernel. You are
-done if your modified kernel executes both the cow and usertests
-programs successfully.
-
-<h2>The problem</h2>
-
-The fork() system call in xv6 copies all of the parent process's
-user-space memory into the child. If the parent is large, copying can
-take a long time. In addition, the copies often waste memory; in many
-cases neither the parent nor the child modifies a page, so that in
-principle they could share the same physical memory. The inefficiency
-is particularly clear if the child calls exec(), since then most of
-the copied pages are thrown away without ever being used. Of course,
-sometimes both child and parent modify memory at the same virtual
-address after a fork(), so for some pages the copying is truly needed.
-
-<h2>The solution</h2>
-
-The goal of copy-on-write (COW) fork() is to defer allocating and
-copying physical memory pages for the child until they are actually
-needed, in the hope that they may never be needed.
-
-<p>
-COW fork() creates just a pagetable for the child, with PTEs for user
-memory pointing to the parent's physical pages. COW fork() marks all
-the user PTEs in both parent and child as read-only. When either
-process tries to write one of these COW pages, the CPU will force a
-page fault. The kernel page-fault handler detects this case, allocates
-a page of physical memory for the faulting process, copies the
-original page into the new page, and modifies the relevant PTE in the
-faulting process to refer to the new page, this time with the PTE
-marked writeable. When the page fault handler returns, the user
-process will be able to write its copy of the page.
-
-<p>
-COW fork() makes freeing of the physical pages that implement user
-memory a little trickier. A given physical page may be referred to by
-multiple processes' page tables, and should be freed when the last
-reference disappears.
-
-<h2>The cow test program</h2>
-
-To help you test your implementation, we've provided an xv6 program
-called cow (source in user/cow.c). cow runs various tests, but
-even the first will fail on unmodified xv6. Thus, initially, you
-will see:
-
-<pre>
-$ cow
-simple: fork() failed
-$
-</pre>
-
-The "simple" test allocates more than half of available physical
-memory, and then fork()s. The fork fails because there is not enough
-free physical memory to give the child a complete copy of the parent.
-
-<p>
-When you are done, your kernel should be able to run both cow and
-usertests. That is:
-
-<pre>
-$ cow
-simple: ok
-simple: ok
-three: zombie!
-ok
-three: zombie!
-ok
-three: zombie!
-ok
-file: ok
-ALL COW TESTS PASSED
-$ usertests
-...
-ALL TESTS PASSED
-$
-</pre>
-
-<h2>Hints</h2>
-
-Here's one reasonable plan of attack. Modify uvmcopy() to map the
-parent's physical pages into the child, instead of allocating new
-pages, and clear PTE_W in the PTEs of both child and parent.
-Modify usertrap() to recognize a page fault. When a page fault occurs
-on a COW page, allocate a new page with kalloc(), copy the old page to
-the new page, and install the new page in the PTE with PTE_W set.
-Next, ensure that each physical page is freed when the last PTE
-reference to it goes away (but not before!), perhaps by implementing
-reference counts in kalloc.c. Finally, modify copyout() to use the
-same scheme as page faults when it encounters a COW page.
-
-<p>
-It may be useful to have a way to record, for each PTE, whether it is
-a COW mapping. You can use the RSW (reserved for software) bits in
-the RISC-V PTE for this.
-
-</body>
-</html>
diff --git a/labs/fs.html b/labs/fs.html
deleted file mode 100644
index a21e61f..0000000
--- a/labs/fs.html
+++ /dev/null
@@ -1,360 +0,0 @@
-<html>
-<head>
-<title>Lab: file system</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: file system</h1>
-
-<p>In this lab you will add large files and <tt>mmap</tt> to the xv6 file system.
-
-<h2>Large files</h2>
-
-<p>In this assignment you'll increase the maximum size of an xv6
-file. Currently xv6 files are limited to 268 blocks, or 268*BSIZE
-bytes (BSIZE is 1024 in xv6). This limit comes from the fact that an
-xv6 inode contains 12 "direct" block numbers and one "singly-indirect"
-block number, which refers to a block that holds up to 256 more block
-numbers, for a total of 12+256=268. You'll change the xv6 file system
-code to support a "doubly-indirect" block in each inode, containing
-256 addresses of singly-indirect blocks, each of which can contain up
-to 256 addresses of data blocks. The result will be that a file will
-be able to consist of up to 256*256+256+11 blocks (11 instead of 12,
-because we will sacrifice one of the direct block numbers for the
-double-indirect block).
-
-<h3>Preliminaries</h3>
-
-<p>Modify your Makefile's <tt>CPUS</tt> definition so that it reads:
-<pre>
-CPUS := 1
-</pre>
-
-<b>XXX doesn't seem to speedup things</b>
-<p>Add
-<pre>
-QEMUEXTRA = -snapshot
-</pre>
-right before
-<tt>QEMUOPTS</tt>
-<p>
-The above two steps speed up qemu tremendously when xv6
-creates large files.
-
-<p><tt>mkfs</tt> initializes the file system to have fewer
-than 1000 free data blocks, too few to show off the changes
-you'll make. Modify <tt>param.h</tt> to
-set <tt>FSSIZE</tt> to:
-<pre>
- #define FSSIZE 20000 // size of file system in blocks
-</pre>
-
-<p>Download <a href="big.c">big.c</a> into your xv6 directory,
-add it to the UPROGS list, start up xv6, and run <tt>big</tt>.
-It creates as big a file as xv6 will let
-it, and reports the resulting size. It should say 140 sectors.
-
-<h3>What to Look At</h3>
-
-The format of an on-disk inode is defined by <tt>struct dinode</tt>
-in <tt>fs.h</tt>. You're particularly interested in <tt>NDIRECT</tt>,
-<tt>NINDIRECT</tt>, <tt>MAXFILE</tt>, and the <tt>addrs[]</tt> element
-of <tt>struct dinode</tt>. Look Figure 7.3 in the xv6 text for a
-diagram of the standard xv6 inode.
-
-<p>
-The code that finds a file's data on disk is in <tt>bmap()</tt>
-in <tt>fs.c</tt>. Have a look at it and make sure you understand
-what it's doing. <tt>bmap()</tt> is called both when reading and
-writing a file. When writing, <tt>bmap()</tt> allocates new
-blocks as needed to hold file content, as well as allocating
-an indirect block if needed to hold block addresses.
-
-<p>
-<tt>bmap()</tt> deals with two kinds of block numbers. The <tt>bn</tt>
-argument is a "logical block" -- a block number relative to the start
-of the file. The block numbers in <tt>ip->addrs[]</tt>, and the
-argument to <tt>bread()</tt>, are disk block numbers.
-You can view <tt>bmap()</tt> as mapping a file's logical
-block numbers into disk block numbers.
-
-<h3>Your Job</h3>
-
-Modify <tt>bmap()</tt> so that it implements a doubly-indirect
-block, in addition to direct blocks and a singly-indirect block.
-You'll have to have only 11 direct blocks, rather than 12,
-to make room for your new doubly-indirect block; you're
-not allowed to change the size of an on-disk inode.
-The first 11 elements of <tt>ip->addrs[]</tt> should be
-direct blocks; the 12th should be a singly-indirect block
-(just like the current one); the 13th should be your new
-doubly-indirect block.
-
-<p>
-You don't have to modify xv6 to handle deletion of files with
-doubly-indirect blocks.
-
-<p>
-If all goes well, <tt>big</tt> will now report that it
-can write sectors. It will take <tt>big</tt> minutes
-to finish.
-
-<b>XXX this runs for a while!</b>
-
-<h3>Hints</h3>
-
-<p>
-Make sure you understand <tt>bmap()</tt>. Write out a diagram of the
-relationships between <tt>ip->addrs[]</tt>, the indirect block, the
-doubly-indirect block and the singly-indirect blocks it points to, and
-data blocks. Make sure you understand why adding a doubly-indirect
-block increases the maximum file size by 256*256 blocks (really -1),
-since you have to decrease the number of direct blocks by one).
-
-<p>
-Think about how you'll index the doubly-indirect block, and
-the indirect blocks it points to, with the logical block
-number.
-
-<p>If you change the definition of <tt>NDIRECT</tt>, you'll
-probably have to change the size of <tt>addrs[]</tt>
-in <tt>struct inode</tt> in <tt>file.h</tt>. Make sure that
-<tt>struct inode</tt> and <tt>struct dinode</tt> have the
-same number of elements in their <tt>addrs[]</tt> arrays.
-
-<p>If you change the definition of <tt>NDIRECT</tt>, make sure to create a
-new <tt>fs.img</tt>, since <tt>mkfs</tt> uses <tt>NDIRECT</tt> too to build the
-initial file systems. If you delete <tt>fs.img</tt>, <tt>make</tt> on Unix (not
-xv6) will build a new one for you.
-
-<p>If your file system gets into a bad state, perhaps by crashing,
-delete <tt>fs.img</tt> (do this from Unix, not xv6). <tt>make</tt> will build a
-new clean file system image for you.
-
-<p>Don't forget to <tt>brelse()</tt> each block that you
-<tt>bread()</tt>.
-
-<p>You should allocate indirect blocks and doubly-indirect
- blocks only as needed, like the original <tt>bmap()</tt>.
-
-<p>Optional challenge: support triple-indirect blocks.
-
-<h2>Writing with a Log</h2>
-
-Insert a print statement in bwrite (in bio.c) so that you get a
-print every time a block is written to disk:
-
-<pre>
- printf("bwrite block %d\n", b->blockno);
-</pre>
-
-Build and boot a new kernel and run this:
-<pre>
- $ rm README
-</pre>
-
-<p>You should see a sequence of bwrite prints after the <tt>rm</tt>.</p>
-
-<div class="question">
-<ol>
-<li>Annotate the bwrite lines with the kind of information that is
-being written to the disk (e.g., "README's inode", "allocation
-bitmap"). If the log is being written, note both that the log is being
-written and also what kind of information is being written to the log.
-<li>Mark with an arrow the first point at which, if a
-crash occured, README would be missing after a reboot
-(after the call to <tt>recover_from_log()</tt>).
-</ol>
-</p>
-</div>
-
-
-<h2>Crash safety</h2>
-
-<p>This assignment explores the xv6 log in two parts.
-First, you'll artificially create a crash which illustrates
-why logging is needed. Second, you'll remove one
-inefficiency in the xv6 logging system.
-
-<p>
-Submit your solution before the beginning of the next lecture
-to <a href="https://6828.scripts.mit.edu/2018/handin.py/">the submission
-web site</a>.
-
-<h3>Creating a Problem</h3>
-
-<p>
-The point of the xv6 log is to cause all the disk updates of a
-filesystem operation to be atomic with respect to crashes.
-For example, file creation involves both adding a new entry
-to a directory and marking the new file's inode as in-use.
-A crash that happened after one but before the other would
-leave the file system in an incorrect state after a reboot,
-if there were no log.
-
-<p>
-The following steps will break the logging code in a way that
-leaves a file partially created.
-
-<p>
-First, replace <tt>commit()</tt> in <tt>log.c</tt> with
-this code:
-<pre>
-#include "kernel/proc.h"
-void
-commit(void)
-{
- int pid = myproc()->pid;
- if (log.lh.n > 0) {
- write_log();
- write_head();
- if(pid > 1) // AAA
- log.lh.block[0] = 0; // BBB
- install_trans();
- if(pid > 1) // AAA
- panic("commit mimicking crash"); // CCC
- log.lh.n = 0;
- write_head();
- }
-}
-</pre>
-
-<p>
-The BBB line causes the first block in the log to be written to
-block zero, rather than wherever it should be written. During file
-creation, the first block in the log is the new file's inode updated
-to have non-zero <tt>type</tt>.
-Line BBB causes the block
-with the updated inode to be written to block 0 (whence
-it will never be read), leaving the on-disk inode still marked
-unallocated. The CCC line forces a crash.
-The AAA lines suppress this buggy behavior for <tt>init</tt>,
-which creates files before the shell starts.
-
-<p>
-Second, replace <tt>recover_from_log()</tt> in <tt>log.c</tt>
-with this code:
-<pre>
-static void
-recover_from_log(void)
-{
- read_head();
- printf("recovery: n=%d but ignoring\n", log.lh.n);
- // install_trans();
- log.lh.n = 0;
- // write_head();
-}
-</pre>
-
-<p>
-This modification suppresses log recovery (which would repair
-the damage caused by your change to <tt>commit()</tt>).
-
-<p>
-Finally, remove the <tt>-snapshot</tt> option from the definition
-of <tt>QEMUEXTRA</tt> in your Makefile so that the disk image will see the
-changes.
-
-<p>
-Now remove <tt>fs.img</tt> and run xv6:
-<pre>
- % rm fs.img ; make qemu
-</pre>
-<p>
-Tell the xv6 shell to create a file:
-<pre>
- $ echo hi > a
-</pre>
-
-<p>
-You should see the panic from <tt>commit()</tt>. So far
-it is as if a crash occurred in a non-logging system in the middle
-of creating a file.
-
-<p>
-Now re-start xv6, keeping the same <tt>fs.img</tt>:
-<pre>
- % make qemu
-</pre>
-
-<p>
-And look at file <tt>a</tt>:
-<pre>
- $ cat a
-</pre>
-
-<p>
- You should see <tt>panic: ilock: no type</tt>. Make sure you understand what happened.
-Which of the file creation's modifications were written to the disk
-before the crash, and which were not?
-
-<h3>Solving the Problem</h3>
-
-Now fix <tt>recover_from_log()</tt>:
-<pre>
-static void
-recover_from_log(void)
-{
- read_head();
- cprintf("recovery: n=%d\n", log.lh.n);
- install_trans();
- log.lh.n = 0;
- write_head();
-}
-</pre>
-
-<p>
-Run xv6 (keeping the same <tt>fs.img</tt>) and read <tt>a</tt> again:
-<pre>
- $ cat a
-</pre>
-
-<p>
-This time there should be no crash. Make sure you understand why
-the file system now works.
-
-<p>
-Why was the file empty, even though you created
-it with <tt>echo&nbsp;hi&nbsp;>&nbsp;a</tt>?
-
-<p>
-Now remove your modifications to <tt>commit()</tt>
-(the if's and the AAA and BBB lines), so that logging works again,
-and remove <tt>fs.img</tt>.
-
-<h3>Streamlining Commit</h3>
-
-<p>
-Suppose the file system code wants to update an inode in block 33.
-The file system code will call <tt>bp=bread(block 33)</tt> and update the
-buffer data. <tt>write_log()</tt> in <tt>commit()</tt>
-will copy the data to a block in the log on disk, for example block 3.
-A bit later in <tt>commit</tt>, <tt>install_trans()</tt> reads
-block 3 from the log (containing block 33), copies its contents into the in-memory
-buffer for block 33, and then writes that buffer to block 33 on the disk.
-
-<p>
-However, in <tt>install_trans()</tt>, it turns out that the modified
-block 33 is guaranteed to be still in the buffer cache, where the
-file system code left it. Make sure you understand why it would be a
-mistake for the buffer cache to evict block 33 from the buffer cache
-before the commit.
-
-<p>
-Since the modified block 33 is guaranteed to already be in the buffer
-cache, there's no need for <tt>install_trans()</tt> to read block
-33 from the log. Your job: modify <tt>log.c</tt> so that, when
-<tt>install_trans()</tt> is called from <tt>commit()</tt>,
-<tt>install_trans()</tt> does not perform the needless read from the log.
-
-<p>To test your changes, create a file in xv6, restart, and make sure
-the file is still there.
-
-<b>XXX Does this speedup bigfile?</b>
-
-<b>XXX Maybe support lseek and modify shell to append to a file?</b>
-
-
-</body>
-</html>
diff --git a/labs/fs1.html b/labs/fs1.html
deleted file mode 100644
index 45d3e0c..0000000
--- a/labs/fs1.html
+++ /dev/null
@@ -1,215 +0,0 @@
-<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.
diff --git a/labs/lazy.html b/labs/lazy.html
deleted file mode 100644
index 9d97cab..0000000
--- a/labs/lazy.html
+++ /dev/null
@@ -1,132 +0,0 @@
-<html>
-<head>
-<title>Lab: xv6 lazy page allocation</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: xv6 lazy page allocation</h1>
-
-<p>
-One of the many neat tricks an O/S can play with page table hardware
-is lazy allocation of heap memory. Xv6 applications ask the kernel for
-heap memory using the sbrk() system call. In the kernel we've given
-you, sbrk() allocates physical memory and maps it into the process's
-virtual address space. There are programs that allocate memory but
-never use it, for example to implement large sparse arrays.
-Sophisticated kernels delay allocation of each page of memory until
-the application tries to use that page -- as signaled by a page fault.
-You'll add this lazy allocation feature to xv6 in this lab.
-
-<h2>Part One: Eliminate allocation from sbrk()</h2>
-
-Your first task is to delete page allocation from the sbrk(n) system
-call implementation, which is the function sys_sbrk() in sysproc.c. The
-sbrk(n) system call grows the process's memory size by n bytes, and
-then returns the start of the newly allocated region (i.e., the old
-size). Your new sbrk(n) should just increment the process's size
-(myproc()->sz) by n and return the old size. It should not allocate memory
--- so you should delete the call to growproc() (but you still need to
-increase the process's size!).
-
-<p>
-Try to guess what the result of this modification will be: what will
-break?
-
-<p>
-Make this modification, boot xv6, and type <tt>echo hi</tt> to the shell.
-You should see something like this:
-
-<pre>
-init: starting sh
-$ echo hi
-usertrap(): unexpected scause 0x000000000000000f pid=3
- sepc=0x00000000000011dc stval=0x0000000000004008
-va=0x0000000000004000 pte=0x0000000000000000
-panic: unmappages: not mapped
-</pre>
-
-The "usertrap(): ..." message is from the user trap handler in trap.c;
-it has caught an exception that it does not know how to handle. Make
-sure you understand why this page fault occurs. The "stval=0x0..04008"
-indicates that the virtual address that caused the page fault is
-0x4008.
-
-<h2>Part Two: Lazy allocation</h2>
-
-Modify the code in trap.c to respond to a page fault from user space
-by mapping a newly-allocated page of physical memory at the faulting
-address, and then returning back to user space to let the process
-continue executing. You should add your code just before
-the <tt>printf</tt> call that produced the "usertrap(): ..."
-message.
-
-<p>
-Hint: look at the printf arguments to see how to find the virtual
-address that caused the page fault.
-
-<p>
-Hint: steal code from allocuvm() in vm.c, which is what sbrk()
-calls (via growproc()).
-
-<p>
-Hint: use PGROUNDDOWN(va) to round the faulting virtual address
-down to a page boundary.
-
-<p>
-Hint: <tt>usertrapret()</tt> in order to avoid
-the <tt>printf</tt> and the <tt>myproc()->killed&nbsp;=&nbsp;1</tt>.
-
-<p>
-Hint: you'll need to call mappages().
-
-<p>Hint: you can check whether a fault is a page fault by r_scause()
- is 13 or 15 in trap().
-
-<p>Hint: modify unmappages() to not free pages that aren't mapped.
-
-<p>Hint: if the kernel crashes, look up sepc in kernel/kernel.asm
-
-<p>Hint: if you see the error "imcomplete type proc", include "proc.h"
- (and "spinlock.h").
-
-<p>Hint: the first test in sbrk() allocates something large, this
- should succeed now.
-
-<p>
-If all goes well, your lazy allocation code should result in <tt>echo
-hi</tt> working. You should get at least one page fault (and thus lazy
-allocation) in the shell, and perhaps two.
-
-<p>If you have the basics working, now turn your implementation into
- one that handles the corner cases too:
-
-<ul>
-
- <li> Handle negative sbrk() arguments. sbrktest() in usertests will
- tests this.
-
- <li> Handle fork correctly. sbrktst() will test this.
-
- <li> Make sure that kernel use of not-yet-allocated user addresses
- works; for example, if a program passes an sbrk()-allocated
- address to write(). sbrktest() will test this.
-
- <li> Handle out of memory correctly. sbrktst() will test this.
-
- <li> Handle faults on the invalid page below the stack. stacktest()
- in usertests will tests this.
-
-</ul>
-
-<p>Run all tests in usertests() to make sure your solution doesn't
-break other tests.
-
-<p>
-<div class="question">
-<p><b>Submit</b>: The code that you added to trap.c in a file named <em>hwN.c</em> where <em>N</em> is the homework number as listed on the schedule.
-</div>
-
-
-</body>
-</html>
diff --git a/labs/lock.html b/labs/lock.html
deleted file mode 100644
index 707d6c4..0000000
--- a/labs/lock.html
+++ /dev/null
@@ -1,148 +0,0 @@
-<html>
-<head>
-<title>Lab: locks</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: locks</h1>
-
-<p>In this lab you will try to avoid lock contention for certain
-workloads.
-
-<h2>lock contention</h2>
-
-<p>The program user/kalloctest stresses xv6's memory allocator: three
- processes grow and shrink there address space, which will results in
- many calls to <tt>kalloc</tt> and <tt>kfree</tt>,
- respectively. <tt>kalloc</tt> and <tt>kfree</tt>
- obtain <tt>kmem.lock</tt>. To see if there is lock contention for
- <tt>kmem.lock</tt> replace the call to <tt>acquire</tt>
- in <tt>kalloc</tt> with the following code:
-
- <pre>
- while(!tryacquire(&kmem.lock)) {
- printf("!");
- }
- </pre>
-
-<p><tt>tryacquire</tt> tries to acquire <tt>kmem.lock</tt>: if the
- lock is taking it returns false (0); otherwise, it returns true (1)
- and with the lock acquired. Your first job is to
- implement <tt>tryacquire</tt> in kernel/spinlock.c.
-
-<p>A few hints:
- <ul>
- <li>look at <tt>acquire</tt>.
- <li>don't forget to restore interrupts when acquision fails
- <li>Add tryacquire's signature to defs.h.
- </ul>
-
-<p>Run usertests to see if you didn't break anything. Note that
- usertests never prints "!"; there is never contention
- for <tt>kmem.lock</tt>. The caller is always able to immediately
- acquire the lock and never has to wait because some other process
- has the lock.
-
-<p>Now run kalloctest. You should see quite a number of "!" on the
- console. kalloctest causes many processes to contend on
- the <tt>kmem.lock</tt>. This lock contention is a bit artificial,
- because qemu is simulating 3 processors, but it is likely on real
- hardware, there would be contention too.
-
-<h2>Removing lock contention</h2>
-
-<p>The root cause of lock contention in kalloctest is that there is a
- single free list, protected by a single lock. To remove lock
- contention, you will have to redesign the memory allocator to avoid
- a single lock and list. The basic idea is to maintain a free list
- per CPU, each list with its own lock. Allocations and frees on each
- CPU can run in parallel, because each CPU will operate on a
- different list.
-
-<p> The main challenge will be to deal with the case that one CPU runs
- out of memory, but another CPU has still free memory; in that case,
- the one CPU must "steal" part of the other CPU's free list.
- Stealing may introduce lock contention, but that may be acceptable
- because it may happen infrequently.
-
-<p>Your job is to implement per-CPU freelists and stealing when one
- CPU is out of memory. Run kalloctest() to see if your
- implementation has removed lock contention.
-
-<p>Some hints:
- <ul>
- <li>You can use the constant <tt>NCPU</tt> in kernel/param.h
- <li>Let <tt>freerange</tt> give all free memory to the CPU
- running <tt>freerange</tt>.
- <li>The function <tt>cpuid</tt> returns the current core, but note
- that you can use it when interrupts are turned off and so you will
- need to turn on/off interrupts in your solution.
- </ul>
-
-<p>Run usertests to see if you don't break anything.
-
-<h2>More scalabale bcache lookup</h2>
-
-
-<p>Several processes reading different files repeatedly will
- bottleneck in the buffer cache, bcache, in bio.c. Replace the
- acquire in <tt>bget</tt> with
-
- <pre>
- while(!tryacquire(&bcache.lock)) {
- printf("!");
- }
- </pre>
-
- and run test0 from bcachetest and you will see "!"s.
-
-<p>Modify <tt>bget</tt> so that a lookup for a buffer that is in the
- bcache doesn't need to acquire <tt>bcache.lock</tt>. This is more
- tricky than the kalloc assignment, because bcache buffers are truly
- shared among processes. You must maintain the invariant that a
- buffer is only once in memory.
-
-<p> There are several races that <tt>bcache.lock</tt> protects
-against, including:
- <ul>
- <li>A <tt>brelse</tt> may set <tt>b->ref</tt> to 0,
- while concurrent <tt>bget</tt> is incrementing it.
- <li>Two <tt>bget</tt> may see <tt>b->ref = 0</tt> and one may re-use
- the buffer, while the other may replaces it with another block.
- <li>A concurrent <tt>brelse</tt> modifies the list
- that <tt>bget</tt> traverses.
- </ul>
-
-<p>A challenge is testing whether you code is still correct. One way
- to do is to artificially delay certain operations
- using <tt>sleepticks</tt>. <tt>test1</tt> trashes the buffer cache
- and exercises more code paths.
-
-<p>Here are some hints:
- <ul>
- <li>Read the description of buffer cache in the xv6 book (Section 7.2).
- <li>Use a simple design: i.e., don't design a lock-free implementation.
- <li>Use a simple hash table with locks per bucket.
- <li>Searching in hash table for a buffer and allocating an entry
- for that buffer when the buffer is not found must be atomic.
- <li>It is fine to acquire <tt>bcache.lock</tt> in <tt>brelse</tt>
- to update the LRU/MRU list.
- </ul>
-
-<p>Check that your implementation has less contention
- on <tt>test0</tt>
-
-<p>Make sure your implementation passes bcachetest and usertests.
-
-<p>Optional:
- <ul>
- <li>make the buffer cache more scalable (e.g., avoid taking
- out <tt>bcache.lock</tt> on <tt>brelse</tt>).
- <li>make lookup lock-free (Hint: use gcc's <tt>__sync_*</tt>
- functions.) How do you convince yourself that your implementation is correct?
- </ul>
-
-
-</body>
-</html>
diff --git a/labs/mmap.html b/labs/mmap.html
deleted file mode 100644
index 6f779c4..0000000
--- a/labs/mmap.html
+++ /dev/null
@@ -1,171 +0,0 @@
-<html>
-<head>
-<title>Lab: mmap</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: mmap</h1>
-
-<p>In this lab you will use </tt>mmap</tt> on Linux to demand-page a
-very large table and add memory-mapped files to xv6.
-
-<h2>Using mmap on Linux</h2>
-
-<p>This assignment will make you more familiar with how to manage virtual memory
-in user programs using the Unix system call interface. You can do this
-assignment on any operating system that supports the Unix API (a Linux Athena
-machine, your laptop with Linux or MacOS, etc.).
-
-<p>Download the <a href="mmap.c">mmap homework assignment</a> and look
-it over. The program maintains a very large table of square root
-values in virtual memory. However, the table is too large to fit in
-physical RAM. Instead, the square root values should be computed on
-demand in response to page faults that occur in the table's address
-range. Your job is to implement the demand faulting mechanism using a
-signal handler and UNIX memory mapping system calls. To stay within
-the physical RAM limit, we suggest using the simple strategy of
-unmapping the last page whenever a new page is faulted in.
-
-<p>To compile <tt>mmap.c</tt>, you need a C compiler, such as gcc. On Athena,
-you can type:
-<pre>
-$ add gnu
-</pre>
-Once you have gcc, you can compile mmap.c as follows:
-<pre>
-$ gcc mmap.c -lm -o mmap
-</pre>
-Which produces a <tt>mmap</tt> file, which you can run:
-<pre>
-$ ./mmap
-page_size is 4096
-Validating square root table contents...
-oops got SIGSEGV at 0x7f6bf7fd7f18
-</pre>
-
-<p>When the process accesses the square root table, the mapping does not exist
-and the kernel passes control to the signal handler code in
-<tt>handle_sigsegv()</tt>. Modify the code in <tt>handle_sigsegv()</tt> to map
-in a page at the faulting address, unmap a previous page to stay within the
-physical memory limit, and initialize the new page with the correct square root
-values. Use the function <tt>calculate_sqrts()</tt> to compute the values.
-The program includes test logic that verifies if the contents of the
-square root table are correct. When you have completed your task
-successfully, the process will print &ldquo;All tests passed!&rdquo;.
-
-<p>You may find that the man pages for mmap() and munmap() are helpful references.
-<pre>
-$ man mmap
-$ man munmap
-</pre>
-
-
-<h2>Implement memory-mapped files in xv6</h2>
-
-<p>In this assignment you will implement memory-mapped files in xv6.
- The test program <tt>mmaptest</tt> tells you what should work.
-
-<p>Here are some hints about how you might go about this assignment:
-
- <ul>
- <li>Start with adding the two systems calls to the kernel, as you
- done for other systems calls (e.g., <tt>sigalarm</tt>), but
- don't implement them yet; just return an
- error. run <tt>mmaptest</tt> to observe the error.
-
- <li>Keep track for each process what <tt>mmap</tt> has mapped.
- You will need to allocate a <tt>struct vma</tt> to record the
- address, length, permissions, etc. for each virtual memory area
- (VMA) that maps a file. Since the xv6 kernel doesn't have a
- memory allocator in the kernel, you can use the same approach has
- for <tt>struct file</tt>: have a global array of <tt>struct
- vma</tt>s and have for each process a fixed-sized array of VMAs
- (like the file descriptor array).
-
- <li>Implement <tt>mmap</tt>: allocate a VMA, add it to the process's
- table of VMAs, fill in the VMA, and find a hole in the process's
- address space where you will map the file. You can assume that no
- file will be bigger than 1GB. The VMA will contain a pointer to
- a <tt>struct file</tt> for the file being mapped; you will need to
- increase the file's reference count so that the structure doesn't
- disappear when the file is closed (hint:
- see <tt>filedup</tt>). You don't have worry about overlapping
- VMAs. Run <tt>mmaptest</tt>: the first <tt>mmap</tt> should
- succeed, but the first access to the mmaped- memory will fail,
- because you haven't updated the page fault handler.
-
- <li>Modify the page-fault handler from the lazy-allocation and COW
- labs to call a VMA function that handles page faults in VMAs.
- This function allocates a page, reads a 4KB from the mmap-ed
- file into the page, and maps the page into the address space of
- the process. To read the page, you can use <tt>readi</tt>,
- which allows you to specify an offset from where to read in the
- file (but you will have to lock/unlock the inode passed
- to <tt>readi</tt>). Don't forget to set the permissions correctly
- on the page. Run <tt>mmaptest</tt>; you should get to the
- first <tt>munmap</tt>.
-
- <li>Implement <tt>munmap</tt>: find the <tt>struct vma</tt> for
- the address and unmap the specified pages (hint:
- use <tt>uvmunmap</tt>). If <tt>munmap</tt> removes all pages
- from a VMA, you will have to free the VMA (don't forget to
- decrement the reference count of the VMA's <tt>struct
- file</tt>); otherwise, you may have to shrink the VMA. You can
- assume that <tt>munmap</tt> will not split a VMA into two VMAs;
- that is, we don't unmap a few pages in the middle of a VMA. If
- an unmapped page has been modified and the file is
- mapped <tt>MAP_SHARED</tt>, you will have to write the page back
- to the file. RISC-V has a dirty bit (<tt>D</tt>) in a PTE to
- record whether a page has ever been written too; add the
- declaration to kernel/riscv.h and use it. Modify <tt>exit</tt>
- to call <tt>munmap</tt> for the process's open VMAs.
- Run <tt>mmaptest</tt>; you should <tt>mmaptest</tt>, but
- probably not <tt>forktest</tt>.
-
- <li>Modify <tt>fork</tt> to copy VMAs from parent to child. Don't
- forget to increment reference count for a VMA's <tt>struct
- file</tt>. In the page fault handler of the child, it is OK to
- allocate a new page instead of sharing the page with the
- parent. The latter would be cooler, but it would require more
- implementation work. Run <tt>mmaptest</tt>; make sure you pass
- both <tt>mmaptest</tt> and <tt>forktest</tt>.
-
- </ul>
-
-<p>Run usertests to make sure you didn't break anything.
-
-<p>Optional challenges:
- <ul>
-
- <li>If two processes have the same file mmap-ed (as
- in <tt>forktest</tt>), share their physical pages. You will need
- reference counts on physical pages.
-
- <li>The solution above allocates a new physical page for each page
- read from the mmap-ed file, even though the data is also in kernel
- memory in the buffer cache. Modify your implementation to mmap
- that memory, instead of allocating a new page. This requires that
- file blocks be the same size as pages (set <tt>BSIZE</tt> to
- 4096). You will need to pin mmap-ed blocks into the buffer cache.
- You will need worry about reference counts.
-
- <li>Remove redundancy between your implementation for lazy
- allocation and your implementation of mmapp-ed files. (Hint:
- create an VMA for the lazy allocation area.)
-
- <li>Modify <tt>exec</tt> to use a VMA for different sections of
- the binary so that you get on-demand-paged executables. This will
- make starting programs faster, because <tt>exec</tt> will not have
- to read any data from the file system.
-
- <li>Implement on-demand paging: don't keep a process in memory,
- but let the kernel move some parts of processes to disk when
- physical memory is low. Then, page in the paged-out memory when
- the process references it. Port your linux program from the first
- assignment to xv6 and run it.
-
- </ul>
-
-</body>
-</html>
diff --git a/labs/syscall.html b/labs/syscall.html
deleted file mode 100644
index 2281f2e..0000000
--- a/labs/syscall.html
+++ /dev/null
@@ -1,443 +0,0 @@
-<html>
-<head>
-<title>Lab: Alarm and uthread</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: Alarm and uthread</h1>
-
-This lab will familiarize you with the implementation of system calls
-and switching between threads of execution. In particular, you will
-implement new system calls (<tt>sigalarm</tt> and <tt>sigreturn</tt>)
-and switching between threads in a user-level thread package.
-
-<h2>Warmup: RISC-V assembly</h2>
-
-<p>For this lab it will be important to understand a bit of RISC-V assembly.
-
-<p>Add a file user/call.c with the following content, modify the
- Makefile to add the program to the user programs, and compile (make
- fs.img). The Makefile also produces a binary and a readable
- assembly a version of the program in the file user/call.asm.
-<pre>
-#include "kernel/param.h"
-#include "kernel/types.h"
-#include "kernel/stat.h"
-#include "user/user.h"
-
-int g(int x) {
- return x+3;
-}
-
-int f(int x) {
- return g(x);
-}
-
-void main(void) {
- printf(1, "%d %d\n", f(8)+1, 13);
- exit();
-}
-</pre>
-
-<p>Read through user/call.asm and understand it. The instruction manual
- for RISC-V is in the doc directory (doc/riscv-spec-v2.2.pdf). Here
- are some questions that you should answer for yourself:
-
- <ul>
- <li>Which registers contain arguments to functions? Which
- register holds 13 in the call to <tt>printf</tt>? Which register
- holds the second argument? Which register holds the third one? Etc.
-
- <li>Where is the function call to <tt>f</tt> from main? Where
- is the call to <tt>g</tt>?
- (Hint: the compiler may inline functions.)
-
- <li>At what address is the function <tt>printf</tt> located?
-
- <li>What value is in the register <tt>ra</tt> just after the <tt>jalr</tt>
- to <tt>printf</tt> in <tt>main</tt>?
- </ul>
-
-<h2>Warmup: system call tracing</h2>
-
-<p>In this exercise you will modify the xv6 kernel to print out a line
-for each system call invocation. It is enough to print the name of the
-system call and the return value; you don't need to print the system
-call arguments.
-
-<p>
-When you're done, you should see output like this when booting
-xv6:
-
-<pre>
-...
-fork -> 2
-exec -> 0
-open -> 3
-close -> 0
-$write -> 1
- write -> 1
-</pre>
-
-<p>
-That's init forking and execing sh, sh making sure only two file descriptors are
-open, and sh writing the $ prompt. (Note: the output of the shell and the
-system call trace are intermixed, because the shell uses the write syscall to
-print its output.)
-
-<p> Hint: modify the syscall() function in kernel/syscall.c.
-
-<p>Run the xv6 programs you wrote in earlier labs and inspect the system call
- trace. Are there many system calls? Which system calls correspond
- to code in the applications you wrote?
-
-<p>Optional: print the system call arguments.
-
-
-<h2>Alarm</h2>
-
-<p>
-In this exercise you'll add a feature to xv6 that periodically alerts
-a process as it uses CPU time. This might be useful for compute-bound
-processes that want to limit how much CPU time they chew up, or for
-processes that want to compute but also want to take some periodic
-action. More generally, you'll be implementing a primitive form of
-user-level interrupt/fault handlers; you could use something similar
-to handle page faults in the application, for example.
-
-<p>
-You should add a new <tt>sigalarm(interval, handler)</tt> system call.
-If an application calls <tt>sigalarm(n, fn)</tt>, then after every
-<tt>n</tt> "ticks" of CPU time that the program consumes, the kernel
-should cause application function
-<tt>fn</tt> to be called. When <tt>fn</tt> returns, the application
-should resume where it left off. A tick is a fairly arbitrary unit of
-time in xv6, determined by how often a hardware timer generates
-interrupts.
-
-<p>
-You'll find a file <tt>user/alarmtest.c</tt> in your xv6
-repository. Add it to the Makefile. It won't compile correctly
-until you've added <tt>sigalarm</tt> and <tt>sigreturn</tt>
-system calls (see below).
-
-<p>
-<tt>alarmtest</tt> calls <tt>sigalarm(2, periodic)</tt> in <tt>test0</tt> to
-ask the kernel to force a call to <tt>periodic()</tt> every 2 ticks,
-and then spins for a while.
-You can see the assembly
-code for alarmtest in user/alarmtest.asm, which may be handy
-for debugging.
-When you've finished the lab,
-<tt>alarmtest</tt> should produce output like this:
-
-<pre>
-$ alarmtest
-test0 start
-......................................alarm!
-test0 passed
-test1 start
-..alarm!
-..alarm!
-..alarm!
-.alarm!
-..alarm!
-..alarm!
-..alarm!
-..alarm!
-..alarm!
-..alarm!
-test1 passed
-$
-</pre>
-
-<p>The main challenge will be to arrange that the handler is invoked
- when the process's alarm interval expires. You'll need to modify
- usertrap() in kernel/trap.c so that when a
- process's alarm interval expires, the process executes
- the handler. How can you do that? You will need to understand
- how system calls work (i.e., the code in kernel/trampoline.S
- and kernel/trap.c). Which register contains the address to which
- system calls return?
-
-<p>Your solution will be only a few lines of code, but it may be tricky to
- get it right.
-We'll test your code with the version of alarmtest.c in the original
-repository; if you modify alarmtest.c, make sure your kernel changes
-cause the original alarmtest to pass the tests.
-
-<h3>test0: invoke handler</h3>
-
-<p>Get started by modifying the kernel to jump to the alarm handler in
-user space, which will cause test0 to print "alarm!". Don't worry yet
-what happens after the "alarm!" output; it's OK for now if your
-program crashes after printing "alarm!". Here are some hints:
-
-<ul>
-
-<li>You'll need to modify the Makefile to cause <tt>alarmtest.c</tt>
-to be compiled as an xv6 user program.
-
-<li>The right declarations to put in <tt>user/user.h</tt> are:
-<pre>
- int sigalarm(int ticks, void (*handler)());
- int sigreturn(void);
-</pre>
-
-<li>Update user/sys.pl (which generates user/usys.S),
- kernel/syscall.h, and kernel/syscall.c
- to allow <tt>alarmtest</tt> to invoke the sigalarm and
- sigreturn system calls.
-
-<li>For now, your <tt>sys_sigreturn</tt> should just return zero.
-
-<li>Your <tt>sys_sigalarm()</tt> should store the alarm interval and
-the pointer to the handler function in new fields in the <tt>proc</tt>
-structure, defined in <tt>kernel/proc.h</tt>.
-
-<li>You'll need to keep track of how many ticks have passed since the
-last call (or are left until the next call) to a process's alarm
-handler; you'll need a new field in <tt>struct&nbsp;proc</tt> for this
-too. You can initialize <tt>proc</tt> fields in <tt>allocproc()</tt>
-in <tt>proc.c</tt>.
-
-<li>Every tick, the hardware clock forces an interrupt, which is handled
-in <tt>usertrap()</tt>; you should add some code here.
-
-<li>You only want to manipulate a process's alarm ticks if there's a a
- timer interrupt; you want something like
-<pre>
- if(which_dev == 2) ...
-</pre>
-
-<li>Only invoke the alarm function if the process has a
- timer outstanding. Note that the address of the user's alarm
- function might be 0 (e.g., in alarmtest.asm, <tt>periodic</tt> is at
- address 0).
-
-<li>It will be easier to look at traps with gdb if you tell qemu to
-use only one CPU, which you can do by running
-<pre>
- make CPUS=1 qemu
-</pre>
-
-<li>You've succeeded if alarmtest prints "alarm!".
-
-</ul>
-
-<h3>test1(): resume interrupted code</h3>
-
-Chances are that alarmtest crashes at some point after it prints
-"alarm!". Depending on how your solution works, that point may be in
-test0, or it may be in test1. Crashes are likely caused
-by the alarm handler (<tt>periodic</tt> in alarmtest.c) returning
-to the wrong point in the user program.
-
-<p>
-Your job now is to ensure that, when the alarm handler is done,
-control returns to
-the instruction at which the user program was originally
-interrupted by the timer interrupt. You must also ensure that
-the register contents are restored to values they held
-at the time of the interrupt, so that the user program
-can continue undisturbed after the alarm.
-
-<p>Your solution is likely to require you to save and restore
- registers---what registers do you need to save and restore to resume
- the interrupted code correctly? (Hint: it will be many).
- Several approaches are possible; for this lab you should make
- the <tt>sigreturn</tt> system call
- restore registers and return to the original
- interrupted user instruction.
- The user-space alarm handler
- calls sigreturn when it is done.
-
- Some hints:
- <ul>
- <li>Have <tt>usertrap</tt> save enough state in
- <tt>struct proc</tt> when the timer goes off
- that <tt>sigreturn</tt> can correctly return to the
- interrupted user code.
-
- <li>Prevent re-entrant calls to the handler----if a handler hasn't
- returned yet, the kernel shouldn't call it again.
- </ul>
-
-<p>Once you pass <tt>test0</tt> and <tt>test1</tt>, run usertests to
- make sure you didn't break any other parts of the kernel.
-
-<h2>Uthread: switching between threads</h2>
-
-<p>Download <a href="uthread.c">uthread.c</a> and <a
- href="uthread_switch.S">uthread_switch.S</a> into your xv6 directory.
-Make sure <tt>uthread_switch.S</tt> ends with <tt>.S</tt>, not
-<tt>.s</tt>. Add the
-following rule to the xv6 Makefile after the _forktest rule:
-
-<pre>
-$U/_uthread: $U/uthread.o $U/uthread_switch.o
- $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_uthread $U/uthread.o $U/uthread_switch.o $(ULIB)
- $(OBJDUMP) -S $U/_uthread > $U/uthread.asm
-</pre>
-Make sure that the blank space at the start of each line is a tab,
-not spaces.
-
-<p>
-Add <tt>_uthread</tt> in the Makefile to the list of user programs defined by UPROGS.
-
-<p>Run xv6, then run <tt>uthread</tt> from the xv6 shell. The xv6 kernel will print an error message about <tt>uthread</tt> encountering a page fault.
-
-<p>Your job is to complete <tt>uthread_switch.S</tt>, so that you see output similar to
-this (make sure to run with CPUS=1):
-<pre>
-~/classes/6828/xv6$ make CPUS=1 qemu
-...
-$ uthread
-my thread running
-my thread 0x0000000000002A30
-my thread running
-my thread 0x0000000000004A40
-my thread 0x0000000000002A30
-my thread 0x0000000000004A40
-my thread 0x0000000000002A30
-my thread 0x0000000000004A40
-my thread 0x0000000000002A30
-my thread 0x0000000000004A40
-my thread 0x0000000000002A30
-...
-my thread 0x0000000000002A88
-my thread 0x0000000000004A98
-my thread: exit
-my thread: exit
-thread_schedule: no runnable threads
-$
-</pre>
-
-<p><tt>uthread</tt> creates two threads and switches back and forth between
-them. Each thread prints "my thread ..." and then yields to give the other
-thread a chance to run.
-
-<p>To observe the above output, you need to complete <tt>uthread_switch.S</tt>, but before
-jumping into <tt>uthread_switch.S</tt>, first understand how <tt>uthread.c</tt>
-uses <tt>uthread_switch</tt>. <tt>uthread.c</tt> has two global variables
-<tt>current_thread</tt> and <tt>next_thread</tt>. Each is a pointer to a
-<tt>thread</tt> structure. The thread structure has a stack for a thread and a
-saved stack pointer (<tt>sp</tt>, which points into the thread's stack). The
-job of <tt>uthread_switch</tt> is to save the current thread state into the
-structure pointed to by <tt>current_thread</tt>, restore <tt>next_thread</tt>'s
-state, and make <tt>current_thread</tt> point to where <tt>next_thread</tt> was
-pointing to, so that when <tt>uthread_switch</tt> returns <tt>next_thread</tt>
-is running and is the <tt>current_thread</tt>.
-
-<p>You should study <tt>thread_create</tt>, which sets up the initial stack for
-a new thread. It provides hints about what <tt>uthread_switch</tt> should do.
-Note that <tt>thread_create</tt> simulates saving all callee-save registers
-on a new thread's stack.
-
-<p>To write the assembly in <tt>thread_switch</tt>, you need to know how the C
-compiler lays out <tt>struct thread</tt> in memory, which is as
-follows:
-
-<pre>
- --------------------
- | 4 bytes for state|
- --------------------
- | stack size bytes |
- | for stack |
- --------------------
- | 8 bytes for sp |
- -------------------- <--- current_thread
- ......
-
- ......
- --------------------
- | 4 bytes for state|
- --------------------
- | stack size bytes |
- | for stack |
- --------------------
- | 8 bytes for sp |
- -------------------- <--- next_thread
-</pre>
-
-The variables <tt>&next_thread</tt> and <tt>&current_thread</tt> each
-contain the address of a pointer to <tt>struct thread</tt>, and are
-passed to <tt>thread_switch</tt>. The following fragment of assembly
-will be useful:
-
-<pre>
- ld t0, 0(a0)
- sd sp, 0(t0)
-</pre>
-
-This saves <tt>sp</tt> in <tt>current_thread->sp</tt>. This works because
-<tt>sp</tt> is at
-offset 0 in the struct.
-You can study the assembly the compiler generates for
-<tt>uthread.c</tt> by looking at <tt>uthread.asm</tt>.
-
-<p>To test your code it might be helpful to single step through your
-<tt>uthread_switch</tt> using <tt>riscv64-linux-gnu-gdb</tt>. You can get started in this way:
-
-<pre>
-(gdb) file user/_uthread
-Reading symbols from user/_uthread...
-(gdb) b *0x230
-
-</pre>
-0x230 is the address of uthread_switch (see uthread.asm). When you
-compile it may be at a different address, so check uthread_asm.
-You may also be able to type "b uthread_switch". <b>XXX This doesn't work
- for me; why?</b>
-
-<p>The breakpoint may (or may not) be triggered before you even run
-<tt>uthread</tt>. How could that happen?
-
-<p>Once your xv6 shell runs, type "uthread", and gdb will break at
-<tt>thread_switch</tt>. Now you can type commands like the following to inspect
-the state of <tt>uthread</tt>:
-
-<pre>
- (gdb) p/x *next_thread
- $1 = {sp = 0x4a28, stack = {0x0 (repeats 8088 times),
- 0x68, 0x1, 0x0 <repeats 102 times>}, state = 0x1}
-</pre>
-What address is <tt>0x168</tt>, which sits on the bottom of the stack
-of <tt>next_thread</tt>?
-
-With "x", you can examine the content of a memory location
-<pre>
- (gdb) x/x next_thread->sp
- 0x4a28 <all_thread+16304>: 0x00000168
-</pre>
-Why does that print <tt>0x168</tt>?
-
-<h3>Optional challenges</h3>
-
-<p>The user-level thread package interacts badly with the operating system in
-several ways. For example, if one user-level thread blocks in a system call,
-another user-level thread won't run, because the user-level threads scheduler
-doesn't know that one of its threads has been descheduled by the xv6 scheduler. As
-another example, two user-level threads will not run concurrently on different
-cores, because the xv6 scheduler isn't aware that there are multiple
-threads that could run in parallel. Note that if two user-level threads were to
-run truly in parallel, this implementation won't work because of several races
-(e.g., two threads on different processors could call <tt>thread_schedule</tt>
-concurrently, select the same runnable thread, and both run it on different
-processors.)
-
-<p>There are several ways of addressing these problems. One is
- using <a href="http://en.wikipedia.org/wiki/Scheduler_activations">scheduler
- activations</a> and another is to use one kernel thread per
- user-level thread (as Linux kernels do). Implement one of these ways
- in xv6. This is not easy to get right; for example, you will need to
- implement TLB shootdown when updating a page table for a
- multithreaded user process.
-
-<p>Add locks, condition variables, barriers,
-etc. to your thread package.
-
-</body>
-</html>
-
diff --git a/labs/xv6.html b/labs/xv6.html
deleted file mode 100644
index 13d581e..0000000
--- a/labs/xv6.html
+++ /dev/null
@@ -1,238 +0,0 @@
-<html>
-<head>
-<title>Lab: xv6</title>
-<link rel="stylesheet" href="homework.css" type="text/css" />
-</head>
-<body>
-
-<h1>Lab: xv6</h1>
-
-This lab makes you familiar with xv6 and its system calls.
-
-<h2>Boot xv6</h2>
-
-<p>Login to Athena (e.g., ssh -X athena.dialup.mit.edu) and attach the course
-locker: (You must run this command every time you log in; or add it to your
-~/.environment file.)
-
-<pre>
-$ add -f 6.828
-</pre>
-
-<p>Fetch the xv6 source:
-
-<pre>
-$ mkdir 6.828
-$ cd 6.828
-$ git clone git://github.com/mit-pdos/xv6-riscv.git
-Cloning into 'xv6-riscv'...
-...
-$
-</pre>
-
-<p>XXX pointer to an update tools page
-
-<p>Build xv6 on Athena:
-<pre>
-$ cd xv6-public
-$ makeriscv64-linux-gnu-gcc -c -o kernel/entry.o kernel/entry.S
-riscv64-linux-gnu-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -MD -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o kernel/start.o kernel/start.c
-...
-$ make qemu
-...
-mkfs/mkfs fs.img README user/_cat user/_echo user/_forktest user/_grep user/_init user/_kill user/_ln user/_ls user/_mkdir user/_rm user/_sh user/_stressfs user/_usertests user/_wc user/_zombie user/_cow
-nmeta 46 (boot, super, log blocks 30 inode blocks 13, bitmap blocks 1) blocks 954 total 1000
-balloc: first 497 blocks have been allocated
-balloc: write bitmap block at sector 45
-qemu-system-riscv64 -machine virt -kernel kernel/kernel -m 3G -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
-hart 0 starting
-hart 2 starting
-hart 1 starting
-init: starting sh
-$
-</pre>
-
-<p>
-If you type <tt>ls</tt> at the prompt, you should output similar to the following:
-<pre>
-$ ls
-. 1 1 1024
-.. 1 1 1024
-README 2 2 2181
-cat 2 3 21024
-echo 2 4 19776
-forktest 2 5 11456
-grep 2 6 24512
-init 2 7 20656
-kill 2 8 19856
-ln 2 9 19832
-ls 2 10 23280
-mkdir 2 11 19952
-rm 2 12 19936
-sh 2 13 38632
-stressfs 2 14 20912
-usertests 2 15 106264
-wc 2 16 22160
-zombie 2 17 19376
-cow 2 18 27152
-console 3 19 0
-</pre>
-These are the programs/files that <tt>mkfs</tt> includes in the
-initial file system. You just ran one of them: <tt>ls</tt>.
-
-<h2>sleep</h2>
-
-<p>Implement the UNIX program sleep for xv6; your sleep should pause
- for a user-specified number of ticks.
-
-<p>Some hints:
- <ul>
- <li>Look at some of the other programs in <tt>user/</tt> to see
- how you can obtain the command-line arguments passed to a program. If the user
- forgets to pass an argument, sleep should print an error message.
-
- <li>The command-line argument is passed as a string; you can convert it to an
- integer using <tt>atoi</tt> (see user/ulib.c).
-
- <li>Use the system call <tt>sleep</tt> (see user/usys.S and kernel/sysproc.c).
-
- <li>Make sure <tt>main</tt> calls <tt>exit()</tt> in order to exit
- your program.
-
- <li>Add the program to <tt>UPROGS</tt> in Makefile and compile
- user programs by typing <tt>make fs.img</tt>.
-
- </ul>
-
- <p>Run the program from the xv6 shell:
- <pre>
- $ make qemu
- ...
- init: starting sh
- $ sleep 10
- (waits for a little while)
- $
- </pre>
-
- <p>Optional: write an uptime program that prints the uptime in terms
- of ticks using the <tt>uptime</tt> system call.
-
-<h2>pingpong</h2>
-
-<p> Write a program that uses UNIX system calls to ``ping-pong'' a
- byte between two processes over a pair of pipes, one for each
- direction. The parent sends by writing a byte to <tt>fd[1]</tt> and
- the child receives it by reading from <tt>fd[0]</tt>. After
- receiving a byte from parent, the child responds with its own byte
- by writing to <tt>fd[1]</tt>, which the parent then reads.
-
-<p>Some hints:
- <ul>
- <li>Use <tt>pipe</tt> to create a pipe.
- <li>Use <tt>fork</tt> to create a child.
- <li>Use <tt>read</tt> to read from the pipe, and <tt>write</tt> to write to the pipe.
- </ul>
-
-<h2>primes</h2>
-
- <p>Write a concurrent version of prime sieve using pipes. This idea
- is due to Doug McIlroy, inventor of Unix pipes. The picture
- halfway down <a href="http://swtch.com/~rsc/thread/">the page</a>
- and the text surrounding it explain how to do it.
-
- <p>Your goal is to use <tt>pipe</tt> and <tt>fork</tt> to set up
- the pipeline. The first process feeds the numbers 2 through 35
- into the pipeline. For each prime number, you will arrange to
- create one process that reads from its left neighbor over a pipe
- and writes to its right neighbor over another pipe. Since xv6 has
- limited number of file descriptors and processes, the first
- process can stop at 35.
-
-<p>Some hints:
- <ul>
- <li>Be careful to close file descriptors that a process doesn't
- need, because otherwise your program will run xv6 out of resources
- before the first process reaches 35.
-
- <li>Once the first process reach 35, you should arrange that the
- pipeline terminates cleanly (Hint: read will return an end-of-file
- when the write-side of the pipe is closed).
- </ul>
-
-<h2>find</h2>
-
-<p>Write a simple version of the UNIX find program: find all the files
- in a directory tree whose name matches a string. For example if the
- file system contains a file <tt>a/b</tt>, then running find as
- follows should produce:
- <pre>
- $ find . b
- ./a/b
- $
- </pre>
-
-<p>Some hints:
- <ul>
- <li>Look at user/ls.c to see how to read directories.
- <li>Use recursion to run find in sub-directories.
- <li>Don't recurse into "." and "..".
- </ul>
-
-<p>Optional: support regular expressions in name matching. Grep has some
- primitive support for regular expressions.
-
-<h2>xargs</h2>
-
-<p>Write a simple version of the UNIX xargs program: read lines from
- standard in and run a command for each line, supplying the line as
- arguments to the command. The following example illustrates xarg's
- behavior:
- <pre>
- $ xargs echo bye
- hello too
- bye hello too
- <ctrl-d>
- $
- </pre>
- Note that the command here is "echo bye" and the additional
- arguments are "hello too", making the command "echo bye hello too",
- which outputs "bye hello too".
-
-<p>xargs and find combine well:
- <pre>
- find . b | xargs grep hello
- </pre>
- will run "grep hello" on each file named b in the directories below ".".
-
-<p>Some hints:
- <ul>
- <li>Use <tt>fork</tt> and <tt>exec</tt> system call to invoke the
- command on each line of input. Use <tt>wait</tt> in the parent
- to wait for the child to complete running the command.
- <li>Read from stdin a character at the time until the newline
- character ('\n').
- <li>kernel/param.h declares MAXARG, which may be useful if you need
- to declare an argv.
- </ul>
-
-<h2>Optional: modify the shell</h2>
-
-There are endless ways in which the shell could be extended. Here are
-some suggestions:
-
-<ul>
-
-<li>Modify the shell to support wait.
-
-<li>Modify the shell to support lists of commands, separated by ";"
-
-<li>Modify the shell to support sub-shells by implementing "(" and ")"
-
-<li>Modify the shell to allow users to edit the command line
-
-</ul>
-
-</body>
-</html>
-
-