MiniOS
In this project, you will extend a small operating system to set up a simple system call and
initialize a simple page table. You are provided with the code of a tiny operating system, which is
not based on any other OS (e.g., Linux). This OS does not include any interrupt or trap handlers,
except minimal system call support.
The system consists of three pieces:
1. The boot loader (boot.efi), which loads the OS kernel and a user application into
memory; you are already given a binary for the boot loader to simplify the build process
2. The OS kernel (kernel_x86_64), a piece of software that runs in the privileged mode
(ring 0)
3. A simple user application (user_x86_64), a piece of software that runs in the unprivileged
mode (ring 3).
The OS kernel supports a video frame buffer which uses 800x600 (RGB32) video mode. On top
of the video frame buffer console, the system implements text output functions such as printf,
which is analogous to the user-space equivalents for the most part but used inside the OS kernel.
Environment:
OS: Ubuntu 22.04 Desktop x86-64
CPU Architecture: x86_64
run:
sudo apt update
sudo apt install make gcc binutils
sudo apt install git
sudo apt install ovmf qemu-system-x86
You can also install additional packages that can be useful for you (e.g., vim, emacs, or any other
editor).
Provided Code:
The provided code consists of the “kernel” and “user” parts. The “user” part makes system calls
to print some messages. You will only need to modify the “kernel” part. You are ONLY allowed
to change kernel_code.c and kernel_extra.c inside “kernel”. Everything else will be discarded for
grading.
The boot loader allocates some memory (around 32 MB), which will be used inside our kernel.
Part of this memory is used for the heap (see the extra credit assignment). But the remaining part
can be used to initialize the page table. You can assume that the memory base address is already
page-aligned.
2
Your Coding Part:
In kernel_code.c, there are two functions that you need implement:
1. kernel_init. It passes addresses to the user space stack, user program (code and data), and
memory that can be used to initialize the page table. Initially, user stack and program will
reside in kernel space. Our system makes sure that it will still work even if you do not
relocate those into user space. However, your eventual goal (Q4) is to move those
addresses in a dedicated user space portion of the address space. To load the page table,
you need to use load_page_table. This is a special function, which checks your page
table (and returns a string with an error message if it fails) and then loads it to the CR3
register which is used to specify the top level (root) page table address in x86-64.
2. syscall_entry. This is the entry point for all system calls. You need to implement one
specific system call (n = 1) and return an error status (-1) in all other cases. Note that the
mechanism to route system calls from user space is already implemented, you only need
to wire it to the corresponding handler inside this function.
In kernel_extra.c, you need to write your memory allocator code.
To compile and test the OS code, you need to run:
make clean
make
make test
Output:
3
Q2: System calls [40 Points]
You need to modify syscall_entry and implement one specific system call (n = 1) to print a
message on the screen using ‘printf’. The return status of this call should be 0. You will return an
error status (-1) in all other cases. Note that the mechanism to route system calls from user space
is already implemented in kernel_asm.S, you only need to wire it to the corresponding handler
inside syscall_entry in kernel_code.c.
Remember that you are allowed to change kernel_code.c ONLY.
Q3: Page table [60 Points]
The boot loader already sets up the default page table, but you need to create your own x86-64
page table (this can be done fully in C). This page table will provide identical virtual-to-physical
mappings for the first 4 GB of physical memory, and you have to use 4 KB pages. All pages are
assumed to be used for the privileged (ring 0) kernel mode. Although your memory size does not
exceed 1 GB, we are asking you to map 4 GB because the frame buffer’s video memory (used by
‘printf’) is typically located somewhere in the region of 3-4 GB. You can ignore the fact that
some mappings will point to non-existent physical pages in this 4 GB region.
The code provided in kernel_init will already call a special load_page_table function,
which will verify your page table and load it into the CR3 hardware register. You need to
initialize the page table before this function is called.
Remember that all entries of bitfields for page tables must be initialized, including those that are
not currently used. (You should specify 0 for any reserved bits.) It may be more convenient (and
less error prone), if you simply use 64-bit integers (uint64_t) and set corresponding bits yourself
(i.e., avoiding bitfields completely).
For simplicity, we will only allow two bits: the ‘present’ bit and the ‘writable’ bit, both of which
must be set to 1 in the page table. Please do not attempt to use any other bits (except the ‘user’
bit in the next question)! Note that load_page_table will fail if you set any other bits. You only
need to consider the “long” (64-bit) mode and 4 KB pages. Our page table will only use
traditional 4 levels!
Note that even though user applications are only allowed to be executed in user space, the
provided load_page_table function will take care of that problem as long as you do not modify
user_stack and user_program. For now, you are only asked to create kernel-space mappings.
You will modify user_stack and user_program in the next question.
You are allowed to change kernel_code.c ONLY. Do not attempt to “trick” the operating system
to get points without actually implementing the page table.
4
Q4: User space support for the page table [30 pts]
You should extend the page table for the application so that you can also properly execute
application code from user space. The page table should still point to the kernel portion (i.e., the
first entry of PML4). However, this page table will also refer to the pages that are assigned to the
user application itself (the user program and the user stack). Note that the kernel portion should
only be accessible in kernel space, whereas the application portion is accessible in both user and
kernel space. Also note that both user_program and user_stack must point to user-space (rather
than kernel-space) virtual addresses. Initially, they point to the kernel-space virtual addresses
(somewhere in the first 4 GB).
You are allowed to assign any virtual addresses from the bottom 2 MB for the application
(i.e., 0xFFFFFFFFFFE00000 to 0xFFFFFFFFFFFFFFFF). x86-64 sign extends 48-bit virtual
addresses (from the page table) to 64-bit virtual addresses. You can assume that the program
occupies just 1 page, and the user stack is also just 1 page. The stack grows downwards, thus the
initial user_stack address points to the next page (i.e., stack_memory_base + 4096).
Consequently, you need to get the physical page address correctly and map that physical page to
the new virtual address. Finally, the new virtual address that you will write into user_stack
should also point to the next virtual page (i.e., stack_memory_base + 4096) since the stack
grows downwards. Note that user_program will still point to a “normal” base address, but you
also need to map the corresponding page and write the user-space virtual address.
As shown in Figure below, the user-space mappings correspond to the last entry of PML4 (Level
4), last entry of PDP (Level 3), and the last entry of PD (Level 2). You can use any entries in PT
(Level 1). Please make sure to set up correct permissions for these new mappings. The userspace portion can be accessed in both modes, and the kernel-space portion can only be accessed
in kernel/privileged mode.
5
You are allowed to change kernel_code.c ONLY. Do not attempt to “trick” the operating system
to get points without actually implementing the user-space portion of the page table.
Extra Credit:
Integrate the memory allocator implementation into kernel_extra.c. Remember that you are NOT
allowed to use any standard header files. The template in kernel_extra.c already includes some
headers that you will likely need. You need to provide mm_init, malloc, and free. The MiniOS
kernel will do some tests on your memory allocator.
You are allowed to change kernel_extra.c ONLY. Do not attempt to “trick” the operating system
to get points without actually implementing the extra-credit part.