How to begin to write an Os
This tutorial is meant for those who are familiar with assembly languges and some knowledge of system internal. Writing an Os is a challenging project. The initial step of writing one is to code an efficient boot loader to load the kernel.
What happens when you boot a computer?
- The Bios is started first
- It read the boot sector and loads it into the memory
The boot sector is the first sector of the disk. A sector contain 512 bytes and that is the limitation to write our boot loader. So the boot loader must be at the most 512 bytes. So we must load the real program or the kernel using the loader. The boot sector is loaded at the physical address 0000:7c00.
And how does the Bios knows whether the disk has a valid bootsector?
The final two bytes of the bootsector ie; bytes 511 and 512 should be Aah and 55h respectively. In other words, the last word in the bootsector should be AA55h.
- NASM or any other good assembler
- Interrupt list like Ralf Brown's interrupt list
- PC Emulator like Bochs will help you and avoid frequent reboot of the system. But this is not a compulsory requirement.
Now we will move into the coding part. Let's start with a simple boot loader.
; A simple bootloader-bl.asm
org 0x7c00 ; Bios loads our loader at this point
mov si, msg ; Displays a message on the screen
mov ah, 0 ; Wait for key
db 0EAh ; Machine reboot
displaystring: ; Function to display string
jz short finish
mov ah,0x0E ; Echo a char
msg db 'Press Any Key …', 13, 10, 0
times 510-($-$$) db 0 ; Fills the rest of the space except the last two bytes with zero
dw 0AA55h ; Magic word for a valid bootsector
; The End
Now we have to compile this to plain binary. And then write the bl.bin into the bootsector.
NASM bl.asm -f bin -o bl.bin
You can use any programs to write the binary file onto the bootsector. Even the good old Debug.exe can also be used for that.
-w 100 0 0 1 ; writes bl.bin into the floppy bootsector
Set the defualt boot to floppy and boot the machine.
If you are using a Bochs freedos emulator, you will have to do the following steps;
- Edit file 'bochsrc' and change boot to 'a' ie; boot: a
- Copy the binary file 'bl.bin' into the Bochs directory and rename as 'a.img'
- Run 'Bochs.exe'
You will see the message 'Press Any Key …'
He he….Did I hear you screaming in joy? Yes, you have written a successful bootloader.
This is not the end. The main purpose of a bootloader is not yet sattisfied. And what is it?
You will have to load the kernel into the memory because a 512 bytes of code will not run a machine with all its power. The next step is to load a simple kernel into the memory.
I have used many interrupt calls in the following example code. Each of them is properly commented.
First of all we have to allocate stack for our program. Then load the kernel into any know address. I have loaded the kernel into 7c00h + 512 for the simplicity of compiling the loader and kernel in the same program. Before loading we have to reset the disk ie; bringing the disk head to the track zero. This is same like seeking the file descriptor to beginning of the file. Then load the kernel into memory. And what mext? Ofcourse jump into the loaded kernel. So simple…isn't it? Try studying this example code given below.
; Boot Loader by Fajib
; This is the boot sector….
; This will load the real os from the disk into the memory
; setting the stack for loading the program
mov ax, 0x9000
mov ss, ax
mov sp, 0xffff
mov [bootdrv], dl ; dl contains the bootdisk name ie; dl = 0 if floppy a:\
call load ; function to load the kernel from disk into memory
mov si, msgloadsuccess
jmp loadkernel ; I can directly jump into kernel becoz I load the kernel
; at 7c00 + 512
;hangs if kernel not loaded
mov si, msghang
; loader variables
bootdrv db 0
msgresetfail db 'Disk Reset Failure!', 13, 10, 0
msgresetsuccess db 'Disk reset success…', 13, 10, 0
msgkernelload db 'Loading kernel…', 13, 10, 0
msgloadsuccess db 'Kernel loaded successfully…', 13, 10, 0
msghang db '.', 0
; loader functions
; we have to reset the disk before it move to the loaded program
; say our real program is stored in sector 2
; let's load it
push ds ; reset disk system
mov ax, 0 ; forces controller to recalibrate drive heads (seek to track 0)
mov dl, [bootdrv]
mov si, msgresetsuccess
mov ax,0 ; loads sector into memory
mov al,1 ; loading 1 sector-kernel will be loaded. Increase al for loading more
mov cx,2 ; ch = cylinder number and cl = sector number 1-63
mov bx,7e00h ; 7e00h = 7c00h + 512 … loading my program here makes it easy for me
int 13h ; we can directly compile bootsector and kernel together in a single
jc load ; if fail then try to load it again
mov si, msgkernelload
mov si, msgresetfail
jz short putstrd
times 510-($-$$) db 0 ; Filling the remaining free space in the sector
Wow! You have now loaded a kernel successfully… But do not stop here… Improvise your kernel as good possible… Experimentation is the best learning method… Do inform me about your improvements and bugs in my code to me at;
Irc: I will be there in #osdev as Devil_liveD