Multiboot

Multiboot

Summary: Motivation Behind Multiboot

The motivation behind the development of the Multiboot Specification stems from the need for a standardized booting process for different operating systems. Before Multiboot, each operating system had its own unique boot loader, leading to significant incompatibilities and complexities for users who wanted to switch between different OSes or manage multi-boot systems.

The key goals of Multiboot include:

  1. Standardization: Creating a common booting protocol that any compliant boot loader can use to load any compliant OS, thus eliminating the need for OS-specific boot loaders.
  2. Flexibility: Allowing boot loaders to load various kernels and initialize the system with relevant parameters, making it easier to support a wide range of operating systems.
  3. Simplification: Simplifying the boot process for developers and users by providing a consistent interface, reducing the complexity and effort needed to support multiple operating systems.

By introducing Multiboot, the developers aimed to streamline the booting process, making it more efficient and accessible, particularly in environments where multiple operating systems might be used on the same machine.

General components

  1. Multiboot Header: This is a data structure that a Multiboot-compliant boot loader looks for in the OS image. It includes fields like the magic number, flags, checksum, and other optional fields that provide additional information or requirements for loading the OS.
  2. Multiboot Information Structure: After booting, the boot loader provides the OS with this structure containing information about the machine’s memory, boot device, command line, modules, and more.
  3. Tags: Multiboot supports various tags that allow an OS image to request or specify certain actions or data from the boot loader, such as preferred load addresses, memory limits, and video modes.
  4. Alignment and Addressing: Multiboot has specific requirements and options for memory alignment and addressing, which helps in managing different memory architectures and system configurations.

These components work together to create a unified interface between boot loaders and operating systems, simplifying the process of loading and initializing kernels in a consistent manner.

Code

boot.s

This bootloader is simple to maintain, easy to understand, and efficient in its execution.
It should functionality while being more straightforward to work with and modify in the future.

/* boot.S - Bootstrap the kernel
 *
 * This file is responsible for setting up the initial environment needed to run the kernel.
 * It adheres to the Multiboot specification and provides an entry point for the kernel.
 */

#define ASM_FILE 1
#include <multiboot.h>

/* C symbol format. If HAVE_ASM_USCORE is defined, prepend an underscore to C symbols. */
#ifdef HAVE_ASM_USCORE
# define EXT_C(sym) _##sym
#else
# define EXT_C(sym) sym
#endif

/* Define the stack size (16KB). */
#define STACK_SIZE 0x4000

/* Multiboot header flags.
 * These flags specify the kernel's requirements for page alignment, memory information, and video mode.
 * The AOUT_KLUDGE flag is used if the kernel is not an ELF binary.
 */
#ifdef __ELF__
# define AOUT_KLUDGE 0
#else
# define AOUT_KLUDGE MULTIBOOT_AOUT_KLUDGE
#endif
#define MULTIBOOT_HEADER_FLAGS (MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_VIDEO_MODE | AOUT_KLUDGE)

        .text

        .globl  start, _start
start:
_start:
        /* Jump to the Multiboot entry point */
        jmp     multiboot_entry

        /* Align the following data to a 32-bit boundary. */
        .align  4
        
        /* Multiboot header
         *
         * This header informs the bootloader that the kernel is Multiboot-compliant
         * and specifies its loading requirements.
         */
multiboot_header:
        /* magic - The magic number that identifies this as a Multiboot header. */
        .long   MULTIBOOT_HEADER_MAGIC

        /* flags - The flags field specifying features required by the kernel. */
        .long   MULTIBOOT_HEADER_FLAGS

        /* checksum - The checksum field ensures that the sum of the magic number, flags, and checksum is zero. */
        .long   -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

#ifndef __ELF__
        /* The following fields are only used if the kernel is not an ELF binary (a.out format).
         * They specify the load addresses, entry point, and other important addresses.
         */
        .long   multiboot_header   /* header_addr - The address of this Multiboot header. */
        .long   _start             /* load_addr - The physical address to load the kernel image. */
        .long   _edata             /* load_end_addr - The end address of the loaded image (data section). */
        .long   _end               /* bss_end_addr - The end address of the bss section (uninitialized data). */
        .long   multiboot_entry    /* entry_addr - The entry point to start executing the kernel. */
#else /* ! __ELF__ */
        /* If the kernel is an ELF binary, these fields are not used and are set to zero. */
        .long   0
        .long   0
        .long   0
        .long   0
        .long   0       
