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.
Tools required:
- 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
call displaystring
mov ah, 0 ; Wait for key
int 016h
db 0EAh ; Machine reboot
dw 0000h
dw 0FFFFh
displaystring: ; Function to display string
lodsb
or al,al
jz short finish
mov ah,0x0E ; Echo a char
mov bx,0x0007
int 0x10
jmp displaystring
finish:
retn
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.
c:\>debug bl.bin
-w 100 0 0 1 ; writes bl.bin into the floppy bootsector
-q
c:\>
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
org 0x7c00
start:
; setting the stack for loading the program
cli
mov ax, 0x9000
mov ss, ax
mov sp, 0xffff
sti
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
call putstr
jmp loadkernel ; I can directly jump into kernel becoz I load the kernel
; at 7c00 + 512
;hangs if kernel not loaded
oops:
mov si, msghang
call putstr
jmp oops
retf
; 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
load:
; 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]
int 13h
pop ds
jc resetfail
mov si, msgresetsuccess
call putstr
mov ax,0 ; loads sector into memory
mov es,ax
mov ah,2
mov al,1 ; loading 1 sector-kernel will be loaded. Increase al for loading more
mov dx,0
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
; program
jc load ; if fail then try to load it again
mov si, msgkernelload
call putstr
retn
resetfail:
mov si, msgresetfail
call putstr
retn
putstr:
lodsb
or al,al
jz short putstrd
mov ah,0x0E
mov bx,0x0007
int 0x10
jmp putstr
putstrd:
retn
times 510-($-$$) db 0 ; Filling the remaining free space in the sector
dw 0AA55h
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;
Email: fajib@mail.com
Irc: I will be there in #osdev as Devil_liveD