[ / main / writing / grubKernel ]

    Creating a Multi-Boot Compatible Kernel



©2002 neuraldk

    The Multi-Boot Standard




The multi-boot standard came about from a lack of general-purpose, boot loaders, and a lack of standardized kernels. It states a collection of rules that a kernel must comply with in order to be multi-boot compliant. The advantage, of course, is that your kernel can be booted, out of the box, by any multi-boot compliant boot-loader, such as GRUB.




The rules are simple, and easy to incorporate into your kernel. Some developers, however, have experienced troubles getting their kernel booted properly by GRUB, and so I've decided to create this tutorial in the hopes that it'll help some independant operating system developers out.




I intend to use Linux withen this tutorial as a development system simply because it has an excellent programming interface, and many different virtual machines to aid in development (vmware, bochs, plex86, etc).




    Getting and Installing GRUB




The latest version of GRUB can be downloaded from:

    ftp://alpha.gnu.org/gnu/grub/



When I last downloaded, there was also a pre-installed GRUB floppy image (grub-0.92-i386-pc.ext2fs), which can speed up the installation process quite nicely.




After downloading, you can compile like any standard unix program (./configure, make), and now you're ready to setup a floppy with GRUB. If you managed to get a copy of the pre-installed image, you can skip the next step.




    Creating an unformatted GRUB Image




After compiling grub, you will be left with two files of interest: stage1/stage1 and stage2/stage2. To create an unformatted GRUB image, you're going to want to concatenate these two files together:




    cat stage1/stage1 stage2/stage2 > boot.img



If you're testing your operating system with a virtual environment, you should be able to use this image as a floppy drive. If, however, you are using a non-virtual environment you'll want to write this image to a floppy disk:

    cat boot > /dev/fd0



    Creating a bootable GRUB floppy




Now you're going to want to create a disk that will be your operating system's boot disk. This disk must be formatted with a file system that GRUB supports (such as ext2fs, reiserfs, fat, etc). If you're using a virtual machine, you can create a floppy image with dd, and then format it like any device. We're also going to need to mount this floppy for our next step.




    virtual machine:
      dd if=/dev/zero of=floppy.img bs=512 count=2880
      mke2fs floppy.img
      mount floppy.img /mnt/floppy -o loop
    real environment:
      mke2fs /dev/fd0
      mount /dev/fd0 /mnt/floppy



This formatted disk must also have the directory /boot/grub, which must contain stage1 and stage2. The location, on the floppy, of stage2 must also never change, or else GRUB will not be able to boot, so it's a good idea to make the file immutable:

    mkdir -p /mnt/floppy/boot/grub
    cp stage1/stage1 stage2/stage2 /mnt/floppy/boot/grub
    chmod a-w /boot/grub/stage2



    Setting up GRUB




Now that we've been through all the preliminary steps, it's time to get GRUB up and running. First, boot your computer or virtual machine with your unformatted GRUB disk, or image.




At this point you'll be presented with the GRUB shell, and you'll want to remove the current floppy disk, or image, and insert the formatted GRUB floppy disk or image, and issue the command setup (fd0). This will setup your formatted floppy to boot GRUB properly, which will the basic building block we'll use when developing our operating system.




If you now boot with this floppy, you'll get the same result as with the previous unformatted floppy disk, or image, and so you may be asking, "What's the point? Why make two of the same disk?"




Well, the two aren't the same, actually. One has a formatted file system, while the other doesn't. We're going to need some form of a file system in order to copy our kernel to the GRUB floppy, which the first floppy doesn't afford us. It's purpose was simply to setup the second floppy, which is much more useful to us.




    Creating a Mini-Kernel




In order for our kernel to be loadable by GRUB, we must provide it a multi-boot header, withen the first 8k of our kernel.




dd magic
dd flags
dd checksum
Required
dd header_Address
dd start_of_text
dd end_of_data
dd end_of_bss
dd entry_address
Required if flags[16] is set
dd mode_type
dd width
dd height
dd depth
Required if flags[2] is set



Because of the 8k limitation, it's usually a good idea to define this header in assembly language so that you have complete control over where it appears. Certain C compilers may relocate your data to well beyond the 8kb mark, at which point GRUB will no longer recognize your kernel as a multi-boot kernel and will not boot it.