#endif /* __ELF__ */

        /* The following fields specify the desired video mode.
         * These are only used if the MULTIBOOT_VIDEO_MODE flag is set.
         */
        .long 0                     /* Reserved field (unused). */
        .long 1024                  /* width - The desired screen width. */
        .long 768                   /* height - The desired screen height. */
        .long 32                    /* depth - The desired color depth (bits per pixel). */

multiboot_entry:
        /* Initialize the stack pointer.
         * The stack is set up at the top of the defined stack area.
         */
        movl    $(stack + STACK_SIZE), %esp

        /* Reset the EFLAGS register.
         * This clears any leftover flags from the bootloader.
         */
        pushl   $0
        popf

        /* Push the Multiboot information structure pointer (passed in %ebx) onto the stack. */
        pushl   %ebx

        /* Push the Multiboot magic number (passed in %eax) onto the stack. */
        pushl   %eax

        /* Call the C main function.
         * This is the entry point of the C code that will handle further initialization.
         */
        call    EXT_C(cmain)

        /* If the C main function returns, halt the CPU.
         * The system should never reach this point; if it does, something went wrong.
         */
        pushl   $halt_message
        call    EXT_C(printf)
        
loop:
        hlt    /* Halt the CPU indefinitely. */
        jmp     loop /* Infinite loop to keep the CPU halted. */

halt_message:
        .asciz  "Halted." /* Message to display if the CPU is halted. */

        /* Define the stack area.
         * The stack is declared as a common symbol, meaning it can be defined in multiple files,
         * but only one definition will be linked into the final binary.
         */
        .comm   stack, STACK_SIZE


Key components

Multiboot Header:

The Multiboot header is a critical part of any Multiboot-compliant kernel. It provides information that allows the bootloader to properly load the kernel. The header contains a magic number, flags indicating the kernel’s requirements, and a checksum to validate the header.
Depending on whether the kernel is in ELF format or a.out format, additional fields specify loading addresses and entry points.
Entry Point (start and _start):

The entry point is where the bootloader transfers control to the kernel. The code at this entry point sets up the initial execution environment, including the stack, and then jumps to the multiboot_entry label.

Stack Initialization:

The stack is set up by moving the stack pointer to the top of a pre-allocated stack space (STACK_SIZE is defined as 16KB). This is crucial because the kernel needs a valid stack for function calls and local variables.

EFLAGS Reset:

The EFLAGS register is reset to ensure no residual flags from the bootloader affect the kernel’s execution.

Calling the C Main Function:

After setting up the initial environment, the assembly code pushes the necessary arguments (Multiboot information structure and magic number) onto the stack and calls the C cmain function. This is where the main logic of the kernel begins.

Halt and Loop:

If the cmain function returns (which it should not under normal circumstances), the CPU is halted with an infinite loop to prevent it from executing any unintended instructions.

Stack Area:

The stack is declared with the .comm directive, which sets aside memory for the stack in the final binary.

multiboot.h

/* multiboot.h - Multiboot header file
 *
 * This file contains the definitions and structures necessary for interacting with
 * the Multiboot Specification, which defines a standard for booting operating systems.
 * It provides a uniform interface between bootloaders and kernels.
 */

#ifndef MULTIBOOT_HEADER
#define MULTIBOOT_HEADER 1

/* How many bytes from the start of the file we search for the header. */
#define MULTIBOOT_SEARCH            8192       // Search range for the Multiboot header in the boot image
#define MULTIBOOT_HEADER_ALIGN      4          // Alignment requirement for the Multiboot header

/* The magic number that should be in the 'magic' field of the Multiboot header. */
#define MULTIBOOT_HEADER_MAGIC      0x1BADB002 // Unique identifier for the Multiboot header

/* The magic number that must be passed in %eax to the kernel upon boot. */
#define MULTIBOOT_BOOTLOADER_MAGIC  0x2BADB002 // Magic number to identify the bootloader

/* Alignment for multiboot modules (page alignment). */
#define MULTIBOOT_MOD_ALIGN         0x00001000 // Alignment for loaded modules (4KB page boundary)

/* Alignment of the multiboot info structure. */
#define MULTIBOOT_INFO_ALIGN        0x00000004 // Alignment for the Multiboot information structure

/* Multiboot header flags */
#define MULTIBOOT_PAGE_ALIGN        0x00000001 // Align modules on page (4KB) boundaries
#define MULTIBOOT_MEMORY_INFO       0x00000002 // Provide memory information to the OS
#define MULTIBOOT_VIDEO_MODE        0x00000004 // Provide video mode information to the OS
#define MULTIBOOT_AOUT_KLUDGE       0x00010000 // Use address fields in the header (a.out kludge)

