In this first xv6 assignment you will be modifying and adding code to a number of different files that implement the kernel. Along the way you'll learn how to build the kernel and test/debug your work, and will hopefully acquire a working knowledge of some essential kernel tasks and modules. You won't be adding much source code; rather, the goal is to get you comfortable with the workflow so that future assignments requiring more extensive changes to the codebase won't be too intimidating!
You should've received an e-mail invitation to share a private repository with me on BitBucket --- you need to have accepted this invitation in order to continue.
Assuming you have Git installed on your machine, simply
run the following command (with your own username) to clone your repository into
a directory named xv6
on your machine.
$ git clone https://bitbucket.org/michaelee/cs450-summer2015-USERNAME.git xv6
Next, you should create an upstream branch that tracks my public xv6 repository, just in case I push any more commits to it for upcoming machine problems.
$ cd xv6
$ git remote add upstream https://bitbucket.org/michaelee/xv6.git
You can now fetch and merge the master branch from this upstream repository into your own master branch. Nothing should happen, as I won't have pushed any new commits yet.
$ git fetch upstream
From https://bitbucket.org/michaelee/xv6
* [new branch] master -> upstream/master
$ git merge upstream/master
Already up-to-date.
(Note: if you prefer to watch a full demonstration of the following instructions first, just scroll down for a video recording.)
In order to run xv6, we'll be using a combination of the VirtualBox virtualization platform and the Vagrant virtual environment management tool. Installers for both pieces of software are available for Linux, Mac OS X, and Windows. Download and install them first, then come back here — download links follow:
While VirtualBox comes with a graphical user interface, we won't be using it directly. Instead, everything will be done through Vagrant's command line interface.
Start up your platform's command line interpreter (a terminal emulator on Linux/OS X, or Windows command prompt), change into the cloned "xv6" directory, then type the following command:
$ vagrant up
If everything's been installed properly, this command will download the necessary images from the Internet and configure them so as to set up a virtual Linux machine that you can subsequently connect to in order to build and test xv6. It'll take a while. Output will look something like this:
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/trusty64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/trusty64' is up to date...
==> default: Setting the name of the VM: xv6_default_1413579643103_34759
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
default: Adapter 1: nat
==> default: Forwarding ports...
default: 22 => 2222 (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
default: SSH address: 127.0.0.1:2222
default: SSH username: vagrant
default: SSH auth method: private key
default: Warning: Connection timeout. Retrying...
default: Warning: Remote connection disconnect. Retrying...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
default: /vagrant => /Users/lee/Desktop/xv6
==> default: Running provisioner: shell...
default: Running: /var/folders/dg/sq87_t4s20n1jfl25jrlstrh0000gn/T/vagrant-shell20141017-40376-1exv8ab.sh
==> default: stdin: is not a tty
==> default: Ign http://security.ubuntu.com trusty-security InRelease
==> default: Ign http://archive.ubuntu.com trusty InRelease
==> default: Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
...
...
...
==> default: Setting up gdb (7.7-0ubuntu3.1) ...
==> default: Setting up libc6-dbg:amd64 (2.19-0ubuntu6.3) ...
==> default: Processing triggers for libc-bin (2.19-0ubuntu6.3) ...
When done, try the command "vagrant status
" --- it should produce the
following output:
Current machine states:
default running (virtualbox)
The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.
This means the virtual machine is now running. You can connect to it using the
command "vagrant ssh
", which will result in the following:
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-36-generic x86_64)
* Documentation: https://help.ubuntu.com/
System information as of Fri Oct 17 21:00:59 UTC 2014
System load: 0.52 Processes: 90
Usage of /: 2.7% of 39.34GB Users logged in: 0
Memory usage: 8% IP address for eth0: 10.0.2.15
Swap usage: 0%
Graph this data and manage this system at:
https://landscape.canonical.com/
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
vagrant@vagrant-ubuntu-trusty-64:~$
You're now logged into the virtual machine. The "/vagrant
" directory on the
virtual machine is synchronized with the git repository you cloned earlier, and
we'll be going there to build xv6 and run it using an emulator that was
automatically installed for you on the virtual machine. The following commands
will do this:
$ cd /vagrant
$ make
$ make qemu-nox
The first make
command should've resulted in a (successful) build process,
while the second should've resulted in the following output:
qemu-system-i386 -nographic -hdb fs.img xv6.img -smp 1 -m 512 -snapshot
xv6...
cpu0: starting
init: starting sh
$
This last prompt is produced by xv6, which you've now booted into on top of an emulator (which is in turn running within a virtual machine).
You can explore a bit here, but when you're ready to exit the xv6 session, use
the key sequence "^a x
" (i.e., hit the 'a' key while holding down the control
key, then release both and hit the 'x' key) — ^a
is a special prefix
key used to send the emulator an interrupt sequence.
You should now be dropped back into the virtual linux machine. To get out of
that and back onto your own machine, just type "exit
". At this point, if you
wish, you can terminate the virtual machine by entering "vagrant halt
". To
bring it back up just use "vagrant up
". To SSH in to a running virtual
machine, use "vagrant ssh
".
The following terminal session recording demonstrates the entire procedure
described above (note that I already downloaded the Linux image earlier, so your
"vagrant up
" command may take significantly longer to complete).
The great thing about using Vagrant is that it automatically gives us the ability to synchronize directory contents between the host machine and the virtual (or "guest") machine. This means you can do all your coding with tools installed on your own machine (e.g., favorite IDEs/editors) and only SSH into the virtual machine for testing purposes.
The xv6 kernel codebase is already sitting in your cloned repository, so you
will simply open and edit any files in there. When done, you will do a "vagrant
ssh
", then "cd /vagrant
" in the guest machine to get to the synchronized
directory (containing your changes) so as to build and test your changes.
To re-build the xv6 image, you'll always want to first do a "make clean
"
before running "make
", as weird errors will often arise otherwise. Get in the
habit of just running the commands together with "make clean ; make
" (the
shell accepts multiple commands separated by semicolons).
If you wish to debug (e.g., step through and set breakpoints in) the kernel, you
can use "make qemu-nox-gdb
" to run the emulator in a mode that allows you to
attach a gdb session to it. This will require that you "vagrant ssh
" into the
virtual machine twice --- once to start up the emulator and another to run gdb
--- or make use of a terminal multiplexer (tmux
is already installed for you). In the recording below I first make a simple
modification to the shell program, then demonstrate the use of the terminal
emulator to aid me in a quick debugging session. Note that I use the key
sequences "^b "
" to split the screen horizontally and "^b o
" to switch
between panes.
For this lab you'll add a new system call called getcount
to xv6, which, when
passed a valid system call number (listed in the file "syscall.h
") as an
argument, will return the number of times the referenced system call was invoked
by the calling process.
E.g., the following test program:
#include "types.h"
#include "user.h"
#include "syscall.h"
int
main(int argc, char *argv[])
{
printf(1, "initial fork count %d\n", getcount(SYS_fork));
if (fork() == 0) {
printf(1, "child fork count %d\n", getcount(SYS_fork));
printf(1, "child write count %d\n", getcount(SYS_write));
} else {
wait();
printf(1, "parent fork count %d\n", getcount(SYS_fork));
printf(1, "parent write count %d\n", getcount(SYS_write));
}
printf(1, "wait count %d\n", getcount(SYS_wait));
exit();
}
Will produce the following output (note that each character is output with a
separate call to write
in xv6):
initial fork count 0
child fork count 0
child write count 19
wait count 0
parent fork count 1
parent write count 41
wait count 1
You will need to modify a number of different files for this exercise, though
the total number of lines of code you'll be adding is quite small. At a minimum,
you'll need to alter syscall.h
, syscall.c
, user.h
, and usys.S
to
implement your new system call (try tracing how some other system call is
implemented, e.g., uptime
, for clues). You will likely also need to update
struct proc
, located in proc.h
, to add a syscall-count tracking data
structure for each process. To re-initialize your data structure when a process
terminates, you may want to look into the functions found in proc.c
.
Chapter 3 of the xv6 book contains details on traps and system calls (though most of the low level details won't be necessary for you to complete this exercise).
To test your implementation, you'll run the getcount
executable (when booted
into xv6), which is based on the program above. Because the program depends on
your implementation, however, it isn't compiled into xv6 by default. When you're
ready to test, you should uncomment the line in the Makefile
following the
UPROGS
declaration that builds getcount
(delete the '#' character in front
of the string "_getcount
").
Note that while getcount
only prints out counts for three different system
calls, your implementation should support all the system calls listed in
syscall.h
. Feel free to add tests to the test program, located in
getcount.c
, for other system calls.
Submitting your assignment is easy. Just make sure that you've committed all
your work to your local repository --- "git commit -am "Your commit message"
"
will do the trick --- and then you can push these commits to your private
repository (shared with me):
$ git push origin master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 292 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
To git@bitbucket.org:michaelee/cs450-summer2015-USERNAME.git
9dce23b..0bdfeac master -> master
If you see output like the above, your commits have been pushed. You might want
to do a "git status
" to make sure you have no uncommited changes and/or aren't
ahead of the remote repository.