It should be noted that, if you're compiling your kernel into the ELF format, you will only need to define the first three elements. The ELF format itself will define the next five withen it's header information. The last four are only useful if you want GRUB to change the video mode for you.




    The Multi-Boot Header Fields




Magic Must contain 0x1BADB002
Flags With Bit 0 set, all boot modules loaded with the kernel will be page aligned.
With Bit 1 set, GRUB will inform us with all memory information available on the current system.
With Bit 2 set, GRUB will inform us on available video modes on the current system.
Bit 16 states that fields 4 to 8 in the multi-boot header are defined, and should be used to load the kernel.
Checksum When this 32-bit integer is added to magic and flags, the result will be zero. In other words, it's exactly equal to 0 - (magic + flags)



    A Mini-Kernel




With this information, we can write a simple kernel that is loadable by GRUB:




    ; multi-boot mini kernel - [ start.asm ]
    ;
    ; (c) 2002, NeuralDK
    section .text
    bits 32
    ; multi boot header defines
    MBOOT_PAGE_ALIGN   equ 1 << 0
    MBOOT_MEM_INFO     equ 1 << 1
    MBOOT_AOUT_KLUDGE  equ 1 << 16
    MBOOT_MAGIC equ 0x1BADB002
    MBOOT_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO | MBOOT_AOUT_KLUDGE
    CHECKSUM    equ -(MBOOT_MAGIC + MBOOT_FLAGS)
    STACK_SIZE  equ 0x1000
    ; defined in the linker script
    extern textEnd
    extern dataEnd
    extern bssEnd
    global  start
    global _start
    entry:
        jmp start
        ; The Multiboot header
    align 4, db 0
    mBootHeader:
        dd MBOOT_MAGIC
        dd MBOOT_FLAGS
        dd CHECKSUM
        ; fields used if MBOOT_AOUT_KLUDGE is set in
        ; MBOOT_HEADER_FLAGS
        dd mBootHeader                     ; these are PHYSICAL addresses
        dd entry                           ; start of kernel .text (code) section
        dd dataEnd                         ; end of kernel .data section
        dd bssEnd                          ; end of kernel BSS
        dd entry                           ; kernel entry point (initial EIP)
     start:
    _start:
        mov edi, 0xB8000
        mov esi, string
        mov ah, 0x0F
      .charLoop:
        lodsb
        stosw
        or al, al
        jnz .charLoop
        jmp short $
    section .data
    string          db "ndk is alive!", 0
    section .bss
        align 4, db 0
        common stack 0x1000
        resb 0x4000



Hopefully most of this will be self explanatory given the above descriptions. This assembly source simply includes the multi-boot header, and a string displaying routine. In fact, this code is based, almost-exclusively, on the example located in the multi-boot specifications, with a few changes; I've rewritten the code for Nasm, and encorporated sections.




Why include sections? Well, very few kernels are 100% assembly. Most include C or C++ source, which is going to want, at least, the .text, .data and .bss sections. Also, with these sections defined, we're able to include entries 4 to 8 of the multi-boot header. They aren't required for ELF kernels, but if you intend to produce a kernel in any other format, you'll need these, and so I always provide them.




If anybody's gotten ahead of me now, they'll notice that this will not assemble correctly, and will complain about missing symbols. This is because I've written a linker script for this source code. Why? Simply because, with a linker script, I can have complete control over where sections lie, which is important, because the multi-boot header must be withen the first 8kb of our binary. Also, with a linker script I can define symbols for the starting and ending of each section (which is required for fields 4 to 8 of the multi-boot header).




    SECTIONS {
      .text 0x00100000 :{
        *(.text)
      }
      textEnd = .;
      .data :{
        *(.data)
        *(.rodata)
      }
      dataEnd = .;
      .bss :{
        *(.common)
        *(.bss)
      }
      bssEnd = .;
    }



Looking above you'll probably spot a few oddities in the above script. Firstly, I've decided to put the read-only data in the data section which, by default, is read/write. Most programmers would include the read only section in the text section, which is read-only. The only reason I don't is because, once your kernel has been booted by GRUB you're left with two defined GDT entries; one code, and one data. Both start at 0x0, and extend to 0xFFFFFFFF. See what I'm getting at here? Even if you put the read-only data in a read-only section, it can still be read by the read/write data descriptor! They both span the same memory area. It makes no difference.




