Writing a Simple Operating System — from Scratch

(Jeff_L) #1

CHAPTER 5. WRITING, BUILDING, AND LOADING YOUR


KERNEL 53


5.2.3 Finding Our Way into the Kernel


It was definitely a good idea to start with a very simple kernel, but by doing so we
overlooked a potential problem: when we boot the kernel, recklessly we jumped to, and
therefore began execution from, the first instruction of the kernel code; but we saw in
Section XXX how the C compiler can decide to place code and data whereever it chooses
in the output file. Since our simple kernel had a single function, and based on our
previous obsrevations of how the compiler generates machine code, we might assume
that the first machine code instruction is the first instruction of kernel’s entry function,
main, but suppose our kernel code look like that in Figure XXX.


void some_function () {
}

void main() {
char* video_memory = 0xb8000;
*video_memory = ’X’;
// Call some function
some_function ();
}

Now, the compiler will likely precede the instructions of the intended entry function
mainby those ofsomefunction, and since our boot-strapping code will begin execution
blindly from the first instruction, it will hit the firstretinstruction ofsomefunction
and return to the boot sector code without ever having enteredmain. The problem
is, that entering our kernel in the correct place is too dependant upon the ordering of
elemtents (e.g. functions) in our kernel’s source code and upon the whims of the compiler
and linker, so we need to make this more robust.
A trick that many operating systems use to enter the kernel correctly is to write a
very simple assembly routine that is always attached to the start of the kernel machine
code, and whose sole purpose is to call the entry function of the kernel. The reason
assembly is used is because we know exactly how it will be translated in machine code,
and so we can make sure that the first instruction will eventually result in the kernel’s
entry function being reached.
This is a good example of how the linker works, since we haven’t really exploited
this important tool yet. The linker takes object files as inputs, then joins them together,
but resolves any labels to their correct addresses. For example, if one object file has a
piece of code that has a call to a function,somefunction, defined in another object
file, then after the object file’s code has been physically linked together into one file,
the label :code:’somefunction’ will be resolved to the offset of wherever that particular
routine ended up in the combined code.
Figure XXX shows a simple assembly routine for entering the kernel.


; Ensures that we jump straight into the kernel ’s entry function.
[bits 32] ; We’re in protected mode by now , so use 32-bit instructions.
[extern main] ; Declate that we will be referencing the external symbol ’main ’,
; so the linker can substitute the final address
Free download pdf