xv6 machine problem 1: Adding a system call

Objectives

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!

Obtaining the repository

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.

Running xv6

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

Demo

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

Working on xv6

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.

Adding a system call

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

Hints

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

Testing

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.

Submission

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.