Secondly, you'll notice I've set the kernel to be loaded at the 1MB address. This is important. A multi-boot compatible kernel cannot be loaded before the 1MB mark, it must be loaded at or above 1MB.




Lastly, note that I've defined the ending of each section in this linker script. These are imported in the above assembly source, and used to define the multi-boot header.




After saving the above script as ./ldscript, you should be able to assemble and link this multi-boot kernel:




    nasm -f elf start.asm
    gcc -Xlinker -T -Xlinker ldscript
        -ffreestanding
        -fno-builtin -nostdlib
        -nostartfiles -s start.o
        -o miniKernel



All the switches to GCC are simply to tell it to link just our object. We don't want crt0.o or any other standard linker dependancies linked into our operating system. Not only is it wasted, operating system dependant code, but it will also result in duplicated symbols. You'll notice we defined the start and _start symbols in our assembly source. There was a reason for that. GCC has it's own start function which is called before the main function and sets up the Linux environment for the C or C++ program. We, obviously, don't want this. We aren't rewritting Linux, we're writting a completely new operating system, and so we should redefine this function.




    Booting this Kernel with GRUB




Okay, with the above code out of the way, lets try booting it. All we have to do is copy our newly created kernel to our formatted GRUB floppy disk, or image.




    virtual machine:
      mount floppy.img /mnt/floppy -o loop
      cp miniKernel /mnt/floppy
    real machine:
      mount /dev/fd0 /mnt/floppy
      cp miniKernel /mnt/floppy



And now reboot your environment with this floppy inserted, and you'll get the same GRUB shell again. Now try issuing the following commands to GRUB:




    root (fd0)
    kernel /miniKernel
    boot



You're kernel should boot, display a message at the top left hand corner, and then loop endlessly.




A lot of work for all that, right? Perhaps, but the way we've coded our operating system, it's incredibly easy to extend. Before we do anything, though, we should define an IDT and GDT. Yes, GRUB does create a GDT for us, but it's size and location are undefined, and it's generally a good idea to create your own. You must create an IDT, seeing as though GRUB doesn't provide us with one.




Interestingly enough, with an IDT and GDT in place, our kernel is advanced enough to run the multi-boot example kernel, and now provides the basic elements of any kernel. What took me many hundreds of lines when developing polyOS can now be completed in less than 300, and our resulting kernel is only about 6kb.




