Our next major implementation task is to load and run an executable. We’ll approach this in three steps:
We’ll discuss steps 1 and 2 in class, working from the code available on this page. Once we’ve done that, we’ll spend time figuring out a general approach to step 3.
Our kernel is going to load and run a program named init.
Download and extract the init.tar.gz file to get the provided code for this executable.
Once you’ve extracted the archive you should have an init directory;
move this directory into your kernel project (so it sits at the same level as the kernel and limine directories).
If you open a terminal and change into the init directory you should be able to run make to build it.
If that fails for any reason please ask for help!
We want init to build as part of our full build of the os project, so we’re going to have to modify the top-level Makefile as well.
First, we’ll create a target in the top-level Makefile that recursively builds in the init directory.
Add this below the similar rule for kernel:
.PHONY: init
init:
$(MAKE) -C init
We also want our clean target to recursively clean the init build as well.
You already have a clean target, but you should update it so it looks like this:
.PHONY: clean
clean:
rm -f iso_root boot.iso
$(MAKE) -C kernel clean
$(MAKE) -C init clean
Finally, we need to update the boot.iso target so it depends on the init program.
The first line of that target lists dependencies;
add init to the list so the full line looks like this:
boot.iso: limine kernel init limine.cfg
Now when you run make at the top level you should build the init program along with everything else.
Run make clean, verify that init/init is gone, then run make, and verify that init/init exists before moving on.
Our bootloader has support for kernel modules, which are really just files that the bootloader copies into memory along with our kernel.
We’re going to edit the bootloader configuration in limine.cfg to include our init program as a kernel module, and then access that module using the bootloader’s modules structure tag.
First, we need to put the init program on the disk image that our OS boots from.
To do this, modify the cp command that runs under the boot.iso target in your top-level Makefile.
Just add init/init right after kernel/kernel.elf on that line so the full line looks like this:
cp kernel/kernel.elf init/init limine.cfg limine/limine.sys limine/limine-cd.bin limine/limine-eltorito-efi.bin iso_root/
All that change does is copy the init program into a temporary directory that is used to create the CD image.
Next up, we’ll need to update the bootloader configuration to tell it to load init as a module.
Add these lines to the end of limine.cfg:
# Load the init program as a module
MODULE_PATH=boot:///init
MODULE_STRING=init
Finally, you’ll need to update your boot.c file to find the STIVALE2_STRUCT_TAG_MODULES_ID tag and locate the loaded module file.
For now you can just print out the module string and its start and end addresses:
Modules:
init 0xffff80007f3f3000-0xffff80007f5f32f0
We discussed the procedure for loading an ELF file in class. At a high leve, the process for loading an ELF file is:
begin address, which we can cast to an ELF header pointer.LOAD and a size greater than zero:
A. Use vm_map to map the page(s) necessary to hold the program header at the requested virtual address.
B. Use memcpy to copy data from the ELF file (beginning of file + offset) to this virtual address.
C. Update the permissions of the mapped memory to reflect the permissions specified in the program header table. You’ll need to map memory as writable at first, but you should use vm_protect to make it read-only and/or executable as requested.entry address (stored in the ELF header) to a function pointer and call it.