Your next step towards isolation requires you to separate the applications from the kernel: They will be built independently from the kernel, with system calls being the only shared interface.
Extract the applications (located in the
user folder) from the kernel into a separate path and adapt the build system accordingly. You will need a linker script defining the final structure of the executable file. Apart from the start address (64 MiB), this script should be quite similar to the kernel's linker script (
Furthermore, build a static library (called
libsys) containing the basic C/C++ runtime system, the system call stubs and – if desired – additional helper utilities (like string, vector or even a dynamic memory allocator). To execute the constructors of global objects, every application needs some magic initialization code, which can also be adapted from the kernel (have a look at the files
libcxx.cc in the kernels
With the help of this library, every application can now be compiled on its own, without having to be linked against the kernel or requiring parts of it to be
Flat Binaries (5 ECTS)
objcopy utility, you can then convert the resulting ELF binaries into so-called flat binaries: complete memory images of the application that can be executed directly (without a loader).
- Make sure to unroll the
All applications should be packed into an initial ramdisk provided with a header containing the number of application binaries and their sizes. This task is done by the Image Builder for you. However, you have to implement the kernel part: reading the ramdisk (via Multiboot::Module) at runtime and creating a thread for each application with the flat binary (memory image) mapped to user space.
- If you enable
boot/multiboot/config.inc), you'll be able to directly map a flat binary into a thread's user land, without the need to copy it.
TARed ELFs (7.5 ECTS)
Although writing a loader for binaries in the Executable and Linking Format may sound like a major undertaking at first, supporting its basic functionality is actually rather easy: You only need to support static binaries – without shared libraries or relocations. Therefore, you can safely ignore the section header table and just focus on the
load entries of the program header table. For your convenience, you have already been provided with a structure for parsing ELF files, however, the loader still needs to be implemented.
- The memory size can intentionally exceed the file size of an entry. Handle this case according to the specification.
Please make sure that the
executableflags are taken into account and the access rights of the pages are configured accordingly.
The binaries should be packed using Tar; you are also supplied with a parser for this format. The tar file will be loaded as inital ramdisk, which can be accessed in the kernel with Multiboot::Module.
- Executable and Linking Format (ELF)
- Tape Archive (TAR)
Dynamically growing Stack
Instead of begin pre-allocated, the user space stack should be allocated dynamically and grow, according to its demand, up to a maximum value (e.g., 1 MiB). This does not require any modifications in the user space but the kernel needs a page fault handler, has to detect if the failing address is a valid user stack address, and allocate a page frame accordingly.
- Modifications of the current page table requires flushing the affected pages out of the Translation Lookaside Buffer (TLB), for example by using the
asm volatile("invlpg (%0)" : : "r"(address) : "memory");
The Intel manual offers helpful information regarding this topic in Section 4.7 Page Fault Exceptions.
- For the sake of simplicity, the page fault handler should not be an extension to the interrupt_handler but a new entry function registered using IDT::handle() .
- MPStuBS must prevent concurrent access to the page frame allocator!
FPU/MMX/SSE in User Space (Optional)
Neither the Floating Point Unit (FPU) nor the Multi Media Extension (MMX) / Streaming SIMD Extensions (SSE) are used in the kernel, however, they might be useful for user space applications. Since they use additional registers, enabling them requires changing the compiler flags in the build system and support in the kernel: On each process switch, the state of the current thread has to be saved (using FPU::State::save()) and restored for the subsequent thread (FPU::State::restore()).
- The FPU State requires a 16 byte alignment!