Let's take a look at the revised start.asm code:




    ; multi-boot mini kernel - [ start.asm ]
    ;
    ; (c) 2002, NeuralDK
    section .text
    bits 32
    ; multi boot header defines
    MBOOT_PAGE_ALIGN   equ 1 << 0
    MBOOT_MEM_INFO     equ 1 << 1
    MBOOT_AOUT_KLUDGE  equ 1 << 16
    MBOOT_MAGIC equ 0x1BADB002
    MBOOT_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO | MBOOT_AOUT_KLUDGE
    CHECKSUM    equ -(MBOOT_MAGIC + MBOOT_FLAGS)
    STACK_SIZE  equ 0x1000
    ; defined in the linker script
    extern textEnd
    extern dataEnd
    extern bssEnd
    extern cmain
    global  start
    global _start
    entry:
        jmp start
        ; The Multiboot header
    align 4, db 0
    mBootHeader:
        dd MBOOT_MAGIC
        dd MBOOT_FLAGS
        dd CHECKSUM
        ; fields used if MBOOT_AOUT_KLUDGE is set in
        ; MBOOT_HEADER_FLAGS
        dd mBootHeader                     ; these are PHYSICAL addresses
        dd entry                           ; start of kernel .text (code) section
        dd dataEnd                         ; end of kernel .data section
        dd bssEnd                          ; end of kernel BSS
        dd entry                           ; kernel entry point (initial EIP)
     start:
    _start:
        ; clear the idt + gdt @ 0x0
        xor edi, edi
        mov ecx, 0x800 + 0x800
        rep stosb
        ; setup a bare bones GDT
        mov esi, my_gdt
        mov edi, 0x0800
        mov ecx, 8 * 4
        rep movsb
        lgdt    [pGDT]                  ; load the GDT
        lidt    [pIDT]                  ; load the IDT
        mov     dx, 0x08        ; 0x08 - kernel data segment
        mov     ds, dx
        mov     es, dx
        mov     fs, dx
        mov     gs, dx
        mov     dx, 0x18        ; 0x18 - kernel stack segment
        mov     ss, dx
        mov    esp, (stack + STACK_SIZE)
        ; push the multiboot info structure, and magic
        push ebx
        push eax
        ; load cs with new selector
        jmp 0x10:new_gdt
      new_gdt:
        ; time for some C!
        call cmain
        jmp short $
    section .data
    string          db "ndk is alive!", 0
    pIDT            dw 800h         ; limit of 256 IDT slots
                    dd 00000000h    ; starting at 0
    pGDT            dw 800h         ; 256 GDT slots
                    dd 00000800h    ; starting at 800h (after IDT)
    my_gdt:
            ; Null descriptor
            ;   base : 0x00000000
            ;   limit: 0x00000000 ( 0.0 MB)
            dd 0
            dd 0
            ; 0x08 descriptor - Kernel data segment
            ;   base : 0x00000000
            ;   limit: 0xfffff pages (4 GB)
            ;   DPL  : 0
            ;   32 bit, present, system, expand-up, writeable
            dd 0x0000ffff
            dd 0x00cf9200
            ; 0x10 descriptor - Kernel code segment
            ;   base : 0x00000000
            ;   limit: 0xfffff (4 GB)
            ;   DPL  : 0
            ;   32 bit, present, system, non-conforming, readable
            dd 0x0000ffff
            dd 0x00cf9A00
            ; 0x18 descriptor - Kernel stack segment
            ;   base : 0x00000000 //0x00080000
            ;   limit: 0xfffff (4 GB)
            ;   DPL  : 0
            ;   32 bit, present, system, expand-up, writeable
            dd 0x0000ffff
            dd 0x00cb9200
    section .bss
        align 4, db 0
        common stack 0x1000
        resb 0x4000



