Building a basic X86 kernel part 6 - The kernel in real mode

Now that we have our basic bootloader ready, we can focus on the last missing piece: our actual kernel. Our bootloader so far only prints a message, so we need to alter it to make it load our kernel instead. We can do that by using another BIOS interrupt call like we did for printing the message: 0x13. Let’s stay in real mode for now, move our message printing logic to the kernel and load that kernel from the bootloader.

Splitting it up

First, let’s create a separate file called kernel.asm that will contain our printing logic like before. We again need the ORG instruction because we get loaded at a specific address in memory:

; kernel.asm

org 0x500
bits 16

cld

mov si, msg
mov ah, 0x0e

print:
 	lodsb

	or al, al
	jz halt
	int 0x10
	jmp print

halt:
	hlt

msg db "Welcome to the bootloader!", 0xa, 0xd, 0x0

If we compile this to a flat binary with NASM, we get a kernel.o file that we need to write to our floppy as well. In the previous chapter, we already learned how to write bootloader.o to this floppy and doing the same for kernel.o is no different:

dd conv=notrunc if=kernel.o of=disk.img bs=512 count=1 seek=1

The difference here is that we first seek a sector. In other words, we write to the second sector of our floppy disk.

Now, to read bytes from a disk, we can use BIOS interrupt 0x13. From Ralf Brown’s reference, we can see it takes the following arguments:

DISK - READ SECTOR(S) INTO MEMORY

AH = 02h
AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer

So, from our bootloader, we now need to read the kernel from our floppy. We want to read the second sector and load that at address 0x50:0x0 in memory, so in our case this translates to:

; bootloader.asm

org 0x7c00
bits 16

cli

; Buffer ES:BX to load kernel
; Effectively ES:BX == 0x50:0x0
mov ax, 0x50
mov es, ax
xor bx, bx

; Read the second sector (cl)
; from track 0 (ch)
mov al, 1
mov cl, 2
mov ch, 0

; Use head 0 (dh) of drive 0 (dl)
mov dh, 0
mov dl, 0

; 0x02 is the read operation
mov ah, 0x02

; Call the BIOS routine
int 0x13

; Now jump to our kernel
jmp 0x50:0x0

times 510 - ($-$$) db 0
dw 0xAA55

Now if you run this in QEMU with GDB enabled, you can set a breakpoint at 0x500 and see the code of kernel.asm in your assembly window. Or if you feel more lucky, just run it and see the welcome message being printed.

In the final chapter, we will make the switch to protected mode, leaving you with a basic setup to move on from!