/* Flags to be set in the 'flags' member of the multiboot info structure. */
#define MULTIBOOT_INFO_MEMORY       0x00000001 // Memory information available
#define MULTIBOOT_INFO_BOOTDEV      0x00000002 // Boot device information available
#define MULTIBOOT_INFO_CMDLINE      0x00000004 // Command line information available
#define MULTIBOOT_INFO_MODS         0x00000008 // Module information available

/* Flags for mutually exclusive sections in the multiboot info structure. */
#define MULTIBOOT_INFO_AOUT_SYMS    0x00000010 // a.out symbol table available
#define MULTIBOOT_INFO_ELF_SHDR     0x00000020 // ELF section header table available

#define MULTIBOOT_INFO_MEM_MAP      0x00000040 // Full memory map available
#define MULTIBOOT_INFO_DRIVE_INFO   0x00000080 // Drive information available
#define MULTIBOOT_INFO_CONFIG_TABLE 0x00000100 // Configuration table available
#define MULTIBOOT_INFO_BOOT_LOADER_NAME 0x00000200 // Boot loader name available
#define MULTIBOOT_INFO_APM_TABLE    0x00000400 // APM table available
#define MULTIBOOT_INFO_VBE_INFO     0x00000800 // VBE (VESA BIOS Extensions) information available
#define MULTIBOOT_INFO_FRAMEBUFFER_INFO 0x00001000 // Framebuffer information available

#ifndef ASM_FILE

/* Type definitions for specific data sizes */
typedef unsigned char           multiboot_uint8_t;   // 8-bit unsigned integer
typedef unsigned short          multiboot_uint16_t;  // 16-bit unsigned integer
typedef unsigned int            multiboot_uint32_t;  // 32-bit unsigned integer
typedef unsigned long long      multiboot_uint64_t;  // 64-bit unsigned integer

/* Multiboot header structure
 *
 * This structure is used by the kernel to communicate its loading requirements to the bootloader.
 * It contains fields for memory addresses, entry points, and flags that dictate how the kernel should be loaded.
 */
struct multiboot_header {
    multiboot_uint32_t magic;           // Must be MULTIBOOT_HEADER_MAGIC
    multiboot_uint32_t flags;           // Feature flags
    multiboot_uint32_t checksum;        // Checksum of the above fields; should sum to zero with magic and flags

    /* These fields are only valid if MULTIBOOT_AOUT_KLUDGE is set */
    multiboot_uint32_t header_addr;     // The address of the header
    multiboot_uint32_t load_addr;       // The load address of the kernel image
    multiboot_uint32_t load_end_addr;   // The end address of the loadable image
    multiboot_uint32_t bss_end_addr;    // The end address of the bss (uninitialized data)
    multiboot_uint32_t entry_addr;      // The entry point of the kernel

    /* These fields are only valid if MULTIBOOT_VIDEO_MODE is set */
    multiboot_uint32_t mode_type;       // Video mode type requested
    multiboot_uint32_t width;           // Screen width
    multiboot_uint32_t height;          // Screen height
    multiboot_uint32_t depth;           // Bits per pixel
};

/* The symbol table for a.out binaries */
struct multiboot_aout_symbol_table {
    multiboot_uint32_t tabsize;         // Size of the symbol table
    multiboot_uint32_t strsize;         // Size of the string table
    multiboot_uint32_t addr;            // Address of the symbol table
    multiboot_uint32_t reserved;        // Reserved, must be zero
};
typedef struct multiboot_aout_symbol_table multiboot_aout_symbol_table_t;

/* The section header table for ELF binaries
 *
 * This structure contains information about the ELF sections in the kernel image,
 * including the number of sections, their size, and the address of the section headers.
 */
struct multiboot_elf_section_header_table {
    multiboot_uint32_t num;             // Number of section headers
    multiboot_uint32_t size;            // Size of each section header
    multiboot_uint32_t addr;            // Address of the section header table
    multiboot_uint32_t shndx;           // Index of the string table section header
};
typedef struct multiboot_elf_section_header_table multiboot_elf_section_header_table_t;

/* Multiboot information structure
 *
 * This structure is provided by the bootloader to the kernel and contains information
 * about the boot process, including memory layout, modules loaded, and other boot-related data.
 */
struct multiboot_info {
    multiboot_uint32_t flags;           // Flags indicating which fields are valid

    /* Available memory from BIOS */
    multiboot_uint32_t mem_lower;       // Amount of lower memory (in KB)
    multiboot_uint32_t mem_upper;       // Amount of upper memory (in KB)