And the kernel.c file, provided in the multi-boot specifications:




    /* kernel.c - the C part of the kernel */
    /* Copyright (C) 1999  Free Software Foundation, Inc.
       This program is free software; you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation; either version 2 of the License, or
       (at your option) any later version.
       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.
       You should have received a copy of the GNU General Public License
       along with this program; if not, write to the Free Software
       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
    #include "multiboot.h"
    /* Macros. */
    /* Check if the bit BIT in FLAGS is set. */
    #define CHECK_FLAG(flags,bit)   ((flags) & (1 << (bit)))
    /* Some screen stuff. */
    /* The number of columns. */
    #define COLUMNS                 80
    /* The number of lines. */
    #define LINES                   24
    /* The attribute of an character. */
    #define ATTRIBUTE               7
    /* The video memory address. */
    #define VIDEO                   0xB8000
    /* Variables. */
    /* Save the X position. */
    static int xpos;
    /* Save the Y position. */
    static int ypos;
    /* Point to the video memory. */
    static volatile unsigned char *video;
    /* Forward declarations. */
    void cmain (unsigned long magic, unsigned long addr);
    static void cls (void);
    static void itoa (char *buf, int base, int d);
    static void putchar (int c);
    void printf (const char *format, ...);
    /* Check if MAGIC is valid and print the Multiboot information structure
       pointed by ADDR. */
    void
    cmain (unsigned long magic, unsigned long addr)
    {
      multiboot_info_t *mbi;
      /* Clear the screen. */
      cls ();
      /* Am I booted by a Multiboot-compliant boot loader? */
      if (magic != MULTIBOOT_BOOTLOADER_MAGIC)
        {
          printf ("Invalid magic number: 0x%x\n", (unsigned) magic);
          return;
        }
      /* Set MBI to the address of the Multiboot information structure. */
      mbi = (multiboot_info_t *) addr;
      /* Print out the flags. */
      printf ("flags = 0x%x\n", (unsigned) mbi->flags);
      /* Are mem_* valid? */
      if (CHECK_FLAG (mbi->flags, 0))
        printf ("mem_lower = %uKB, mem_upper = %uKB\n",
                (unsigned) mbi->mem_lower, (unsigned) mbi->mem_upper);
      /* Is boot_device valid? */
      if (CHECK_FLAG (mbi->flags, 1))
        printf ("boot_device = 0x%x\n", (unsigned) mbi->boot_device);
      /* Is the command line passed? */
      if (CHECK_FLAG (mbi->flags, 2))
        printf ("cmdline = %s\n", (char *) mbi->cmdline);
      /* Are mods_* valid? */
      if (CHECK_FLAG (mbi->flags, 3))
        {
          module_t *mod;
          int i;
          printf ("mods_count = %d, mods_addr = 0x%x\n",
                  (int) mbi->mods_count, (int) mbi->mods_addr);
          for (i = 0, mod = (module_t *) mbi->mods_addr;
               i < mbi->mods_count;
               i++, mod += sizeof (module_t))
            printf (" mod_start = 0x%x, mod_end = 0x%x, string = %s\n",
                    (unsigned) mod->mod_start,
                    (unsigned) mod->mod_end,
                    (char *) mod->string);
        }
      /* Bits 4 and 5 are mutually exclusive! */
      if (CHECK_FLAG (mbi->flags, 4) && CHECK_FLAG (mbi->flags, 5))
        {
          printf ("Both bits 4 and 5 are set.\n");
          return;
        }
      /* Is the symbol table of a.out valid? */
      if (CHECK_FLAG (mbi->flags, 4))
        {
          aout_symbol_table_t *aout_sym = &(mbi->u.aout_sym);
          printf ("aout_symbol_table: tabsize = 0x%0x, "
                  "strsize = 0x%x, addr = 0x%x\n",
                  (unsigned) aout_sym->tabsize,
                  (unsigned) aout_sym->strsize,
                  (unsigned) aout_sym->addr);
        }
      /* Is the section header table of ELF valid? */
      if (CHECK_FLAG (mbi->flags, 5))
        {
          elf_section_header_table_t *elf_sec = &(mbi->u.elf_sec);
          printf ("elf_sec: num = %u, size = 0x%x,"
                  " addr = 0x%x, shndx = 0x%x\n",
                  (unsigned) elf_sec->num, (unsigned) elf_sec->size,
                  (unsigned) elf_sec->addr, (unsigned) elf_sec->shndx);
        }
      /* Are mmap_* valid? */
      if (CHECK_FLAG (mbi->flags, 6))
        {
          memory_map_t *mmap;
          printf ("mmap_addr = 0x%x, mmap_length = 0x%x\n",
                  (unsigned) mbi->mmap_addr, (unsigned) mbi->mmap_length);
          for (mmap = (memory_map_t *) mbi->mmap_addr;
               (unsigned long) mmap < mbi->mmap_addr + mbi->mmap_length;
               mmap = (memory_map_t *) ((unsigned long) mmap
                                        + mmap->size + sizeof (mmap->size)))
            printf (" size = 0x%x, base_addr = 0x%x%x,"
                    " length = 0x%x%x, type = 0x%x\n",
                    (unsigned) mmap->size,
                    (unsigned) mmap->base_addr_high,
                    (unsigned) mmap->base_addr_low,
                    (unsigned) mmap->length_high,
                    (unsigned) mmap->length_low,
                    (unsigned) mmap->type);
        }
    }
    /* Clear the screen and initialize VIDEO, XPOS and YPOS. */
    static void
    cls (void)
    {
      int i;
      video = (unsigned char *) VIDEO;
      for (i = 0; i < COLUMNS * LINES * 2; i++)
        *(video + i) = 0;
      xpos = 0;
      ypos = 0;
    }
    /* Convert the integer D to a string and save the string in BUF. If
       BASE is equal to 'd', interpret that D is decimal, and if BASE is
       equal to 'x', interpret that D is hexadecimal. */
    static void
    itoa (char *buf, int base, int d)
    {
      char *p = buf;
      char *p1, *p2;
      unsigned long ud = d;
      int divisor = 10;
      /* If %d is specified and D is minus, put `-' in the head. */
      if (base == 'd' && d < 0)
        {
          *p++ = '-';
          buf++;
          ud = -d;
        }
      else if (base == 'x')
        divisor = 16;
      /* Divide UD by DIVISOR until UD == 0. */
      do
        {
          int remainder = ud % divisor;
          *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
        }
      while (ud /= divisor);
      /* Terminate BUF. */
      *p = 0;
      /* Reverse BUF. */
      p1 = buf;
      p2 = p - 1;
      while (p1 < p2)
        {
          char tmp = *p1;
          *p1 = *p2;
          *p2 = tmp;
          p1++;
          p2--;
        }
    }
    /* Put the character C on the screen. */
    static void
    putchar (int c)
    {
      if (c == '\n' || c == '\r')
        {
        newline:
          xpos = 0;
          ypos++;
          if (ypos >= LINES)
            ypos = 0;
          return;
        }
      *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF;
      *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE;
      xpos++;
      if (xpos >= COLUMNS)
        goto newline;
    }
    /* Format a string and print it on the screen, just like the libc
       function printf. */
    void
    printf (const char *format, ...)
    {
      char **arg = (char **) &format;
      int c;
      char buf[20];
      arg++;
      while ((c = *format++) != 0)
        {
          if (c != '%')
            putchar (c);
          else
            {
              char *p;
              c = *format++;
              switch (c)
                {
                case 'd':
                case 'u':
                case 'x':
                  itoa (buf, c, *((int *) arg++));
                  p = buf;
                  goto string;
                  break;
                case 's':
                  p = *arg++;
                  if (! p)
                    p = "(null)";
                string:
                  while (*p)
                    putchar (*p++);
                  break;
                default:
                  putchar (*((int *) arg++));
                  break;
                }
            }
        }
    }



