Featured Post

writing your own OS using Gnu and assembler , linker


The boot process

Before we start writing code, first take a small look at the boot process. From the point of view of the kernel, this section could have the title "what happened so far".
In the case of a CPU reset (which occurs, for example, when switching on), the entire processor state is reset. In particular, the processor switches to the real mode and jumps to the linear address 0xffff0 (eg cs: ip = f000: fff0). At this address is the BIOS. The BIOS initializes the hardware, performs a self-test, and when it is finished, it triggers an interrupt 0x19. The handler loads the boot sector from the hard disk or disk to linear 0x7c00 (eg 07c0: 0000) and jumps to this address.
The boot loader is then responsible for loading the kernel of the operating system into the memory, setting up a suitable environment and jumping to the entry point of the kernel. To prepare the area include the change in the appropriate processor mode (in our case of 32-bit protected mode of x86) and the transfer of certain information to the kernel (eg on the available memory, a kernel command line, etc.)
As soon as the bootloader has jumped to the entry point of the kernel, it will give the control completely to the kernel. It is then no longer needed and its memory can be used elsewhere.

Multiboot

For our operating system we will GRUB use as the boot loader. GRUB is easy to use, widely used and has a wide range of functions.
There are people who think you should write your own boot loader, so you really have done it yourself. These people then use the functions of the BIOS in their boot loader, so they only start to look at the code of others in a different place. In order to do so, they have to invest a lot of time in the functionality of the bootloader, if they do not want to live with limitations in their operating system. Bootloaders are nice projects, but we focus on the operating system.
MultiBoot is the name of the specification to be followed by a kernel to load GRUB can. The linked article contains more information.

The Hello World kernel

To spend "Hello World" presents us with two problems: First, we need to know how to get text on the screen - and second, we have to make sure that the corresponding code is executed at all.
Let's start with the simple: Directly after booting is a PC in text mode. In the text mode, characters are displayed on the screen, which are located in graphics memory from address 0xb8000. Each character requires two bytes: The first byte is the character itself The second byte is called the attribute byte, which determines the color and background color of the text - more details are provided when needed in the article. Text output . So we go the following way (the file is called for me init.c):
void init ( void ) 
{ 
    const  char hw [ ] = "Hello World!" ;
    int i;
    char * video = ( char * ) 0xb8000;
 
    // C strings have a zero byte as a conclusion 
    for  ( i = 0 ; hw [ i ]  ! = ' \ 0 ' ; i ++ )  {
 
        // Mark i in the video memory copy 
        video [ i * 2 ] = hw [ i ] ;
 
        // 0x07 = light gray to black 
        video [ i * 2 + 1 ] = 0x07;
    } 
}
After solving the first problem, we must now only execute the code. So we have to get the multiboot header in our binary. In addition, our function uses a few local variables. Local variables end up on the stack , so we have to provide some space for the stack. This part is done in assembler (this file is the start.S with a ".S"):
.section .text
 
// Init is a function from init. c 
. externally init
 
#define MB_MAGIC 0x 1b adb002
#define MB_FLAGS 0x0
#define MB_CHECKSUM - ( MB_MAGIC + MB_FLAGS )
 
// The multiboot header
. align  4 
. int     MB_MAGIC
. int     MB_FLAGS
. int     MB_CHECKSUM
 
// _start Must be global so that the linker is found and the entry point
Can use // ( all labels that are not global, are only in this file
// Visible )
.global _start
_begin:
    // Stack initialize
     mov $ kernel_stack,% esp
 
    // C code to call
     call init
 
    // If we should ever return from init, we will disable the interrupts and
    // simply hold the processor. ( You need it so do not let hot running unnecessarily. )
_Stop:
    cli 
    hlt
 
    // Should it go yet, we try again the CPU to let you sleep
     jmp _stop
 
// 8 kB stack for the kernel. The  label is placed after the free space,
// Because the stack grows down
.section .bss
.space 8192 
kernel_stack:
At this point we would be ready with the code. Now we have to compile it and try it somehow. Of course you can manually invoke the compiler and linker, but it is advisable to make use of this. Make will always re-translate all files that have actually changed, and it does not forget anything. An appropriate Makefile (which is the file name of the control file for make) might look like this:
SRCS = $ (shell find -name '*. [CS]')
OBJS = $ (addsuffix .o, $ (basename $ (SRCS)))

CC = gcc
LD = ld

ASFLAGS = -m32
CFLAGS = -m32 -Wall -g -fno-stack-protector -nostdinc
LDFLAGS = -melf_i386 -Txt = 0x100000

Kernel: $ (OBJS)
 $ (LD) $ (LDFLAGS) -o $ @ $ ^

% .o:% .c
 $ (CC) $ (CFLAGS) -c -o $ @ $ ^

% .o:% .S
 $ (CC) $ (ASFLAGS) -c -o $ @ $ ^