    /* "root" partition */
    multiboot_uint32_t boot_device;     // Boot device

    /* Kernel command line */
    multiboot_uint32_t cmdline;         // Address of the command line string

    /* Boot-Module list */
    multiboot_uint32_t mods_count;      // Number of boot modules loaded
    multiboot_uint32_t mods_addr;       // Address of the first boot module structure

    union {
        multiboot_aout_symbol_table_t aout_sym;      // a.out symbol table
        multiboot_elf_section_header_table_t elf_sec; // ELF section header table
    } u;

    /* Memory Mapping buffer */
    multiboot_uint32_t mmap_length;     // Length of the memory map buffer
    multiboot_uint32_t mmap_addr;       // Address of the memory map buffer

    /* Drive Info buffer */
    multiboot_uint32_t drives_length;   // Length of the drive information buffer
    multiboot_uint32_t drives_addr;     // Address of the drive information buffer

    /* ROM configuration table */
    multiboot_uint32_t config_table;    // Address of the ROM configuration table

    /* Boot Loader Name */
    multiboot_uint32_t boot_loader_name; // Address of the bootloader name string

    /* APM table */
    multiboot_uint32_t apm_table;       // Address of the APM (Advanced Power Management) table

    /* Video information */
    multiboot_uint32_t vbe_control_info; // VBE control information
    multiboot_uint32_t vbe_mode_info;    // VBE mode information
    multiboot_uint16_t vbe_mode;         // VBE mode
    multiboot_uint16_t vbe_interface_seg; // VBE interface segment
    multiboot_uint16_t vbe_interface_off; // VBE interface offset
    multiboot_uint16_t vbe_interface_len; // VBE interface length

    multiboot_uint64_t framebuffer_addr; // Physical address of the framebuffer
    multiboot_uint32_t framebuffer_pitch; // Number of bytes per scanline in the framebuffer
    multiboot_uint32_t framebuffer_width; // Width of the framebuffer in pixels
    multiboot_uint32_t framebuffer_height; // Height of the framebuffer in pixels
    multiboot_uint8_t framebuffer_bpp;   // Bits per pixel in the framebuffer
    #define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 // Indexed color framebuffer
    #define MULTIBOOT_FRAMEBUFFER_TYPE_RGB     1 // Direct RGB color framebuffer
    #define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 // EGA text mode framebuffer
    multiboot_uint8_t framebuffer_type;  // Framebuffer type (indexed, RGB, or EGA text)
    
    union {
        struct {
            multiboot_uint32_t framebuffer_palette_addr; // Address of the palette table
            multiboot_uint16_t framebuffer_palette_num_colors; // Number of colors in the palette
        };
        struct {
            multiboot_uint8_t framebuffer_red_field_position;   // Position of the red color field
            multiboot_uint8_t framebuffer_red_mask_size;        // Size of the red color mask
            multiboot_uint8_t framebuffer_green_field_position; // Position of the green color field
            multiboot_uint8_t framebuffer_green_mask_size;      // Size of the green color mask
            multiboot_uint8_t framebuffer_blue_field_position;  // Position of the blue color field
            multiboot_uint8_t framebuffer_blue_mask_size;       // Size of the blue color mask
        };
    };
};
typedef struct multiboot_info multiboot_info_t;

/* RGB color structure used in framebuffer */
struct multiboot_color {
    multiboot_uint8_t red;   // Red component of the color
    multiboot_uint8_t green; // Green component of the color
    multiboot_uint8_t blue;  // Blue component of the color
};

/* Memory map entry structure
 *
 * This structure represents a single entry in the memory map, providing details
 * about a specific memory range, including its size, address, and type.
 */
struct multiboot_mmap_entry {
    multiboot_uint32_t size;  // Size of the structure
    multiboot_uint64_t addr;  // Start address of the memory region
    multiboot_uint64_t len;   // Length of the memory region
    #define MULTIBOOT_MEMORY_AVAILABLE              1 // Available memory
    #define MULTIBOOT_MEMORY_RESERVED               2 // Reserved memory
    #define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE       3 // ACPI reclaimable memory
    #define MULTIBOOT_MEMORY_NVS                    4 // NVS memory
    #define MULTIBOOT_MEMORY_BADRAM                 5 // Bad RAM
    multiboot_uint32_t type;  // Type of memory region
} __attribute__((packed));    // Ensure no padding in the structure
typedef struct multiboot_mmap_entry multiboot_memory_map_t;

