As our OS grows, it becomes increasingly unmanageable to have all the files in a single directory. We must define a structure, a simple way to organize the code would be this: boot -> Any code related to the bootloader or subroutines used by it, kernel -> Any code related to the kernel, drivers -> Specific driver code.
Before starting, it is recommended that you read these previous articles:
- Boot Sector
- Interrupts
- Memory
- Stack
- IF-ELSE
- Functions
- Memory Segmentation
- Reading Data from Disk
- Entering Protected Mode 32bits
- Compilation, Linking, Stack Management, and Variables in C
- Pointers
- Kernel
- EntryPoint
- Gmake
We create the directory structure:
mkdir -p Project/kernel/
mkdir -p Project/drivers/
We copy or move the necessary files:
cp kernel.c kernel_entry.asm Project/kernel
Make allows the use of wildcards, making compilation scripts shorter and easier to maintain.
We can expand the names of files that match a pattern:
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
We can create a list from certain source files:
OBJ = ${C_SOURCES:.c=.o}
To use the list, simply reference it as ${OBJ}:
kernel.bin: kernel/kernel_entry.o ${OBJ}
ld -o $@ -Ttext 0x1000 $^ --oformat binary
Now that we have moved the files, we need to modify the includes in boot_sect.asm
%include "boot/boot_sect_print.asm"
%include "boot/boot_sect_print_hex.asm"
%include "boot/boot_sect_disk.asm"
%include "boot/32bit-gdt.asm"
%include "boot/32bit-print.asm"
%include "boot/32bit-switch.asm"
The final Makefile would look like this:
# Automatically generate lists of sources using wildcards.
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
# TODO : Make sources dep on all header files .
# Convert the *.c filenames to *.o to give a list of object files to build
OBJ = ${C_SOURCES:.c=.o}
# Run Qemu to simulate booting of our code.
run : all
qemu-system-x86_64 os-image
# Run Qemu to simulate booting of our code with debugging session.
# gdb -ex "set architecture i386:x86-64" -ex "set disassembly-flavor intel" -ex "target remote localhost:1234" -ex "symbol-file kernel/kernel.elf"
debug : all kernel/kernel.elf
qemu-system-x86_64 os-image -s -S &
# Used for debugging purposes
kernel/kernel.elf: boot/kernel_entry.o ${OBJ}
ld -o $@ -Ttext 0x1000 $^
# Defaul build target
all: os-image
# This is the actual disk image that the computer loads
# which is the combination of our compiled bootsector and kernel
os-image : boot/boot_sect.bin kernel/kernel.bin
cat $^ > os-image
# This builds the binary of our kernel from two object files :
# - the kernel_entry, which jumps to main () in our kernel
# - the compiled C kernel
kernel/kernel.bin : kernel/kernel_entry.o ${OBJ}
ld -o $@ -Ttext 0x1000 $^ --oformat binary
# Generic rule for compiling C code to an object file
# For simplicity , we C files depend on all header files.
%.o : %.c ${HEADERS}
cc -g -m32 -ffreestanding -c $< -o $@
# Assemble the kernel_entry.
%.o : %.asm
nasm -f elf $< -o $@
%.bin : %.asm
nasm -f bin $< -o $@
clean :
rm -fr *.bin *.dis *.o *.core os-image
rm -fr kernel/*.o boot/*.bin drivers/*.o
NOTE: Once again, it is important to use real tabs and not spaces in Makefile files.
We check that the whole process works:
gmake run