Contents
Changelog:
- 20 Jan 2022: edit instructions to be more optimistic/specific about using OS X and mention WSL
- 24 Jan 2022: add considerably more detailed OS X instructions
- 25 Jan 2022: add note about
make clean
in OS X instructions and Linux cross-compiling instructions - 26 Jan 2022: add pointer to list of “files in xv6” to hints.
- 31 Jan 2022: add note regarding
qemu01
- 2 Feb 2022: modify note regarding
qemu01
to have special instructions regarding getting a new compiler and its compatiblity
Your task
- Obtain a copy of an environment with:
- a working C and C++ compiler that supports 32-bit x86 and produces ELF (Linux’s object, library, and executable format) binaries; and
- a recent copy of qemu installed (configured to support full 32-bit x86 emulation — you should have a
qemu-system-i386
command, which can be installed on Ubuntu-like systems with something likesudo apt install qemu-system-x86
)
We have supplied a Linux-based VM with a suitable environment or instructions for setting up a similar Linux environment. Alternately, you can login to the department server
qemu01
via SSH from portal.cs.virginia.edu or NoMachine. (See also instructions for access NoMachine/SSH.) The default version ofgcc
onqemu01
is pretty old; you can obtain a more recent version by runningsource /opt/rh/devtoolset-11/enable
(you’ll need to rerun this command every time you login). Note that you will need to make sure you have a version of xv6 from after 2 Feb 2022 (you cangit clone
again or update an existing copy withgit pull
).We are more limited in our ability to provide tech support for other environments, but students have also had success getting things working:
- natively on OS X; see instructions below
- using the Windows Subsystem for Linux.
- using a non-x86-based Linux machine: requires a cross-compiler, see instructions below.
-
Download and get xv6 to boot in an emulated machine. For how to do this, see the instructions below. Run it and make sure you can run some simple commands in a shell in the emulated machine.
-
Add a new system call
int writecount()
that takes no arguments and returns the number of times thewrite
system call has been called across all processes. See below for hints about how to do this.We do not care how your
writecount()
system call counts calls towrite
that fail, such as from having invalid arguments. We also don’t care how it treats simulataneous calls to write() and/or writecount(). -
Add a new system call
int setwritecount(int new_count)
that takes one integer argument, resets the count returned by thewritecount
call tonew_count
, and returns the value “0” when successful. The count should continue incrementing from any calls towrite()
made afterwards. -
Write a program that uses and tests these new system calls. See below for hints on how to do this.
- Run
make submit
to create a.tar.gz
archive and upload it to the submission site.
Hints
Getting xv6 running
-
Download our version of xv6 using git:
git clone https://github.com/uva-reiss-cs4414/xv6.git
-
In the newly created
xv6
directory, runmake
. -
Boot xv6 in an emulator using one of the following methods:
-
To boot the OS in an emulator with a graphical user interface, run
make qemu
. -
To boot the OS in an emulator without a graphical user interface, run
make qemu-nox
. (nox
probably standards for “no X”; X is short for the X windowing system, the primary graphics system on Unix-like operating systems.)
You can shutdown the system using the shutdown command or using the key sequence: control-a, then x.
-
Adding the system call
For this task, you might want to read
-
the list of files in xv6 and their purpose on our xv6 overview page
-
Chapter 3 of the xv6 book (PDF) describing how exceptions, traps, interrupts, etc. See also note on terminology below. (The book refers to line numbers in this print out of the xv6 source code.)
-
how system call dispatching is implemented in
syscall.c
andsyscall.h
, and how the handlers for individual system calls are implemented insysproc.c
andsysfile.c
(for system calls that deal with files, like read() and write()).
Basic steps for adding the system calls:
-
Create a
sys_writecount
function (orsys_setwritecount
) based on an existing simple system call function likesys_uptime
. (For this assignment, you do not need to (but are allowed to) use a spinlock andacquire
orrelease
likeuptime
does since we do not care how your code works with multiple processors.)The convention in xv6 is that file-related system calls are in
sysfile.c
and process-related system calls are insysproc.c
. (I would considersys_writecount
more of a file-related system call.) But this is just a convention, and you can place the system call implementation in any.c
file which is part of the kernel. -
Add a system call number for your new system call to
syscall.h
. -
Add your
sys_writecount
to the table insyscall.c
. -
Edit
sys_write
. to update a counter read bysys_writecount
. You can use a command likegrep sys_write *.c
to find out where it is. -
Edit
usys.S
anduser.h
to create a system call wrapper function that invokes your system call from a normal user program. -
Follow the same procedure for
setwritecount
, but to get an argument use theargint
function. You can look atsys_kill
for an example of how this is used. The first argument toargint
is the index of the argument (0
for the first argument,1
for the second, and so on.)Notice that though a system call like
kill
takes an argument that can be retrieved withargint
, the prototype of the correspondingsys_*
function is the same as for a system call that takes no argument.
Testing your system call
-
Using
echo.c
and/orkill.c
as a template, create a new program to run your writecount system call and print the results. -
Edit
Makefile
by adding your program toUPROGS
, similar to howecho
andkill
are included on this list. -
Run
make
and thenmake qemu
to boot the OS with your new program included. -
If your test program crashes after finishing, you may have forgotten to
exit()
at the end. (Returning frommain()
will not work.) -
You could run other programs that call
write()
(e.g. by outputting anything to the console) and/or have your test program make writes (using thewrite()
system call wrapping function directly or by taking advantage ofprintf()
calling write) to verify that the count make sense.
Note on printf
implementation
- The built-in xv6
printf()
implementation, whose source code is inprintf.c
, is implemented by callingwrite()
, but it often calls write more than one time per call toprintf()
.
Locks not required
-
sys_uptime
uses a spinlock, which it uses to handle the case where xv6 is running on multiple processors or when the system call is interrupted by a timer interrupt. For this assignment, we do not care if you handle this problem. (We will ensure only one process is callingwrite()
at a time when testing.) -
An implementation of
writecount
that correctly handled multiple concurrent calls towrite()
and/orwritecount()
would probably use a spinlock around accesses to the counter (from bothwrite
andwritecount
).
Debugging
-
In the kernel, you can use
cprintf()
to output messages to the console. This is my primary mechanism for debugging. Outside the kernel you can useprintf()
to output debug messages. -
You can run xv6 under debugger
gdb
using the instructions below. -
The xv6 kernel contains many situations where it asserts that some condition that should always be true is true, and if not, it deliberately crashes the system in what is called a panic. For example,
trap.c
contains code that panics when an unexpected type of exception occurs while code is running in kernel mode, such as the equivalent of a segmentation fault in kernel code. When this happens, xv6 prints out a message like the following:lapicid 0: panic: trap 80483231 80d48a34 80033423 0 0 0 0 0 0 0
in this message:
0
is the number of the CPU that was running when the panic occured. Since we always run xv6 in single-core mode, this will generally be 0.trap
is the message that was passed to the panic function. Usually, thexv6
code is written so that there is only one call to panic with a particular message, so this will precisely identify where the panic occured.8048323
,80d48a34
, etc. are the hexadecimal addresses of the code that was running when thepanic()
was called. You can look up these addresses inkernel.asm
, which contains the full assembly of the xv6 kernel, interleaved with its machine code, the addresses of each instruction, and the corresponding C code.
xv6 book: note on terminology
CS 3330 and the xv6 book and source code use slightly different terminology related to exceptions:
CS 3330 Term | most common xv6 term | description |
---|---|---|
exception | trap | any event in which the processor transfers control from whatever program was running to the OS (to the “kernel”) at a location chosen by the OS |
fault | exception | a program performs an illegal action, causing control to be transferred to the OS to decide what to do |
interrupt | interrupt | an event external to the program, such as a timer or an input/output device needs the OS’s attention |
trap | (specific kind) trap | an exception deliberately triggered by an instruction; on x86 this is via the int instruction |
a note on GDB
- It is possible to use GDB with xv6. To do this instead of running
make qemu
ormake qemu-nox
, runmake qemu-gdb
ormake qemu-nox-gdb
respectively. Then, in another window, rungdb
from the xv6 directory, and then run the commandsource .gdbinit
within GDB. Then, within GDB, you can set breakpoints withbreak function_name
(and various similar commands) and start execution of the OS withcontinue
.
a note on OS X
Because xv6 expects ELF format binaries, xv6 will require a cross-compiler on OS X in addition to a copy of qemu.
Students have had success obtaining these by installing these through the Homebrew packages i686-elf-gcc
and qemu
. To do this:
-
Install Homebrew following the instructions on its website.
-
Make sure to add Homebrew’s tools to your PATH. The official installer will print out instructions for this which if your username is
xxx
and you are using an M1 Mac would probably look something like:echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/xxx/.zprofile eval "$(/opt/homebrew/bin/brew shellenv)"
and if you were using an Intel Mac would probably look like:
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> /Users/xxx/.zprofile eval "$(/usr/local/bin/brew shellenv)"
(where
.zprofile
would likely be replaced with.bash_profile
on OS X 10.14 and earlier).
-
-
Run
brew install i686-elf-gcc
andbrew install qemu
After having these installed, one needs to tell the Makefile to use i686-elf-gcc
instead of the normal gcc
by providing
the argument TOOLPREFIX=i686-elf-
. So one would run make TOOLPREFIX=i686-elf-
instead of make
and
make TOOLPREFIX=i686-elf- qemu-nox
instead of make qemu-nox
.
Note that if you’ve run make
before without the TOOLPREFIX
option, you may need to run make clean
to remove files that were produced incorrectly.
To avoid needing to type TOOLPREFIX=...
every time, one could also modify the Makefile
to add this setting.
Unfortunately, the course staff are not able to test things under OS X generally, so we are limited in what help we can provide if you experience problems.
a note on non-x86-based Linux
If you are using Linux on a non-x86 based machine, such as on ARM, then you will need a cross-compiler that produces 32-bit x86 binaries.
On Ubuntu, this can be installed using sudo apt install gcc-i686-linux-gnu
.
After having this installed, one needs to tell the Makefile to use i686-linux-gnu-gcc
instead of the normal gcc
by providing
the argument TOOLPREFIX=i686-elf-
. So one would run make TOOLPREFIX=i686-linux-gnu-
instead of make
and
make TOOLPREFIX=i686-linux-gnu- qemu-nox
instead of make qemu-nox
.
Note that if you’ve run make
before without the TOOLPREFIX
option, you may need to run make clean
to remove files that were produced incorrectly.
To avoid needing to type TOOLPREFIX=...
every time, one could also modify the Makefile
to add this setting.
Note that you will also need a normal (non-cross) compiler, such as you can install on Ubuntu using sudo apt install build-essential
.
Credit
This assignment is based loosely on Arpaci-Dusseau’s initial-xv6 assignment.