/* Boot module structure
 *
 * This structure represents a boot module loaded by the bootloader, typically
 * used for additional drivers or initial ramdisks. It contains the start and end
 * addresses of the module, along with a command line string.
 */
struct multiboot_mod_list {
    multiboot_uint32_t mod_start; // Start address of the module
    multiboot_uint32_t mod_end;   // End address of the module
    multiboot_uint32_t cmdline;   // Command line associated with the module
    multiboot_uint32_t pad;       // Padding to align to 16 bytes
};
typedef struct multiboot_mod_list multiboot_module_t;

/* APM BIOS information structure
 *
 * This structure provides information about the APM (Advanced Power Management)
 * BIOS, including its version, segment addresses, and flags.
 */
struct multiboot_apm_info {
    multiboot_uint16_t version;      // APM version
    multiboot_uint16_t cseg;         // Code segment
    multiboot_uint32_t offset;       // Offset
    multiboot_uint16_t cseg_16;      // 16-bit code segment
    multiboot_uint16_t dseg;         // Data segment
    multiboot_uint16_t flags;        // APM flags
    multiboot_uint16_t cseg_len;     // Code segment length
    multiboot_uint16_t cseg_16_len;  // 16-bit code segment length
    multiboot_uint16_t dseg_len;     // Data segment length
};

#endif /* ! ASM_FILE */

#endif /* ! MULTIBOOT_HEADER */

Summary of the Documented multiboot.h:

Header Guards: Prevent multiple inclusions of the file with #ifndef MULTIBOOT_HEADER.
Magic Numbers: Magic numbers and alignment constraints define how the Multiboot header is structured and recognized.
Multiboot Header Structure: Contains fields that dictate how the kernel should be loaded by the bootloader, such as memory addresses, entry points, and flags.
Flags: A series of macros define the meaning of the flags in the header and information structures, guiding the bootloader on how to handle different parts of the kernel image.
Multiboot Information Structure: Passed from the bootloader to the kernel, providing critical data about the boot environment, including memory maps, module loading, and framebuffer details.
Support for Various Memory and Module Types: Structures like multiboot_mmap_entry and multiboot_mod_list provide detailed descriptions of memory regions and boot modules.

kernel.c

/* kernel.c - the C part of the kernel
 *
 * This program is part of a simple kernel that interacts with the Multiboot specification,
 * displaying boot information and handling basic screen output.
 */

#include <multiboot.h>

/* Screen properties */
#define COLUMNS     80      // Number of columns on the screen
#define LINES       24      // Number of lines on the screen
#define ATTRIBUTE   7       // Character attribute (color) for display
#define VIDEO       0xB8000 // Video memory starting address (text mode)

/* Macros */
/* CHECK_FLAG - Macro to check if a specific bit (bit) is set in flags. */
#define CHECK_FLAG(flags, bit)   ((flags) & (1 << (bit)))

/* Variables */
static int xpos = 0; // Current X position (column) on the screen
static int ypos = 0; // Current Y position (row) on the screen
static volatile unsigned char *video = (unsigned char *) VIDEO; // Pointer to video memory

/* Function Prototypes */
void cmain(unsigned long magic, unsigned long addr);
static void cls(void);
static void putchar(int c);
static void itoa(char *buf, int base, int d);
void printf(const char *format, ...);

/* cmain - Kernel entry point.
 * This function is called by the bootloader after the kernel is loaded.
 * It checks the Multiboot magic number, displays boot information, and performs
 * basic screen output.
 *
 * Parameters:
 *   magic - The magic number provided by the Multiboot-compliant bootloader.
 *   addr  - The address of the Multiboot information structure.
 */
