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:
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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 inkernel.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 byboot.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 likedd
orRufus
(for Windows). - Boot your computer from the created bootable media.
5. Understanding the Boot Process
- 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 inboot.S
.
- Execution of
boot.S
:boot.S
sets up the stack and CPU state and jumps tomultiboot_entry
.- It then calls the
cmain
function inkernel.c
, passing the Multiboot information structure.
- Kernel Execution (
kernel.c
):- The
cmain
function inkernel.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.
- The
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:
- 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.
- 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.
- 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.
- “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.
- 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.