SIGOPS
Compiling the Kernel

the first part of the first chapter in our series on How to Write an Operating System



The Entry Point

In applications programming, the main() function is usually described as the first function to receive control. However, the real entry is a function called _start(), and is typically defined by crt1.o. This _start() sets up the environment, opens the default descriptors, and then calls the application's main() function. But for an operating system, there isn't any crt1.o (at least, not yet), and so you will need to supply your own _start function as the entry point, instead of main().

In the the first loaded file (which should be a statically linked executable), our boot loader expects a declaration of _start as follows:

void _start(int memSize,  /* total amount of memory 
                             installed in bytes */
            char *parms,  /* ASCII string typed 
                             at the boot prompt */
            struct boot_dir *loadedfiles);


The last parameter, loadedfiles, is struct of type boot_dir which is a structure that contains an array of "file" objects that contain pointers to the individual files. A description of the bootloader system is a must look at to get the os properly booted.

You need to compile your kernel into an ELF executable format. The raw kernel file will always be loaded at 0x100000 (1MB), so the text and data segment locations in the file need to coincide with their locations in memory. For ELF, specifying the options '-dN -Ttext 0x100080' to ld will assign space to common symbols in the file, merge the text and data segments, and start the text segment immediately after the header (which is where it is in the file anyway). For the new uBoot system, the entry point needs to be at 0x101080 because the first 4K is taken by the boot directory entry Note that the linker does not have to create the output binary in this way; however, for reasons of simplicity, the boot loader does not actually interpret the kernel binary.



Exiting the Kernel

The easiest way to return to DOS is simply to leave the _start() function. However, this may not be possible in an exception handler before you have context switching. So, an alternative method (and also a very messy one) is to save the frame pointer (%ebp) in the _start() function to a global variable, and then restore that value plus 4 to the stack register (%esp), immediately followed by a return. This looks like this:

  • To save %ebp (must be in the _start function):
    asm ("mov %%ebp, %0":"=m" (oldEBP));
    
  • To return to the bootloader:
    asm ("mov %0, %%esp; ret": :"r" (oldEBP + 4));
    
where oldEBP is declared as a global unsigned integer.



Compiling the Kernel

The Makefile that we've provided will compile and link the kernel.



Next Section
"Hello World" OS