void cmain(unsigned long magic, unsigned long addr) {
    multiboot_info_t *mbi;

    // Clear the screen
    cls();

    // Validate the Multiboot magic number
    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 from the Multiboot information structure
    printf("flags = 0x%x\n", (unsigned)mbi->flags);

    // Display available memory information if available
    if (CHECK_FLAG(mbi->flags, 0)) 
        printf("mem_lower = %uKB, mem_upper = %uKB\n", (unsigned)mbi->mem_lower, (unsigned)mbi->mem_upper);

    // Display boot device information if available
    if (CHECK_FLAG(mbi->flags, 1))
        printf("boot_device = 0x%x\n", (unsigned)mbi->boot_device);

    // Display command line if available
    if (CHECK_FLAG(mbi->flags, 2))
        printf("cmdline = %s\n", (char *)mbi->cmdline);

    // Display module information if available
    if (CHECK_FLAG(mbi->flags, 3)) {
        multiboot_module_t *mod = (multiboot_module_t *)mbi->mods_addr;
        for (int i = 0; i < mbi->mods_count; i++, mod++) {
            printf("mod_start = 0x%x, mod_end = 0x%x, cmdline = %s\n", 
                    (unsigned)mod->mod_start, (unsigned)mod->mod_end, (char *)mod->cmdline);
        }
    }

    // Ensure that either a.out symbol table or ELF section header table is set, but not both
    if (CHECK_FLAG(mbi->flags, 4) && CHECK_FLAG(mbi->flags, 5)) {
        printf("Both a.out and ELF headers are set!\n");
        return;
    }

    // Display a.out symbol table information if available
    if (CHECK_FLAG(mbi->flags, 4)) {
        multiboot_aout_symbol_table_t *aout_sym = &mbi->u.aout_sym;
        printf("aout_symbol_table: tabsize = 0x%x, strsize = 0x%x, addr = 0x%x\n",
               (unsigned)aout_sym->tabsize, (unsigned)aout_sym->strsize, (unsigned)aout_sym->addr);
    }

    // Display ELF section header table information if available
    if (CHECK_FLAG(mbi->flags, 5)) {
        multiboot_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",
               elf_sec->num, elf_sec->size, elf_sec->addr, elf_sec->shndx);
    }

    // Display memory map information if available
    if (CHECK_FLAG(mbi->flags, 6)) {
        multiboot_memory_map_t *mmap = (multiboot_memory_map_t *)mbi->mmap_addr;
        printf("mmap_addr = 0x%x, mmap_length = 0x%x\n", mbi->mmap_addr, mbi->mmap_length);

        while ((unsigned long)mmap < mbi->mmap_addr + mbi->mmap_length) {
            printf("size = 0x%x, base_addr = 0x%x%08x, length = 0x%x%08x, type = 0x%x\n",
                   mmap->size, (unsigned)(mmap->addr >> 32), (unsigned)mmap->addr, 
                   (unsigned)(mmap->len >> 32), (unsigned)mmap->len, mmap->type);
            mmap = (multiboot_memory_map_t *)((unsigned long)mmap + mmap->size + sizeof(mmap->size));
        }
    }

    // Display a diagonal line on the screen if framebuffer information is available
    if (CHECK_FLAG(mbi->flags, 12)) {
        multiboot_uint32_t color;
        void *fb = (void *)(unsigned long)mbi->framebuffer_addr;

        // Determine the color to use based on the framebuffer type
        switch (mbi->framebuffer_type) {
            case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED:
                color = 0;
                for (unsigned i = 0; i < mbi->framebuffer_palette_num_colors; i++) {
                    struct multiboot_color *palette = (struct multiboot_color *)mbi->framebuffer_palette_addr;
                    if ((0xff - palette[i].blue) < color) color = i;
                }
                break;
            case MULTIBOOT_FRAMEBUFFER_TYPE_RGB:
                color = ((1 << mbi->framebuffer_blue_mask_size) - 1) << mbi->framebuffer_blue_field_position;
                break;
            case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT:
                color = '\\' | 0x0100;
                break;
            default:
                color = 0xFFFFFFFF;
                break;
        }

        // Draw the diagonal line on the framebuffer
        for (unsigned i = 0; i < mbi->framebuffer_width && i < mbi->framebuffer_height; i++) {
            switch (mbi->framebuffer_bpp) {
                case 8:  ((multiboot_uint8_t  *)fb + mbi->framebuffer_pitch * i + i)[0] = color; break;
                case 16: ((multiboot_uint16_t *)fb + mbi->framebuffer_pitch * i + i)[0] = color; break;
                case 24: ((multiboot_uint32_t *)fb + mbi->framebuffer_pitch * i + 3 * i)[0] = color; break;
                case 32: ((multiboot_uint32_t *)fb + mbi->framebuffer_pitch * i + 4 * i)[0] = color; break;
            }
        }
    }
}

/* cls - Clears the screen and resets cursor position.
 * This function clears the video memory by setting all characters to zero,
 * and resets the cursor position to the top-left corner.
 */
static void cls(void) {
    for (int i = 0; i < COLUMNS * LINES * 2; i++) video[i] = 0;
    xpos = ypos = 0;
}

/* itoa - Converts an integer to a string.
 * This function converts the integer D into a null-terminated string in BUF.
 * The conversion is done in the specified BASE (e.g., 10 for decimal, 16 for hex).
 *
 * Parameters:
 *   buf  - The buffer to store the resulting string.
 *   base - The numerical base to use for the conversion (e.g., 'd' for decimal, 'x' for hex).
 *   d    - The integer to convert.
 */