Along with a slightly modified multiboot.h header file provided by the multi-boot documentation (the GAS assembly language references have been removed):




    /* multiboot.h - the header for Multiboot */
    /* Copyright (C) 1999, 2001  Free Software Foundation, Inc.
       This program is free software; you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation; either version 2 of the License, or
       (at your option) any later version.
       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.
       You should have received a copy of the GNU General Public License
       along with this program; if not, write to the Free Software
       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
    /* The magic number passed by a Multiboot-compliant boot loader. */
    #define MULTIBOOT_BOOTLOADER_MAGIC      0x2BADB002
    /* Types. */
    /* The Multiboot header. */
    typedef struct multiboot_header
    {
      unsigned long magic;
      unsigned long flags;
      unsigned long checksum;
      unsigned long header_addr;
      unsigned long load_addr;
      unsigned long load_end_addr;
      unsigned long bss_end_addr;
      unsigned long entry_addr;
    } multiboot_header_t;
    /* The symbol table for a.out. */
    typedef struct aout_symbol_table
    {
      unsigned long tabsize;
      unsigned long strsize;
      unsigned long addr;
      unsigned long reserved;
    } aout_symbol_table_t;
    /* The section header table for ELF. */
    typedef struct elf_section_header_table
    {
      unsigned long num;
      unsigned long size;
      unsigned long addr;
      unsigned long shndx;
    } elf_section_header_table_t;
    /* The Multiboot information. */
    typedef struct multiboot_info
    {
      unsigned long flags;
      unsigned long mem_lower;
      unsigned long mem_upper;
      unsigned long boot_device;
      unsigned long cmdline;
      unsigned long mods_count;
      unsigned long mods_addr;
      union
      {
        aout_symbol_table_t aout_sym;
        elf_section_header_table_t elf_sec;
      } u;
      unsigned long mmap_length;
      unsigned long mmap_addr;
    } multiboot_info_t;
    /* The module structure. */
    typedef struct module
    {
      unsigned long mod_start;
      unsigned long mod_end;
      unsigned long string;
      unsigned long reserved;
    } module_t;
    /* The memory map. Be careful that the offset 0 is base_addr_low
       but no size. */
    typedef struct memory_map
    {
      unsigned long size;
      unsigned long base_addr_low;
      unsigned long base_addr_high;
      unsigned long length_low;
      unsigned long length_high;
      unsigned long type;
    } memory_map_t;



That's it! You now have a fully functioning kernel. After compiling and linking it, you can overwrite our previous miniKernel on your boot disk and reboot to see it in action.




    nasm -f elf start.asm
    gcc -c kernel.c
    gcc -Xlinker -T -Xlinker ldscript
        -ffreestanding -fno-builtin
        -nostdlib -nostartfiles
        -s start.o kernel.o
        -o miniKernel