HW5: Ticked Kernels
A tick-based kernel uses a periodic timer interrupt to manage concurrency and other system events. The periodic timer fires a very low-level interrupt that returns control to the kernel if a process is running, and the kernel can keep track of the number of ticks that have occurred. This basic mechanism simplifies many kernel operations because the system can rely on the interrupt for timeslicing and to ensure the kernel will have an opportunity to run.
While having a periodic tick is very useful, it also has drawbacks. The primary issue is that the interrupt for the tick is continuous, even if nothing is actually using it. For example, if all processes are waiting on an external interrupt and the system could be sleeping, the periodic tick will wake up the kernel which will check that all processes are still blocked and just go back to waiting. On low power systems this becomes a significant waste of energy.
The second issue with a tick-based kernel is it enforces a fixed timeslice for processes. At every tick the system switches to the kernel, interrupting any running process, and then the kernel can decide what to schedule next, including continuing to run the interrupted process. However, this delay can just be wasted time, and the kernel loses flexibility in how long to run a particular process.
In this assignment you will modify a tick-based kernel to make the tick period dynamic and under the kernel’s control. While modifying the kernel to be tickless is outside of the scope of this assignment, you will be able to explore what happens when the ticks are no longer at regular intervals.
xv6 RISC-V Kernel
For this assignment you will use the xv6 kernel for the RISC-V 64 bit architecture. This includes all of the core components of an operating system kernel while still being accessible to hack on.
The kernel runs on QEMU, a CPU emulator.
Your goal is to modify the kernel so that tick period is not fixed (that is you can’t just change the tick period to just a different static value). You should have some policy for how the tick period changes which can be very simple or more complicated. For example, you might consider additively increasing the tick period when all processes are waiting on interrupts and then multiplicatively decrease it if a process exceeds its timeslice.
You are required to include a short description of your adaptive tick interval algorithm.
Part 0: Setup
First you have to install some dependencies.
Clone the xv6 source.
$ git clone https://github.com/mit-pdos/xv6-riscv
Install Instructions Using Package Managers
Install the RISC-V compiler.
$ sudo apt-get install gcc-riscv64-linux-gnu
$ brew tap riscv/riscv $ brew install riscv-tools
$ sudo apt-get install qemu
$ brew install qemu
Install Instructions by Compiling from Source
For the RISC-V compiler:
sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
Get the source:
git clone https://github.com/riscv/riscv-gnu-toolchain cd riscv-gnu-toolchain git submodule update --init --recursive
./configure --prefix=/opt/riscv make
and then add ‘/opt/riscv/bin’ to your path.
For install QEMU compiler:
Get the source:
git clone https://github.com/qemu/qemu cd qemu
./configure --target-list=riscv64-softmmu make -j $(nproc) sudo make install
After that you should be able to run the kernel.
$ cd xv6-riscv $ make qemu
That will load the emulator with the kernel, and by default the kernel loads the
shell process (
sh). This is a basic shell, but should be reasonably familiar.
To exit QEMU you can enter
ctrl+a and then
x to exit QEMU.
To modify the kernel you can directly edit the source and then re-run
qemu. You can also add new applications in the
user folder and then add them
UPROGS variable in the Makefile and they will be available in the
To simplify the project, you should edit the
CPUS variable in the Makefile and
set it to
make clean to ensure the change propagates.
RISC-V is a new risc architecture with substantial documentation available online. Here are some basics which should be useful.
RISC-V cores have three privilege level modes: M (machine), S (supervisor), and
U (user). A specific instruction is used to switch privilege modes. For example,
mret instruction switches from M mode to S mode, and the
switches from S mode to U mode. To go the other way, the
traps to a higher privilege level.
The core boots into M mode. Some initial setup is done, and then the core switches to S mode where the kernel starts running. Of course applications execute in U mode.
The tick in xv6 is implemented using the machine-mode timer and configured
start.c. This is a simple timer which is just a continuously
running clock and single compare register. When the clock value matches the
compare register an interrupt is generated. Since the machine timer operates in
M mode, this interrupt traps the CPU into M mode. The interrupt handler is
specified by the
mtvec register, which is set to the
timervec function in
kernelvec.S file. The
timervec handler adds the tick interval to the
compare register to reset the timer for the next tick event. It then generates a
software interrupt to notify the kernel in S mode that the tick occurred. These
trap events are handled by the kernel in the
The different privilege levels can store state in the
Xscratch register. For
example, machine mode can store 64 bits of state in the
mscratch register. The
xv6 kernel uses the
mscratch register to store a pointer to some state
reserved for the machine timer code. You can see in the
timervec function how
that is used. Note that the interval used for the tick is stored in the array
pointed to by
The xv6 kernel includes the various components you would expect to be in an
operating system kernel. The actual scheduler is in
proc.c. You should be able
to verify that the scheduler by default uses a round-robin scheduler.
The code for handling interrupts and exceptions is in
trap.c. This is, for
example, where the code that handles when a userspace process calls a syscall
and the system traps to the kernel resides, as well as where interrupts are
Userspace applications have a very standard set of syscalls, include
Part 1: Enable the kernel to change the tick interval
You should start by finding a way to change the duration of the tick interval
from the kernel code. That is, changing the value in
is not very helpful since that code operates in M mode and is only called once.
We want to be able to change the tick interval dynamically.
You can test the tick interval has changed by adding a
printf() to the
clockintr() function (where the tick is recorded) and verifying that the tick
occurs at a different rate than before.
Part 2: Implement a dynamic tick interval policy
Building on Part 1, you should now implement a dynamic tick interval policy. This should respond to the operation of processes in the system with the goal of reducing the number of ticks while preventing a process from monopolizing the CPU.
While it will likely take some time to find where to make the changes, a simple algorithm requires only adding about 10 lines of code to the kernel.
You should devise a scheme for verifying that the tick interval is being
dynamically adjusted. Certainly one option is to add a
printf() when the tick
occurs and inspect that the frequency of the print messages changes. You can
also write an application which calls
fork() to create multiple processes that
the OS has to schedule. If one is intermittently CPU bound (i.e. it has periods
of high computation (say
while(1)) and periods of waiting) and the other is IO
bound, both processes should make progress but the tick interval should change.
You should submit two things in a zip file:
The diff of your code in the
xv6-riscv. You can run
git diff > hw5.diffto generate the diff.
A text file with answers to the following two questions:
- What algorithm for adjusting the tick interval did you implement?
- Which syscall does adapting the tick interval most affect? Why?
You should upload your zip or tar file on collab.