static void itoa(char *buf, int base, int d) {
    char *p = buf, *p1, *p2;
    unsigned long ud = (d < 0 && base == 10) ? -d : d;

    // Convert the number to the specified base
    do {
        *p++ = "0123456789abcdef"[ud % base];
    } while (ud /= base);

    // Add negative sign for decimal numbers if needed
    if (d < 0 && base == 10) *p++ = '-';

    *p = 0; // Null-terminate the string

    // Reverse the string in place
    p1 = buf;
    p2 = p - 1;
    while (p1 < p2) {
        char tmp = *p1;
        *p1++ = *p2;
        *p2-- = tmp;
    }
}

/* putchar - Displays a character on the screen.
 * This function outputs a character C to the screen at the current cursor position.
 * It handles line wrapping and newline characters.
 *
 * Parameters:
 *   c - The character to display.
 */
static void putchar(int c) {
    if (c == '\n' || c == '\r') {
        xpos = 0;
        if (++ypos >= LINES) ypos = 0;
        return;
    }

    // Place the character and its attribute into video memory
    video[(xpos + ypos * COLUMNS) * 2] = c;
    video[(xpos + ypos * COLUMNS) * 2 + 1] = ATTRIBUTE;

    // Move the cursor to the next position
    if (++xpos >= COLUMNS) {
        xpos = 0;
        if (++ypos >= LINES) ypos = 0;
    }
}

/* printf - Formats and prints a string to the screen.
 * This function works similarly to the standard C printf function, but outputs directly
 * to the screen. It supports basic format specifiers such as %d, %x, and %s.
 *
 * Parameters:
 *   format - The format string containing text and format specifiers.
 *   ...    - Additional arguments that match the format specifiers.
 */
void printf(const char *format, ...) {
    char **arg = (char **)&format;
    char buf[20];
    arg++;

    for (char c; (c = *format++); ) {
        if (c != '%') {
            putchar(c);
        } else {
            char *p;
            c = *format++;
            if (c == 'd' || c == 'x') {
                itoa(buf, c == 'd' ? 10 : 16, *((int *)arg++));
                p = buf;
            } else if (c == 's') {
                p = *arg++ ? *arg : "(null)";
            } else {
                putchar(*((int *)arg++));
                continue;
            }

            while (*p) putchar(*p++);
        }
    }
}


Implementation

To create a simple operating system or bootable kernel using boot.S, multiboot.h, and kernel.c, you’ll follow a series of steps that involve compiling and linking these files, creating a bootable image, and then testing it using an emulator or on actual hardware. Here’s a detailed explanation of how to use these files together:

1. Understanding the Components

  • boot.S:
    • This is the assembly file responsible for the very initial setup when your kernel is loaded by a Multiboot-compliant bootloader (like GRUB).
    • It sets up the CPU state, initializes the stack, and then transfers control to the cmain function in kernel.c.
    • It includes the Multiboot header, which the bootloader uses to verify that your kernel is Multiboot-compliant and to learn how to load it.
  • multiboot.h:
    • This is a header file that defines the structures and constants used by the Multiboot Specification.
    • It provides definitions that allow kernel.c to interact with the Multiboot information structure passed by the bootloader. This includes details like memory maps, module information, and boot device information.
  • kernel.c:
    • This is the main C file that contains the kernel’s logic after the initial boot process.
    • It starts with the cmain function, which is called by boot.S after the CPU and environment are set up.
    • This file reads the Multiboot information provided by the bootloader and performs initial kernel tasks, such as displaying system information on the screen.

2. Compiling the Code

You need to compile the assembly and C code and link them together to create a bootable kernel binary.

a. Compile boot.S:

nasm -f elf -o boot.o boot.S

This command assembles boot.S into an object file (boot.o). The -f elf option specifies the output format as ELF (Executable and Linkable Format), which is typical for Linux binaries.

b. Compile kernel.c:

gcc -m32 -c -o kernel.o kernel.c -I.

This command compiles kernel.c into an object file (kernel.o). The -m32 flag tells GCC to compile in 32-bit mode (since we’re working with a 32-bit OS). The -I. flag tells GCC to include the current directory when searching for header files like multiboot.h.

c. Linking:

ld -m elf_i386 -Ttext 0x100000 -o kernel.bin boot.o kernel.o --oformat binary

This command links the object files into a single binary (kernel.bin). The -Ttext 0x100000 option sets the starting address of the text segment (code) to 0x100000, where the kernel will be loaded. The --oformat binary option ensures that the output is a flat binary, suitable for booting.

