Project Overview
For this project, you will build a small hypervisor using the KVM API.
I'm not going to give you much direction here, just a simple specification. You can build the hypervisor using whatever language you like,
but you'll want something that has bindings to KVM, e.g. C, C++, Rust, or something which would make it easy to add such bindings.
You'll want to start by going through the LWN tutorial on getting started with
KVM. If you're going to work on a VM, you'll want to make sure you have KVM installed (including the development headers) and you'll want to make sure that nested virtualization is enabled in your VM manager. For example, VMware calls this option "enable hypervisor applications."
VMM Requirements
- Your VMM will be designed for x86-64, and will run on Linux using KVM
- Your VMM must take a binary file as a command-line argument. This binary file will encapsulate some code (for a VM). This code will
be relatively simple for you, but could eventually evolve into an OS kernel. Your VMM must be able to load this binary into guest
memory and run it. To get started it will be easier to use a raw binary, but if you make significant progress, you may want to
take a look at libelf to be able to parse and load ELF binaries. This would be a necessary step to getting an OS running.
- Your VMM should include a very simple line-buffered console device. It will have a very basic interface to the guest. If the guest
writes a printable byte to IO port 0x42, that byte will be saved in a buffer. If a newline character is written, the buffer will
be printed to the screen and the buffer will be flushed.
- Your VMM must include a virtual keyboard device. This does not have
to be a full keyboard device, just a few keys are fine. Your VMM
will accept input one byte at a time from STDIN and set an 8-bit
register (IO port 0x44) to a value corresponding to the key pressed. It will notify
the guest that there was a keypress by setting a status register (IO port 0x45) to 1. When the guest
detects that a key has been pressed, it must ack the event by clearing the status register.
- Your VMM will include a virtual interval timer. The guest will write a time value (call it N) in milliseconds to
IO port 0x46 and will enable the timer by writing a 1 to bit 0 of IO port 0x47. The timer is disabled by clearing
the same bit. While the timer is enabled, it will periodically notify the guest on timer events by setting bit 1
on IO port 0x47. The guest must again ack and clear this "timer fired" bit when it notices the event. If the
guest has not ack'd a previous timer event when a new one is fired, the event is lost.
You may want to look at the GC timing code in Hawkbeans for inspiration here. You can also take a look at the
Linux timer subsystem (e.g.
timer_create()
).
- Your guest will initially be a simple binary, but you will have to extend it to include a console and keyboard driver.
The keyboard and console drivers will operate according to the hardware spec above.
- You'll probably have noticed at this point that we haven't involved
interrupts. To make our VMM and guest simpler, we're going to use
polled I/O. This means that instead of receiving interrupts, the
guest will poll on device registers to check for events. Your guest
must thus enter an event loop, checking every device's status
register (keyboard and timer) to see if an event must be handled.
This will save you the pain of dealing with KVM's IRQCHIP logic and
interrupt routing, and setting up an IDT and interrupt handlers in
your guest.
-
Once you've got this all in place, your guest will accept some input from the keyboard, and when the user
hits enter, the guest will then echo that line to the console on every timer event, until a new line
is entered.
Tips
You probably are not going to want to hand assemble code for your guest (as in the KVM example linked above). Here
is a gcc invocation that might help:
$> as --32 -o smallkern.o smallkern.S
$> ld -m elf_i386 -Ttext 0x7c00 --oformat binary -o smallkern smallkern.o
This will compile and link your x86 assembly into a flat binary (not ELF). This assumes that
the code will be loaded at address
0x7c00
, but you will obviously want to change this. It's
not hard to extend this to work with C code, but you'll want to use the compiler flags
-ffreestanding
,
-nostdlib
, and
-m32
.
Next steps
If you've gotten this far and you're looking to make your VMM/guest more interesting, here are some tips:
- Figure out how to set up interrupts in KVM (see KVM's IRQCHIP calls, and irqfd functionality)
- Implement a virtual VGA device, e.g. a simple framebuffer console. See here.
- Enhance your guest by allowing it to boot into protected and then long (64-bit) mode. You'll have to deal with
setting up paging and setting up the GDT to acheive this. You can also set up the IDT and implement interrupt
handlers. At this point you'll have a little OS kernel of your own.
- Implement virtio for your guest (paravirtualized network, disk, etc.) and VMM. This will allow your guest
to use networking/storage.
- Take a look at Amazon's Firecracker or if you're
ambitious QEMU for more ideas
Hand-In
You will hand in your code to me by sending me your code in tarball form (email is fine). You
must include a Makefile and a README which describes how to build and run
a VM in your hypervisor.