How to make an operating system?
This is one of the most asked questions on the internet asked by new comers to computer programming world.
Operating system design is one of the most hard topic if not the hardest topic in the field of computer science. One needs to know about hardware, extreme level of computer programming including low level assembly language and mid level language like C/C++.
Even after such complexity many coders wants to know and make their own operating system as a hobby project. There exists a number of options when thinking about it.
- From scratch
- Developing on top of an existing kernel like "Linux"
- Customizing a linux based distro and making a new distro.
Option 2 and 3 are comparatively easier than option 1. But today we will write some boilerplate code that boots up a computer and prints something on the screen. This should not be considered a fully functional OS anyway. This is just for fun and can be considered as an entry point to developing your OS. Let's start...
The bootloaders are generally written in 16-bit assembly(also called Real mode), then the bits can be extended to 32-bit(Protected mode).
So the bootloaders must be written in 16-bit assembly.
Before you move to next, you must have some knowledge about 16-bit assembly language.
Requirements
You need an assember that can convert your assembly instructions into raw binary format and an simulator to view.
I am using Nasm assember and Qemu simulator.
For Linux, type following commands to install nasm & qemu(Quick Emulator)
sudo apt-get install nasm
sudo apt-get install qemu qemu-system-x86_64
For Windows,download them from following sites and install them.
Coding
Starting of coding always comes with printing Hello World, isn't it ?Here's the code that prints Hello World! on screen.
(file = hello_world.asm)
OK now, what the hell is this ?
[bits 16] : This line tells the assember you are working in 16-bit real mode.
[org 0x7c00] : This is assember directive. 0x7c00 is the memory location where BIOS will load us. xor ax,ax mov ds,ax mov es,ax mov bx,0x8000 First setting the registers to zero such as ax,ds,es which we will used further . Then we will copy memory location 0x8000 to bx register
because we want to perform operations/instructions because we are loaded at 0x7c00 memory location.
we need memory location above it. hello_world db 'Hello World!',13,0 : this line defines the string with label hello_world,
where 13 is New line and 0 is end of string. mov si, hello_world call print_string Pointing first character of hello_world string to source index(si) register and then call print_string function. Copying 0x0E to ah register.
this will tell to interrupt handler that take value/ASCII character from al & print it using int 0x10. AH = 0x0E AL = character BH = page BL = color (graphics mode) int 0x10 .repeat_next_char : Label for continue to loop until end of string occurs. lodsb : This instruction loads the first character from si to al register using ASCII code. Then we will compare whether al contains 0 or not,if not then print it and jump to loop,
otherwise jump to .done_print. int 0x10 : This is BIOS video interrupt which takes char value from al register & print it. times (510 - ($ - $$)) db 0x00 : A boot sector always be a 512 byte. starting with address 0x00.
because on hard drive,there are only 512 bytes of sectors.
dw 0xAA55 : This is the magic number of bootable device.
This line is boot signature that makes our code to bootable code.
it defines word 0xAA & 0x55.
These are last two bytes of our first sector. because of this number,BIOS loads us at 0x7c00 location when computer starts. For Linux,Type following command to compile file nasm -f bin hello_world.asm -o myos.bin Once file is compiled successfully and myos.bin file is created, then run it in qemu. qemu-system-x86_64 myos.bin For windows, open nasm application, it will prompt a command at location where nasm is installed. Perform same commands as performed for linux juts giving full file name path. Consider i have file in C:\Users\Pritam\Documents\temp folder. nasm.exe -f bin "C:\Users\Pritam\Documents\temp\hello_world.asm"
-o "C:\Users\Pritam\Documents\temp\myos.bin" and to run in qemu, "C:\Program Files\qemu\qemu-system-x86_64.exe" "C:\Users\Pritam\Documents\temp\myos.bin" where i have installed qemu. Here i have created .bin file, but you can also create .iso file. Once it successfully prints Hello World! then attach Secondary device/USB drive and boot .bin/.iso in it. You can use dd command on linux or can use rufus software on windows.
Output of above program is
Without BIOS :
Watch video here for how to implement above procedure for both Linux & Windows :
To print a string on screen at specific location or to set cursor at specific location use following actions.
AH = 0x02
BH = page
DH = row
DL = column
e.g:
mov ah,0x02 ; set value for change to cursor position
mov bh,0x00 ; page
mov dh,0x06 ; y cordinate/row
mov dl,0x05 ; x cordinate/col
int 0x10
mov si, hello_world
call print_string
To get input first set ax to 0x00 and call int 0x16.
To display character which has been input,mov ah to 0x0E and call int 0x10.
It will store char value to al register & key code to ah register.
e.g:
inputLoop:
mov ax,0x00
int 0x16
cmp ah,0x1C ; compare input is enter(1C) or not
je .inputLoop
cmp al,0x61 ; compare input is character 'a' or not
je exitLoop
mov ah,0x0E ;display input char
int 0x10
exitLoop:
ret
As described above that every sector has size only 512 bytes,
so if you write code which is taking more than 512 bytes it will not work or assembler will give you an error.
So to use more memory, you need to load/read next sector into main memory.
To load/read sectors in main memory,
AH = sector number(1,2,3 etc)[1 is already taken by our bootloader]
AL = number of sectors to read
DL = type of memory from where to read(0x80 is for hard drive/USB drive)
CH = cylinder number
DH = head number
CL = sector number
BX = memory location where to jump after loaded
int 0x13 = Disk I/O interrupt
Then jump to your memory location(label in assembly).
e.g:
; load second sector from memory
mov ah, 0x02 ; load second stage to memory
mov al, 1 ; numbers of sectors to read into memory
mov dl, 0x80 ; sector read from fixed/usb disk
mov ch, 0 ; cylinder number
mov dh, 0 ; head number
mov cl, 2 ; sector number
mov bx, _OS_Stage_2 ; load into es:bx segment :offset of buffer
int 0x13 ; disk I/O interrupt
jmp _OS_Stage_2 ; jump to second stage
For clearing the screen,copy 0x13 to ax & call video interrupt.
mov ax,0x13
int 0x10
For graphics, we need to access video memory segments.
This can be done by pushing 0x0A000 into stack,and setting di,ax,es to specific values.
AX = color
DI = x & y cordinates(y=320 for next line(320*200 display))
[ES:DI] = value of x,y cordinates & color(AX)[segment :offset]
Here's the complete 3 stages OS code
Post a Comment
Your feedback is welcome. Be it positive or negative. Please do not post any irrelevant comment or abuse anyway.