3. Creating a Bootable Image

After creating kernel.bin, you need to combine it with a bootloader to create a bootable disk image.

a. Create a GRUB Bootable ISO:

  • First, create the directory structure for GRUB: mkdir -p isodir/boot/grub
  • Copy kernel.bin to the boot directory: cp kernel.bin isodir/boot/kernel.bin
  • Create a GRUB configuration file isodir/boot/grub/grub.cfg: set timeout=0 set default=0 menuentry "My OS" { multiboot /boot/kernel.bin boot }
  • Finally, create the ISO image using grub-mkrescue: grub-mkrescue -o myos.iso isodir

4. Testing the Kernel

You can test your kernel using an emulator like QEMU or on actual hardware.

a. Testing with QEMU:

qemu-system-i386 -cdrom myos.iso

This command starts QEMU and boots from the myos.iso file you created.

b. Testing on Real Hardware:

  • Burn the myos.iso to a CD, DVD, or USB drive using tools like dd or Rufus (for Windows).
  • Boot your computer from the created bootable media.

5. Understanding the Boot Process

  1. Bootloader Execution:
    • The BIOS loads the bootloader (e.g., GRUB) from the bootable media.
    • GRUB reads the Multiboot header from boot.S and loads your kernel (kernel.bin) into memory, passing control to the entry point defined in boot.S.
  2. Execution of boot.S:
    • boot.S sets up the stack and CPU state and jumps to multiboot_entry.
    • It then calls the cmain function in kernel.c, passing the Multiboot information structure.
  3. Kernel Execution (kernel.c):
    • The cmain function in kernel.c processes the Multiboot information, such as available memory, loaded modules, and other boot parameters.
    • The kernel can then proceed with its initialization routines, like setting up hardware, loading drivers, and eventually running user-space programs.

6. Extending Your Kernel

After successfully booting your kernel, you can extend it by:

  • Adding more hardware drivers.
  • Implementing memory management.
  • Creating a file system.
  • Developing a simple shell or user interface.

Each of these steps builds upon the foundation laid by boot.S, multiboot.h, and kernel.c.

Conclusion

By following these steps, you can successfully create, compile, and test a simple operating system kernel using boot.S, multiboot.h, and kernel.c. This process is fundamental for understanding low-level OS development and provides a solid base for building more complex kernel features.

Generic header

This header is generalized and can be applied to any software project:

/* <filename> - <brief description of the file>
 *
 * Copyright (C) <year> <Your Name or Your Organization>
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef <FILENAME>_H
#define <FILENAME>_H

/* Your code goes here */

#endif /* <FILENAME>_H */

Explanation:

  • : Replace this with the actual filename or a brief description of the file.
  • : Provide a short description of what the file contains or its purpose.
  • : Replace this with the current year.
  • : Replace this with your name or your organization’s name.

This header provides a legal framework for the distribution and use of your software while clearly indicating that it is provided “as is” without warranties.

References

Here are some references that provide detailed information on the Multiboot specification and its implementation:

  1. Multiboot Specification 0.6.96:
    • Link: GNU Multiboot Specification
    • Description: This is the official documentation for the Multiboot specification, which is maintained by the GNU project. It details the format, requirements, and fields of the Multiboot header, as well as the structure of the information passed to the kernel by the bootloader.
  2. GRUB Documentation:
    • Link: GNU GRUB Manual
    • Description: The GRUB manual provides a detailed overview of how GRUB, a popular bootloader, implements the Multiboot specification. It includes practical examples and configurations for booting various Multiboot-compliant kernels.
  3. OSDev Wiki – Multiboot:
    • Link: OSDev Wiki – Multiboot
    • Description: The OSDev Wiki is a community-driven resource for operating system development. The Multiboot section provides a practical overview of the Multiboot specification, examples of Multiboot headers, and instructions for writing a Multiboot-compliant kernel.
  4. “Operating Systems: From 0 to 1” – Multiboot:
    • Link: Operating Systems: From 0 to 1 – Multiboot
    • Description: This resource is part of a broader tutorial on building an operating system from scratch. It includes a section on Multiboot, explaining how to create a Multiboot header and how to structure an OS image to be Multiboot-compliant.
  5. GitHub Repositories and Example Projects:
    • Link: GitHub Search for Multiboot
    • Description: Searching GitHub for “Multiboot” will provide numerous example projects and open-source kernels that implement the Multiboot specification. These can serve as practical examples and references for your own implementations.

These resources should provide a comprehensive understanding of the Multiboot specification and how to implement it in your own projects.