-- Leo's gemini proxy

-- Connecting to posixcafe.org:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

9front Bare Bones Kernel


I have recently been interested in reading and understanding the processes of

kernel development. In that effort I have been spending some time reading the

OSDev wiki as well as some fantastic blog on writing a kernel in rust

However I quickly ran in to two problems:


The OSDev wiki is quite outdated at this point, with many getting started points using i386.

The rust blog seemed to abstract a lot of the 'ugly' parts away (for practical cases this is great, but it pays to look at the ugly stuff when learning).


As such I thought it might be worth while to take a peek on getting a barebones kernel

setup using the common tools that are available on the OS that I do most of my

development in, 9front. As such I set out to first learn how 9front manages its kernel, and then see if I could strip out just the minimum to get myself a little "hello world".


Knowing where to look


Let's start by looking at how 9front organizes it's kernel code. All of the kernels are located in '/sys/src/9/$objtype/' with 'port' referring to portable code between them. For our purposes we're only going to look at the amd64 kernel. There are three files that are good to look at first


l.s: contains our assembly bootstrap and entrypoint from the bootloader

main.c: contains our C entrypoint that is called from l.s once we have gotten a stack and switched over to 64 bit long mode.

mkfile: while normally not very interesting, we should take a look to see how we are linking the kernel together.


Also worth noting is a couple additional directories:


/sys/src/boot: contains 9boot bootloader.

/sys/lib/dist/mkfile: script for making a bootable cdrom.


Start putting stuff together


Copying over the 'l.s' file we see tons of stuff that we wont need, so lets trim it down a bit. Reading it quickly we find that we call our main funciton in '_start64v', so let's delete everything after that. We also can see that 'l.s' requires a 'mem.h' so let's grab that as well. Then let's write our own very tiny 'kern.c' with a 'void main(void)' entry point. For now simply enterying a infinite loop will suffice.


#include <u.h>

u32int MemMin; //Filled by l.s, thus the symbol must be somewhere

void
main(void)
{
	for(;;);
}

Now let's get each of these compiled/assembled.


; 6c kern.c && 6a l.s

Now if we check the 9pc64 mkfile for how to link them we see something a bit out of the norm. First we see a 'KTZERO' variable declared and then it being passed to the linker through the -T flag.


Looking at the man page for the linker, we see that the -T flag tells the linker where to start placing the '.TEXT' section for the binary. To understand why this is needed, let's remind ourselves of what goes on in the average boot(in relation to our kernel).


9boot starts

9boot throws us into 32 bit protected mode

9boot finds our kernel listed in the plan9.ini

9boot reads the kernel and throws it into memory

Longjump in to the entry point of the kernel as defined by the multi boot header.


When we first get to our kernel we have not set up virtual memory, so our first sets of jumps

and addressing must use the physical addresses. Looking at 'mem.h' we can see that KZERO (kernel zero) is set to '0xffffffff80000000', so this must be where 9boot puts the start of our kernel binary. However, the start of the binary is not the start of executable code, that would be the '.TEXT' section. So we must have a common definition of where our executable code starts between our linker and our 'l.s' code. This allows 'l.s' to tell 9boot where _exactly_ in physical memory to jump to.


To acomplish this we pick a common starting point, define it in our source code, and make sure to pass it to the linker so things lign up. So now that we know what is going on, let's link our kernel:


6l -o kern -T0xffffffff80110000 -l l.6 kern.6


It's worth noting that 'l.6' must come first or else our dance to get the '.TEXT' section aligned will be pointless, as 'kern.6' will be placed first in to the section.


Now let's verify that we indeed set things up right by using file(1). The output should look like:


kern: amd64 plan 9 boot image


Booting our new kernel


We have one more step before we can actually get our fresh kernel booted in something like QEMU. We need to create a cdrom iso image that contains both our kernel as well as 9boot. For this we will take a look at the existing script for iso generation on 9front: /sys/lib/dist/mkfile.


Lets start by creating a new plan9.ini for 9boot to point to our new kernel:


echo 'bootfile=/amd64/9pc64' > plan9.ini


We'll also want a local copy of /sys/lib/sysconfig/proto/9bootproto so that we can add our kernel path to it.


Now that we have those, let's create our iso using disk/mk9660 like so:


; @{rfork n
	# Setup our root
	bind /root /n/src9
	bind plan9.ini /n/src9/cfg/plan9.ini
	bind kern /n/src9/amd64/9pc64
	disk/mk9660 -c9j -B 386/9bootiso \
		-p 9bootproto \
		-s /n/src9 -v 'Plan 9 BareBones' kern.iso
	}

With that, you should have a bootable iso image fit for use in something like QEMU.


Links


OSDev Wiki

Writing an OS in Rust

Barebones9 source code

-- Response ended

-- Page fetched on Mon May 27 18:20:21 2024