Clean
 Rm $ (OBJS)

.PHONY: clean
It is worth mentioning in particular the option -Ttext = 0x100000 for ld. This is the address to which the kernel is to be loaded and where it is executed. GRUB can not load a kernel to a lower address than 1 MB, and 0x100000 is exactly 1 MB.
The values ​​for CC and LD may need to be adjusted so that they point to a compiler or linker that generates i386 ELF files. With the cross compiler on Windows, it could be for example that for CC i586-elf-gcc and for LD i586-elf-ld must be entered.
After a call to make You should now have a file kernel outfitted. Now we just have to do it somehow. There are several possibilities:
  • Qemu can load multiboot kernels directly from Version 0.11. qemu -kernel kernel brings our kernel for execution and writes a beautiful Hello World in the upper left corner. To delete the rest of the screen we unfortunately forgot, so that is written about any BIOS messages.
  • Any one emulator can boot from disk images, as qemu with floppy.img qemu -FDA . So we need a diskette that contains GRUB and our kernel. There are articles on the GRUB legacy in this wiki.
  • A real PC and a real floppy, of course. Given either create the image and write to floppy disk (eg dd or rawrite ) or directly take a GRUB floppy and copy the kernel on it
If we do not boot directly with qemu 0.11 but use a floppy disk image (or a real floppy disk), we will be grilled after booting GRUB (if no menu.lst was created). The kernel can then be started as follows:
Kernel / kernel
boat

Multiboot header in a separate section

The kernel seems to work wonderfully, but it contains a bug. The error is not dramatic at the moment, but it will eventually strike unexpectedly - so we should do it right now.
As mentioned above, the multiboot header of the kernel must be in the first 8 kB of the file. This works more or less randomly. The section .text ends up at the beginning of the binary, but the order in which the files are written in is left to the linker. We can, however, force .text to start with the multiboot header: we first put it in its own section and tell the linker how to assemble it.
To do this, we modify the start of the start.S as follows:
.section multiboot
#define MB_MAGIC 0x 1b adb002
#define MB_FLAGS 0x0
#define MB_CHECKSUM - ( MB_MAGIC + MB_FLAGS )
 
// The multiboot header
. align  4 
. int     MB_MAGIC
. int     MB_FLAGS
. int     MB_CHECKSUM
 
.section .text
 
// Init is a function from init. c 
. externally init
 
// _start Must be global so that the linker is found and the entry point
...
We also create a linker script , kernel.ld the file:
/ * Start the execution with _start * /
ENTRY (_start)

/ *
 * This is used to specify the order in which sections in the binary
 * Should be written
 * /
SECTIONS
{
    / *
     *. Is the current position in the file. We want the kernel as before
     * To 1 MB load, so we must lie down there the first section
     * /
    . = 0x100000;

    / *
     * The multiboot header must come first (in the first 8 kB).
     * Simply integrate the standard sections in a row.
     * /
    .text: {
        * (Multiboot)
        *(.text)
    }
    .data ALIGN (4096): {
        * (.data)
    }
    .rodata ALIGN (4096): {
        * (Rodata)
    }
    .bss ALIGN (4096): {
        * (.bss)
    }
}
Then the linker must also know that he should use the file. Therefore, in the Makefile that is -Ttext = 0x100000 a -Tkernel.ld

Printf

After very much detailed code, we come to some tasks which I would gladly leave the inclined reader to the exercise. It involves functions for text output - because you can not find any error when you can display any information. We need at least:
  • A function for deleting the screen (not mandatory, but looks more beautiful)
  • A function to output text
  • A function to output numbers in decimal notation
  • A function to output numbers and pointers in hexadecimal notation
In the following parts, I assume that the lower three tasks are taken over by a function kprintf (), which functions exactly the same as the normal printf () function for the named cases. The features are the best in their own source code file (eg console.c) and in the examples in the header file console.h expected.
For the implementation of printf you need stdarg.h to evaluate the variable parameter list. gcc provides for already Builtins to (or a complete stdarg.h when a cross-compiler used) so that the header file must contain only the following:
 
typedef __builtin_va_list va_list;
#define va_start (ap, X) __builtin_va_start (ap, X) 
#define va_arg (ap, type) __builtin_va_arg (ap, type) 
#define va_end (ap) __builtin_va_end (ap)
 
It might also be a good idea, all expenditure on the serial interface output. The emulators can actually display all the output on the serial port on the console or write it to a file. This makes it unnecessary to write things off the emulator (eg addresses in error messages).
The init.c file should look like this (except perhaps a call to clear the screen):
#include "console.h"
 
void init ( void ) 
{ 
    kprintf ( "Hello World! \ n " ) ;
}

Troubleshooting

Comments

Popular posts from this blog

[Inside AdSense] Understanding your eCPM (effective cost per thousand impress...