This project, developed for the Computer Architecture and Organization 2 course, implements a hypervisor in C using the Linux KVM API. It can run one or more 64-bit virtual machines and provides guest communication through I/O ports, local and shared file operations, interrupt handling, and data exchange through a shared buffer.
- 2 MB, 4 MB, or 8 MB of physical memory per guest;
- paging with either 4 KB or 2 MB pages;
- 64-bit long mode and one virtual CPU per virtual machine;
- support for multiple guests, each running in a separate POSIX thread;
- single-byte serial input and output through I/O port
0xE9; - graceful guest termination using the
hltinstruction; - reporting of unexpected KVM exits and virtual machine errors;
- validation of required command-line options and their values.
- guest API consisting of
open,close,read,write, andlseek; - communication with the hypervisor through I/O port
0x278; - up to 32 simultaneously open files per guest;
- isolated local files for each virtual machine;
- shared files supplied through the
-for--fileoption; - copy-on-write semantics: the first write to a shared file creates a private guest copy, leaving the original file unchanged.
A local file named name.txt, created by guest N, is stored in the working directory as guest_N_name.txt. The same naming convention is used for private copies of shared files.
- communication between multiple virtual machines through a 4096-byte shared buffer;
- interrupt vector 32 for assigning roles and signaling new transfer rounds;
- the first guest acts as the writer, while all remaining guests act as readers;
- data transfer through port
0x510and operation acknowledgements through port0x520; - the writer starts a new round only after all active readers acknowledge the previous one;
- data exceeding
BUFFER_SIZEis accepted only up to the buffer capacity.
host/
├── inc/ # Hypervisor headers
└── src/
├── main.c # Guest threads and the KVM run loop
├── vm.c # VM creation, memory, and paging
├── cli.c # Command-line parsing and validation
├── file_service.c # File protocol and copy-on-write
└── buffer_service.c # Shared buffer and synchronization
guest/
├── inc/ # Guest API, IDT/GDT, and I/O helpers
├── src/
│ ├── main.c # Guest entry point
│ ├── file.c # File operation wrappers
│ ├── buffer.c # Shared-buffer read and write operations
│ └── interrupts.c # Interrupt handlers
├── tests/ # Tests for all three project stages
├── guest.ld # Linker script
└── Makefile
common/protocol.h # Shared ports, operations, and constants
The following interfaces are used for communication between the guests and the host:
| Purpose | Port | Direction |
|---|---|---|
| Serial input/output | 0xE9 |
Guest ↔ host |
| File operations | 0x278 |
Guest ↔ host |
| Shared-buffer role and data | 0x510 |
Guest ↔ hypervisor |
| Operation result and reader acknowledgement | 0x520 |
Guest ↔ hypervisor |
The project is intended to run on an x86-64 Linux system with hardware virtualization enabled.
Required software and system features:
- a Linux kernel with KVM support and an accessible
/dev/kvmdevice; - GCC and GNU binutils (
gccandld); - GNU Make;
- the POSIX threads library.
On Debian or Ubuntu, the required development tools can be installed with:
sudo apt install build-essentialVerify that the current user can access KVM:
test -r /dev/kvm -a -w /dev/kvm && echo "KVM is available"If the current user cannot access /dev/kvm, configure membership in the appropriate system group or run the project in an environment that provides KVM access. WSL without nested virtualization support is not sufficient.
Run the following commands from the repository root:
make -C host
make -C guestThe build produces:
host/build/hypervisor- the hypervisor executable;guest/build/guest.img- the guest binary image.
To remove generated build files:
make -C host clean
make -C guest cleanGeneral command syntax:
./host/build/hypervisor \
--memory <2|4|8> \
--page <4|2> \
--guest <guest1.img> [guest2.img ...] \
[--file <shared1> <shared2> ...]| Option | Short form | Description |
|---|---|---|
--memory |
-m |
Memory allocated to each VM in MB: 2, 4, or 8. |
--page |
-p |
Page size: 4 selects 4 KB pages and 2 selects 2 MB pages. |
--guest |
-g |
One or more guest images; a separate VM is created for each image. |
--file |
-f |
Optional list of files shared with the guests for reading. |
Minimal example:
./host/build/hypervisor -m 2 -p 4 -g guest/build/guest.imgExample with three guests and one shared file:
./host/build/hypervisor \
-m 4 -p 2 \
-g guest/build/guest.img guest/build/guest.img guest/build/guest.img \
-f shared/input.txtAll options except --file are required. The hypervisor terminates with an error if an option is missing, specified more than once, or given an unsupported value.
The guest/inc/file.h header exposes the following interface:
int open(const char *path, int flags);
int close(int fd);
int read(int fd, char *buf, int count);
int write(int fd, const char *buf, int count);
int lseek(int fd, const int offset, int off_flag);Supported flags:
| Flag | Value | Description |
|---|---|---|
O_RD |
1 | Open for reading only. |
O_WR |
2 | Open for writing only. |
O_RDWR |
4 | Open for reading and writing. |
O_CREATE |
8 | Create the file if it does not exist. |
SEEK_SET |
1 | Move to the specified offset from the beginning. |
SEEK_END |
2 | Move to the end of the file. |
A file name must start with a letter and may contain letters, digits, and periods. Guests use file names without directory paths. For shared files, the hypervisor compares this name with the base name of each path supplied through --file.
The guest/tests directory contains tests for basic I/O, error validation, file operations, copy-on-write behavior, and shared-buffer transfers. Build the hypervisor before running the tests:
make -C hostmake -C guest TEST=a_serial TEST_MAIN=1
./host/build/hypervisor -m 2 -p 4 -g guest/build_a_serial/guest.imgmake -C guest TEST=b_local TEST_MAIN=1
rm -f guest_0_localtest.txt
./host/build/hypervisor -m 2 -p 4 -g guest/build_b_local/guest.imgmake -C guest TEST=b_cow TEST_MAIN=1
rm -f guest_0_cow.txt guest_1_cow.txt
./host/build/hypervisor \
-m 2 -p 4 \
-g guest/build_b_cow/guest.img guest/build_b_cow/guest.img \
-f shared/cow.txtAfter the test, shared/cow.txt should remain unchanged, while each guest's modifications should appear in its private copy.
make -C guest TEST=buffer_rounds
./host/build/hypervisor \
-m 2 -p 4 \
-g guest/build_buffer_rounds/guest.img \
guest/build_buffer_rounds/guest.img \
guest/build_buffer_rounds/guest.imgA successful test prints one W, one R for each reader, and terminates with exit status 0.
make -C guest TEST=file_transfer
rm -f guest_1_received.txt guest_2_received.txt
./host/build/hypervisor \
-m 2 -p 4 \
-g guest/build_file_transfer/guest.img \
guest/build_file_transfer/guest.img \
guest/build_file_transfer/guest.img \
-f shared/input.txt
cmp shared/input.txt guest_1_received.txt
cmp shared/input.txt guest_2_received.txtThe expected output contains one FILE SEND COMPLETE message for the writer and one FILE RECEIVE COMPLETE message for each reader. Both cmp commands should complete without reporting any differences.
Additional scenarios and commands are documented in guest/tests/testovi.txt and guest/tests/a_cli.txt.
- The order of images following
--guestdetermines each guest's index; guest 0 is assigned the writer role. - Run the program from the repository root so that relative paths and generated local files remain predictable.
- A
KVM_EXIT_HLTmessage represents the expected termination of a virtual machine. - Files inside the
buildandbuild_*directories are build artifacts and can be regenerated from the source code.