This time we will try to write a basic simple multi boot bootloader that will print something on the screen.
Let's start creating a Multiboot-compliant kernel
start.asm
:
[BITS 32]
global start
start:
mov esp, _sys_stack
jmp stublet
ALIGN 4
mboot:
MULTIBOOT_PAGE_ALIGN equ 1<<0
MULTIBOOT_MEMORY_INFO equ 1<<1
MULTIBOOT_AOUT_KLUDGE equ 1<<16
MULTIBOOT_HEADER_MAGIC equ 0x1BADB002
MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
MULTIBOOT_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
EXTERN code, bss, end
; GRUB Multiboot header, boot signature
dd MULTIBOOT_HEADER_MAGIC
dd MULTIBOOT_HEADER_FLAGS
dd MULTIBOOT_CHECKSUM
; AOUT kludge (must be physical addresses)
; Linker script fills these in
dd mboot
dd code
dd bss
dd end
dd start
; Main entrypoint
stublet:
extern main
call main
jmp $
; GDT
; Interrupt Service Routines
; BSS Section
SECTION .bss
resb 8192 ; 8KB of memory reserved
_sys_stack:
; This line intentionally left blank
Explanation:
1 Setting Up 32-bit Mode
- The code begins with setting up 32-bit mode by specifying
[BITS 32]
2 Defining the Start Label
- The start label is defined as the entry point of the kernel. It initializes the stack pointer and jumps to the
stublet
label.
global start
start:
mov esp, _sys_stack
jmp stublet
mov esp, _sys_stack
: Initializes the stack pointer to the top of the stack.jmp stublet
: Jumps to thestublet
label where the main routine will be called.
3 Defining the Multiboot Header:
The Multiboot header is defined next. This header is essential for the bootloader (e.g., GRUB) to recognize and load the kernel:
ALIGN 4
mboot:
MULTIBOOT_PAGE_ALIGN equ 1<<0
MULTIBOOT_MEMORY_INFO equ 1<<1
MULTIBOOT_AOUT_KLUDGE equ 1<<16
MULTIBOOT_HEADER_MAGIC equ 0x1BADB002
MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
MULTIBOOT_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
EXTERN code, bss, end
; GRUB Multiboot header, boot signature
dd MULTIBOOT_HEADER_MAGIC
dd MULTIBOOT_HEADER_FLAGS
dd MULTIBOOT_CHECKSUM
; AOUT kludge (must be physical addresses)
; Linker script fills these in
dd mboot
dd code
dd bss
dd end
dd start
- Constants:
MULTIBOOT_PAGE_ALIGN
: Indicates that the bootloader should load the modules aligned on page (4KB) boundaries.MULTIBOOT_MEMORY_INFO
: Requests memory information from the bootloader.MULTIBOOT_AOUT_KLUDGE
: Indicates that the kernel image is in a.out format.MULTIBOOT_HEADER_MAGIC
: The magic number that identifies the header (0x1BADB002
).MULTIBOOT_HEADER_FLAGS
: Flags indicating the requirements of the kernel.MULTIBOOT_CHECKSUM
: A checksum that, when added to the magic number and flags, results in zero.
- Multiboot Header Fields:
dd MULTIBOOT_HEADER_MAGIC
: The magic number.dd MULTIBOOT_HEADER_FLAGS
: The flags.dd MULTIBOOT_CHECKSUM
: The checksum.dd mboot, code, bss, end
: Addresses for AOUT kludge.dd start
: The entry point of the kernel.
4 Main Entry Point
The stublet
label calls the main function and then enters an infinite loop:
stublet:
extern main
call main
jmp $
extern main
: Declares themain
function as an external symbol.call main
: Calls themain
function.jmp $
: Enters an infinite loop to prevent the CPU from executing undefined behavior aftermain
returns.
5 Defining the BSS Section
The .bss
section is used for uninitialized data. Here, it reserves 8KB of memory for the stack:
; BSS Section
SECTION .bss
resb 8192 ; 8KB of memory reserved
_sys_stack:
; This line intentionally left blank
resb 8192
: Reserves 8KB of uninitialized space._sys_stack
: Label marking the beginning of the stack.
main.c
:
#include <system.h>
/*
* memcpy
* Copy from source to destination. Assumes that
* source and destination are not overlapping.
*/
unsigned char *
memcpy(
unsigned char *dest,
const unsigned char *src,
int count
) {
int i;
i = 0;
for ( ; i < count; ++i ) {
dest[i] = src[i];
}
return dest;
}
/*
* memset
* Set `count` bytes to `val`.
*/
unsigned char *
memset(
unsigned char *dest,
unsigned char val,
int count
) {
int i;
i = 0;
for ( ; i < count; ++i ) {
dest[i] = val;
}
return dest;
}
/*
* memsetw
* Set `count` shorts to `val`.
*/
unsigned short *
memsetw(
unsigned short *dest,
unsigned short val,
int count
) {
int i;
i = 0;
for ( ; i < count; ++i ) {
dest[i] = val;
}
return dest;
}
/*
* strlen
* Returns the length of a given `str`.
*/
int
strlen(
const char *str
) {
int i = 0;
while (str[i] != (char)0) {
++i;
}
return i;
}
/*
* inportb
* Read from an I/O port.
*/
unsigned char
inportb(
unsigned short _port
) {
unsigned char rv;
__asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (_port));
return rv;
}
/*
* outportb
* Write to an I/O port.
*/
void
outportb(
unsigned short _port,
unsigned char _data
) {
__asm__ __volatile__ ("outb %1, %0" : : "dN" (_port), "a" (_data));
}
/*
* Kernel Entry Point
*/
int
main() {
init_video();
puts("Hello world!\n");
for (;;);
return 0;
}
This code snippet includes several basic utility function for memory operations, I/O port operations, and a simple kernel entry point for a bare-metal system.
Utility Functions:
1 memcpy
:
The memcpy
function copies count
bytes from the source (src
) to the destination (dest
). It assumes that the source and destination do not overlap.
unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count) {
int i;
i = 0;
for (; i < count; ++i) {
dest[i] = src[i];
}
return dest;
}
- Parameters:
dest
: Destination buffer where data will be copied.src
: Source buffer from where data will be copied.count
: Number of bytes to copy.
- Returns: The destination buffer.
2 memset
:
The memset
function sets count
bytes of the destination buffer (dest
) to the value val:
unsigned char *memset(unsigned char *dest, unsigned char val, int count) {
int i;
i = 0;
for (; i < count; ++i) {
dest[i] = val;
}
return dest;
}
- Parameters:
dest
: Destination buffer to set the value.val
: Value to set.count
: Number of bytes to set.
- Returns: The destination buffer.
3 memsetw
:
The memsetw
function sets count
shorts (2 bytes each) of the destination buffer (dest
) to the value val
.
unsigned short *memsetw(unsigned short *dest, unsigned short val, int count) {
int i;
i = 0;
for (; i < count; ++i) {
dest[i] = val;
}
return dest;
}
- Parameters:
dest
: Destination buffer to set the value.val
: Value to set.count
: Number of shorts to set.
- Returns: The destination buffer.
4 strlen
:
The strlen
function calculates the length of the given string (str
).
int strlen(const char *str) {
int i = 0;
while (str[i] != (char)0) {
++i;
}
return i;
}
- Parameters:
str
: Null-terminated string to calculate the length.
- Returns: The length of the string.
5 inportb
:
The inportb
function reads a byte from an I/O port.
unsigned char inportb(unsigned short _port) {
unsigned char rv;
__asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (_port));
return rv;
}
- Parameters:
_port
: I/O port address to read from.
- Returns: The byte read from the I/O port.
6 outportb
:
The outportb
function writes a byte to an I/O port.
void outportb(unsigned short _port, unsigned char _data) {
__asm__ __volatile__ ("outb %1, %0" : : "dN" (_port), "a" (_data));
}
Parameters:
_port
: I/O port address to write to._data
: Data byte to write.
Kernel Entry Point:
The main
function is the entry point of the kernel. It initializes the video and prints "Hello world!" to the screen, then enters an infinite loop.
int main() {
init_video();
puts("Hello world!\n");
for (;;);
return 0;
}
init_video
: Initializes the video display (implementation not shown in the provided code).puts
: Prints a string to the screen (implementation not shown in the provided code).- Infinite Loop: Prevents the kernel from exiting, keeping the system running indefinitely.
vga.c
:
#include <system.h>
/*
* Text pointer, background, foreground
*/
unsigned short * textmemptr;
int attrib = 0x0F;
int csr_x = 0, csr_y = 0;
/*
* scroll
* Scroll the screen
*/
void
scroll() {
unsigned blank, temp;
blank = 0x20 | (attrib << 8);
if (csr_y >= 25) {
/*
* Move the current text chunk that makes up the screen
* back in the buffer by one line.
*/
temp = csr_y - 25 + 1;
memcpy(textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
/*
* Set the chunk of memory that occupies
* the last line of text to the blank character
*/
memsetw(textmemptr + (25 - temp) * 80, blank, 80);
csr_y = 25 - 1;
}
}
/*
* move_csr
* Update the hardware cursor
*/
void
move_csr() {
unsigned temp;
temp = csr_y * 80 + csr_x;
/*
* Write stuff out.
*/
outportb(0x3D4, 14);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 15);
outportb(0x3D5, temp);
}
/*
* cls
* Clear the screen
*/
void
cls() {
unsigned blank;
int i;
blank = 0x20 | (attrib << 8);
for (i = 0; i < 25; ++i) {
memsetw(textmemptr + i * 80, blank, 80);
}
csr_x = 0;
csr_y = 0;
move_csr();
}
/*
* putch
* Puts a character to the screen
*/
void
putch(unsigned char c) {
unsigned short *where;
unsigned att = attrib << 8;
if (c == 0x08) {
/* Backspace */
if (csr_x != 0) csr_x--;
} else if (c == 0x09) {
/* Tab */
csr_x = (csr_x + 8) & ~(8 - 1);
} else if (c == '\r') {
/* Carriage return */
csr_x = 0;
} else if (c == '\n') {
/* New line */
csr_x = 0;
csr_y++;
} else if (c >= ' ') {
where = textmemptr + (csr_y * 80 + csr_x);
*where = c | att;
csr_x++;
}
if (csr_x >= 80) {
csr_x = 0;
csr_y++;
}
scroll();
move_csr();
}
/*
* puts
* Put string to screen
*/
void
puts(
unsigned char * text
){
int i;
int len = strlen(text);
for (i = 0; i < len; ++i) {
putch(text[i]);
}
}
/*
* settextcolor
* Sets the foreground and background color
*/
void
settextcolor(
unsigned char forecolor,
unsigned char backcolor
) {
attrib = (backcolor << 4) | (forecolor & 0x0F);
}
/*
* init_video
* Initialize the VGA driver.
*/
void init_video() {
textmemptr = (unsigned short *)0xB8000;
cls();
}
This C code snippet is an implementation of a basic text-based VGA driver for a simple operating system kernel. It provides functions to manipulate the screen, such as scrolling, moving the cursor, clearing the screen, printing characters and strings, and setting text colors. Let's go through each part of the code in detail.
Global Variables
unsigned short *textmemptr;
int attrib = 0x0F;
int csr_x = 0, csr_y = 0;
textmemptr
: A pointer to the start of video memory (typically at0xB8000
for color text mode on VGA).attrib
: Stores the current text attributes (foreground and background color).csr_x
andcsr_y
: Track the cursor position on the screen.
scroll:
The scroll
function scrolls the screen when the cursor goes beyond the last line.
void scroll() {
unsigned blank, temp;
blank = 0x20 | (attrib << 8); // Blank character with current attributes
if (csr_y >= 25) {
temp = csr_y - 25 + 1;
memcpy(textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
memsetw(textmemptr + (25 - temp) * 80, blank, 80);
csr_y = 25 - 1;
}
}
blank
: Represents a blank character with the current attributes.temp
: Calculates the number of lines to scroll.memcpy
: Moves the screen content up by one line.memsetw
: Clears the last line.
move_csr:
The move_csr
function updates the hardware cursor position.
void move_csr() {
unsigned temp;
temp = csr_y * 80 + csr_x;
outportb(0x3D4, 14);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 15);
outportb(0x3D5, temp);
}
temp
: Calculates the cursor position in video memory.outportb
: Writes the cursor position to the VGA hardware registers.
cls:
The cls
function clears the screen by filling it with blank characters.
void cls() {
unsigned blank;
int i;
blank = 0x20 | (attrib << 8);
for (i = 0; i < 25; ++i) {
memsetw(textmemptr + i * 80, blank, 80);
}
csr_x = 0;
csr_y = 0;
move_csr();
}
blank
: Represents a blank character with the current attributes.memsetw
: Clears each line of the screen.- Resets the cursor position to the top-left corner and updates the hardware cursor.
putch:
The putch
function prints a single character to the screen, handling special characters like backspace, tab, carriage return, and newline.
void putch(unsigned char c) {
unsigned short *where;
unsigned att = attrib << 8;
if (c == 0x08) {
if (csr_x != 0) csr_x--;
} else if (c == 0x09) {
csr_x = (csr_x + 8) & ~(8 - 1);
} else if (c == '\r') {
csr_x = 0;
} else if (c == '\n') {
csr_x = 0;
csr_y++;
} else if (c >= ' ') {
where = textmemptr + (csr_y * 80 + csr_x);
*where = c | att;
csr_x++;
}
if (csr_x >= 80) {
csr_x = 0;
csr_y++;
}
scroll();
move_csr();
}
- Handles backspace (
0x08
), tab (0x09
), carriage return ('\r'
), and newline ('\n'
). - Prints regular characters to the current cursor position and updates the cursor.
- Calls
scroll
andmove_csr
to handle screen scrolling and cursor updating.
puts:
The puts
function prints a string to the screen.
void puts(unsigned char *text) {
int i;
int len = strlen(text);
for (i = 0; i < len; ++i) {
putch(text[i]);
}
}
- Iterates over the string and prints each character using
putch
.
settextcolor:
The settextcolor
function sets the foreground and background color attributes.
void settextcolor(unsigned char forecolor, unsigned char backcolor) {
attrib = (backcolor << 4) | (forecolor & 0x0F);
}
- Combines the foreground and background colors into the
attrib
variable.
init_video
The init_video
function initializes the VGA driver by setting the video memory pointer and clearing the screen.
void init_video() {
textmemptr = (unsigned short *)0xB8000;
cls();
}
- Sets
textmemptr
to the base address of VGA text memory. - Calls
cls
to clear the screen.
system.h
:
#ifndef __SYSTEM_H
#define __SYSTEM_H
/*
Include Guard
The include guard ensures that the contents of the header file are only included once during compilation, preventing multiple definition errors.
*/
/*
They are declared as extern to indicate that their definitions are provided elsewhere.
*/
/* Kernel Main */
extern unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count);
extern unsigned char *memset(unsigned char *dest, unsigned char val, int count);
extern unsigned short *memsetw(unsigned short *dest, unsigned short val, int count);
extern int strlen(const char *str);
extern unsigned char inportb (unsigned short _port);
extern void outportb (unsigned short _port, unsigned char _data);
/* VGA driver */
extern void cls();
extern void putch(unsigned char c);
extern void puts(unsigned char *str);
extern void settextcolor(unsigned char forecolor, unsigned char backcolor);
extern void init_video();
#endif
link.ld
:
This linker script is used with the ld
linker to produce the final binary image of the kernel.
OUTPUT_FORMAT("binary")
ENTRY(start)
phys = 0x00100000;
SECTIONS
{
.text phys : AT(phys) {
code = .;
*(.text)
*(.rodata)
. = ALIGN(4096);
}
.data : AT(phys + (data - code))
{
data = .;
*(.data)
. = ALIGN(4096);
}
.bss : AT(phys + (bss - code))
{
bss = .;
*(.bss)
. = ALIGN(4096);
}
end = .;
}
- Overview:
- OUTPUT_FORMAT("binary"): Specifies that the output format should be a raw binary file.
- ENTRY(start): Defines the entry point of the program, which is the
start
label. - phys: A variable representing the physical address where the sections will be loaded, set to
0x00100000
(1 MB). - SECTIONS: Begins the section layout definition.
SECTIONS Block
The SECTIONS
block is where we define how different sections of the program (text, data, bss) are laid out in memory.
.text Section:
.text phys : AT(phys) {
code = .;
*(.text)
*(.rodata)
. = ALIGN(4096);
}
- .text phys : AT(phys): Places the
.text
section at the physical addressphys
(1 MB). - code = .;: Marks the beginning of the
.text
section with thecode
symbol. - *(.text): Includes all
.text
sections from the input object files. - *(.rodata): Includes all
.rodata
(read-only data) sections from the input object files. - . = ALIGN(4096);: Aligns the current location to the next 4096-byte (4 KB) boundary.
.data Section:
.data : AT(phys + (data - code)) {
data = .;
*(.data)
. = ALIGN(4096);
}
- .data : AT(phys + (data - code)): Places the
.data
section at an address calculated by adding the offset of the.data
section from the start of the.text
section tophys
. - data = .;: Marks the beginning of the
.data
section with thedata
symbol. - *(.data): Includes all
.data
sections from the input object files. - . = ALIGN(4096);: Aligns the current location to the next 4096-byte (4 KB) boundary.
.bss Section:
.bss : AT(phys + (bss - code)) {
bss = .;
*(.bss)
. = ALIGN(4096);
}
- .bss : AT(phys + (bss - code)): Places the
.bss
section at an address calculated by adding the offset of the.bss
section from the start of the.text
section tophys
. - bss = .;: Marks the beginning of the
.bss
section with thebss
symbol. - *(.bss): Includes all
.bss
sections from the input object files. - . = ALIGN(4096);: Aligns the current location to the next 4096-byte (4 KB) boundary.
End Symbol:
end = .;
- end = .;: Defines the
end
symbol to mark the end of all sections.
grub.cfg
:
The GRUB configuration file (grub.cfg
) is crucial for booting your kernel. This file provides GRUB with the necessary instructions to load and execute your kernel.
set timeout=5
set default=0
menuentry "My Multiboot Kernel" {
multiboot /boot/kernel.bin
boot
}
Explanation
- set timeout=5: Sets a 5-second timeout before the default menu entry is automatically selected.
- set default=0: Sets the first menu entry as the default.
- menuentry "My Multiboot Kernel": Defines a menu entry with the title "My Multiboot Kernel".
- multiboot /boot/kernel.bin: Specifies the path to your kernel binary and indicates that it conforms to the Multiboot specification.
- boot: Instructs GRUB to boot the specified kernel.
Makefile:
.PHONY: all clean install
all: kernel
run: install
qemu-system-x86_64 -cdrom my_os.iso
install: kernel
mkdir -p iso/boot/grub
cp kernel iso/boot/kernel.bin
cp grub.cfg iso/boot/grub/grub.cfg
grub-mkrescue -o my_os.iso iso
kernel: start.o link.ld main.o vga.o
ld -m elf_i386 -T link.ld -o kernel.bin start.o main.o vga.o
%.o: %.c
gcc -Wall -m32 -fno-pie -O0 -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -I./include -c -o $@ $<
start.o: start.asm
nasm -f elf -o start.o start.asm
clean:
rm -f *.o kernel.bin
Explanation of Options:
1 gcc Options:
-Wall
: Enable all compiler's warning messages.-m32
: Generates code for a 32-bit environment.-O0
: Disables optimization, which means the compiler will generate code that is as straightforward as possible. This can be useful for debugging because the generated code closely corresponds to the source code.
By default-O
Enables basic optimization (-O1
), which can improve performance and reduce code size.-O0
: No Optimization- Effects: The compiler's default behavior is to not perform any optimizations. This makes compilation faster and the generated code closely follows the source code structure.
- Use Case: This level is typically used during the development and debugging phases because it makes it easier to understand the generated code and correlate it with the source code.
-O1
: Basic optimization.- Description: Basic optimization.
- Effects: This level enables optimizations that do not require a significant amount of compilation time. It attempts to reduce code size and improve execution speed without affecting the debug-ability too much.
- Use Case: This level is a good compromise between compilation time and execution performance. It is often used during development when some level of optimization is desired, but full optimization is not yet necessary.
-02
: Moderate optimization.- Description: Moderate optimization.
- Effects: This level enables nearly all supported optimizations that do not involve a space-speed trade-off. It improves execution speed and reduces code size more aggressively than
-O1
. - Use Case: This level is suitable for production builds where performance is important, and the extra compilation time is acceptable.
-03
: High optimization.- Description: High optimization.
- Effects: This level enables all optimizations from
-O2
and also includes more aggressive optimizations that may increase both compilation time and the size of the generated code. It focuses on maximizing execution speed. - Use Case: This level is used for applications where execution performance is critical and the larger code size and longer compilation time are acceptable.
-Os
: Optimize for size.- Description: Optimize for size.
- Effects: This level enables all
-O2
optimizations that do not increase code size. It also includes optimizations aimed at reducing code size. - Use Case: This level is used when memory footprint is a concern, such as in embedded systems or applications where minimizing the executable size is crucial.
-Ofast
: High optimization disregarding strict standard compliance.- Description: High optimization disregarding strict standard compliance.
- Effects: This level enables all
-O3
optimizations and includes aggressive optimizations that may break strict standards compliance and result in non-portable code. - Use Case: This level is used for performance-critical applications where standard compliance is less important than execution speed.
-Og
: Optimize debugging experience.- Description: Optimize debugging experience.
- Effects: This level enables optimizations that do not interfere with debugging. It is designed to provide a good debugging experience while still performing some optimizations.
- Use Case: This level is used during the development phase to balance between optimized code and ease of debugging.
-fstrength-reduce
: Enable strength reduction optimization which replaces expensive operations with cheaper ones (e.g., replacing multiplications with additions).-fomit-frame-pointer
: Omits the frame pointer for functions that don't need one, which can make code slightly faster and smaller.-finline-functions
: Enable function inlining, which replaces function call with the actual code of the function.-nostdinc
: Prevents the compiler from searching standard directories for header files, requiring you to explicitly specify the include directories.-fno-builtin
: Disables recognition of built-in functions, forcing the compiler to generate function calls to the standard library functions.I./include
: Adds./include
to the list of directories to be searched for header files.-c
: Compiles the source file into an object file (i.e., does not link).-o $@
: Specifies the output file name, where$@
is a makefile variable representing the target file.$<
: Represents the first prerequisite in a makefile rule (usually the source file to be compiled).
2 nasm options:
nasm
: This is the Netwide Assembler, a popular assembler for the x86 architecture. It converts assembly language source code into machine code.-f elf
: This option tells NASM to generate an object file in the ELF (Executable and Linkable Format) format. ELF is a common standard file format for executables, object code, shared libraries, and core dumps, used by many Unix-like operating systems, including Linux.-o start.o
: This option specifies the output file name. The-o
flag is followed by the name of the object file to be generated. In this case, it isstart.o
.start.asm
: This is the input file, the assembly language source code that you want to assemble.
3 Complete MakeFile explanation:
.PHONY: all clean install
.PHONY
declares thatall
,clean
, andinstall
are phony targets. These targets are not actual files but names for commands to be executed.
Targets and Rules:
all
:
all: kernel
- The
all
target depends on thekernel
target. When you runmake all
, it will invoke thekernel
target.
run:
run: install
qemu-system-x86_64 -cdrom my_os.iso
- The
run
target depends on theinstall
target. After runninginstall
, it launches QEMU to run the OS using the generated ISO imagemy_os.iso
.
install:
install: kernel
mkdir -p iso/boot/grub
cp kernel iso/boot/kernel.bin
cp grub.cfg iso/boot/grub/grub.cfg
grub-mkrescue -o my_os.iso iso
- The
install
target depends on thekernel
target. - It creates the necessary directories and copies the
kernel
binary andgrub.cfg
configuration file to the appropriate locations. - Finally, it uses
grub-mkrescue
to create a bootable ISO image namedmy_os.iso
from theiso
directory.
kernel
:
kernel: start.o link.ld main.o vga.o
ld -m elf_i386 -T link.ld -o kernel.bin start.o main.o vga.o
- The
kernel
target depends on object filesstart.o
,main.o
, andvga.o
, and the linker scriptlink.ld
. - It links these object files into a single kernel binary
kernel.bin
using the specified linker script.
Object File Compilation:
%.o: %.c
gcc -Wall -m32 -fno-pie -O0 -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -I./include -c -o $@ $<
- This is a pattern rule for compiling
.c
files into.o
object files. %.o: %.c
matches any.c
file and compiles it into a corresponding.o
file.gcc
is invoked with several options:-Wall
: Enables all warnings.-m32
: Generates 32-bit code.-fno-pie
: Disables Position Independent Executable code generation.-O
: Enables optimization.-fstrength-reduce
: Enables strength reduction optimization.-fomit-frame-pointer
: Omits the frame pointer.-finline-functions
: Enables function inlining.-nostdinc
: Prevents including standard system directories for headers.-fno-builtin
: Disables built-in functions.-I./include
: Specifies the include directory for header files.-c
: Compiles the source file without linking.-o $@
: Specifies the output file (target).$<
: Represents the first prerequisite (source file).
start.o
:
start.o: start.asm
nasm -f elf -o start.o start.asm
- This rule compiles the
start.asm
assembly file into thestart.o
object file using NASM. -f elf
specifies the output format as ELF.
clean
:
clean:
rm -f *.o kernel.bin
- The
clean
target removes all object files and thekernel.bin
file.
Output

