Creating Your Own Operating System

In this article, we will write our own bootloader using 16-bit assembly language to create our own operating system.
 
   
 
   
 
 
 
Introduction 
 
Writing an operating system is the most complicated task in the world of programming. 
 
The first part of an operating system is the Bootloader. Bootloader is a piece of program that runs before the operating system starts running. It is used to boot other operating systems and usually each operating system has a set of bootloaders specific for it. Bootloaders usually contain several ways to boot the OS kernel and also contains commands for debugging and/or modifying the kernel environment.
 
We will create a 3 stage OS. The first stage is to just display messages on the screen with colors, the second is to take input from user, and third stage is for drawing. 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 the next part, you must have some knowledge about 16-bit assembly language. Quick review of 16-bit registers.
  
Data registers 
 
AX is the primary accumulator; it is used in input/output and most arithmetic instructions.
BX is known as the base register, and it could be used in indexed addressing.
CX is known as the count register; CX registers store the loop count in iterative operations.
DX is known as the data register. It is also used in input/output operations. 
 
Index Registers 
  • Source Index (SI) 
    It is used as source index for string operations.
  • Destination Index (DI) 
    It is used as destination index for string operations.
  • Segment registers
    Data segment(DS), Code segment(CS), Extra segment(ES), Stack segment(SS)
  • 8-bit registers AH, AL, BH, BL, CH, CL, DH, and DL.
    where H is higher & L is Lower.
    AH(8-15)+AL(0-7) = AX 
For more, learn 16 bit assembly language tutorial and BIOS interrupts in assembly. Download the source code to view the complete OS code.
 
Requirements 
 
You need an assembler (that can convert your assembly instructions into raw binary format) and a simulator to view. I am using NASM (Netwide Assembler) and QEMU (Quick Emulator). For Linux, type the following commands to install NASM & QEMU.
  • sudo apt-get install nasm
  • sudo apt-get install qemu qemu-system-x86_64 
For Windows, download them from the following sites respectively and install.
  • http://www.nasm.us/ 
  • https://qemu.weilnetz.de/  
Coding 
 
Starting of coding always comes with printing Hello World, doesn't it ? Here's the code that prints "Hello World! " on screen.
(file = hello_world.asm) 
  1. [bits 16]           ; tell assembler that working in real mode(16 bit mode)  
  2. [org 0x7c00]        ; organize from 0x7C00 memory location where BIOS will load us  
  3.   
  4.   
  5. start:              ; start label from where our code starts  
  6.   
  7.   
  8.     xor ax,ax           ; set ax register to 0  
  9.     mov ds,ax           ; set data segment(ds) to 0  
  10.     mov es,ax           ; set extra segment(es) to 0  
  11.     mov bx,0x8000  
  12.   
  13.   
  14.     mov si, hello_world              ; point hello_world to source index  
  15.     call print_string                      ; call print different color string function  
  16.   
  17.   
  18.   
  19.     hello_world db  'Hello World!',13,0  
  20.   
  21.   
  22. print_string:  
  23.     mov ah, 0x0E            ; value to tell interrupt handler that take value from al & print it  
  24.   
  25. .repeat_next_char:  
  26.     lodsb                ; get character from string  
  27.     cmp al, 0                    ; cmp al with end of string  
  28.     je .done_print               ; if char is zero, end of string  
  29.     int 0x10                     ; otherwise, print it  
  30.     jmp .repeat_next_char        ; jmp to .repeat_next_char if not 0  
  31.   
  32. .done_print:  
  33.     ret                         ;return  
  34.   
  35.     times (510 - ($ - $$)) db 0x00     ;set 512 bytes for boot sector which are necessary  
  36.     dw 0xAA55                                                    ; boot signature 0xAA & 0x55   
OK! Now, what the hell is this ?
 
[bits 16]

This line tells the assember that you are working in 16-bit real mode. It will convert the assembly data to 16-bit binary form. 
 
[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, we are setting the registers (such as ax, ds, and es) to zero, which we will use further. Then, we will copy the memory location 0x8000 to bx register because we want to perform operations/instructions. As we are loaded at 0x7c00 memory location, we need memory location above it. 
 
hello_world db 'Hello World!',13,0 
 
The above 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 to the first character of hello_world string to source index (si) register and then calling print_string function. Copying 0x0E to ah register. This will tell to interrupt handler that takes value/ASCII character from al & prints 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 & prints 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 devices. This line is boot signature that makes our code into 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 the 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, 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, just with 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.
 
This will print the following output.
 
  
 
Here, I have created .bin file but you can also create .iso file. Once it successfully prints "Hello World!", attach secondary device/USB drive and boot .bin/.iso in it. You can use dd command on linux or rufus software on Windows.
 
To print a string on screen at specific location or to set cursor at specific location, use the following actions.
         AH = 0x02
         BH = page
         DH = row
         DL = column
e.g
  1. mov ah,0x02         ; set value for change to cursor position  
  2. mov bh,0x00         ; page  
  3. mov dh,0x06         ; y cordinate/row  
  4. mov dl,0x05         ; x cordinate/col  
  5. int 0x10  
  6.   
  7. mov si, hello_world  
  8. call print_string      
To get input first, set ax to 0x00 and call int 0x16. To display a character which has been input, move ah to 0x0E and call int 0x10. It will store char value to al register & key code to ah register.
 
e.g
  1. inputLoop:  
  2.     mov ax,0x00  
  3.     int 0x16  
  4.   
  5.     cmp ah,0x1C             ; compare input is enter(1C) or not  
  6.     je .inputLoop  
  7.   
  8.     cmp al,0x61            ; compare input is character 'a' or not  
  9.     je exitLoop  
  10.       
  11.     mov ah,0x0E             ;display input char  
  12.     int 0x10  
  13.   
  14.           
  15. exitLoop:  
  16.         ret   
As described above every sector has a size of only 512 bytes, if you write code which is taking more than 512 bytes, it will not work and 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
  1. ; load second sector from memory  
  2.   
  3. mov ah, 0x02                    ; load second stage to memory  
  4. mov al, 1                       ; numbers of sectors to read into memory  
  5. mov dl, 0x80                    ; sector read from fixed/usb disk  
  6. mov ch, 0                       ; cylinder number  
  7. mov dh, 0                       ; head number  
  8. mov cl, 2                       ; sector number  
  9. mov bx, _OS_Stage_2             ; load into es:bx segment :offset of buffer  
  10. int 0x13                        ; disk I/O interrupt  
  11.   
  12. 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 code that only draws our graphical simple window on a screen with text.
  1.       
  2.     mov ax,0x13              ; clears the screen  
  3.     int 0x10  
  4.   
  5.   
  6.   
  7. ;//////////////////////////////////////////////////////////  
  8. ; drawing window with lines  
  9.   
  10.   
  11.     push 0x0A000                ; video memory graphics segment  
  12.     pop es                      ; pop any extar segments from stack  
  13.     xor di,di                   ; set destination index to 0  
  14.     xor ax,ax                   ; set color register to zero  
  15.   
  16.   
  17.   
  18.     ;//////////////////////////////////////////////  
  19.     ;******drawing top line of our window  
  20.     mov ax,0x02                 ; set color to green  
  21.   
  22.     mov dx,0                    ; initialize counter(dx) to 0  
  23.   
  24.     add di,320                  ; add di to 320(next line)  
  25.     imul di,10                  ;multiply by 10 to di to set y cordinate from where we need to start drawing  
  26.   
  27.     add di,10                   ;set x cordinate of line from where to be drawn  
  28.   
  29.   
  30. _topLine_perPixel_Loop:  
  31.   
  32.   
  33.     mov [es:di],ax              ; move value ax to memory location es:di  
  34.   
  35.     inc di                      ; increment di for next pixel  
  36.     inc dx                      ; increment our counter  
  37.     cmp dx,300                  ; comprae counter value with 300  
  38.     jbe _topLine_perPixel_Loop  ; if <= 300 jump to _topLine_perPixel_Loop  
  39.   
  40.     hlt                         ; halt process after drawing  
  41.   
  42.   
  43.     ;//////////////////////////////////////////////  
  44.     ;******drawing bottm line of our window  
  45.     xor dx,dx  
  46.     xor di,di  
  47.     add di,320  
  48.     imul di,190         ; set y cordinate for line to be drawn  
  49.     add di,10           ;set x cordinate of line to be drawn  
  50.   
  51.     mov ax,0x01         ; blue color  
  52.   
  53. _bottmLine_perPixel_Loop:  
  54.   
  55.     mov [es:di],ax  
  56.   
  57.     inc di  
  58.     inc dx  
  59.     cmp dx,300  
  60.     jbe _bottmLine_perPixel_Loop  
  61.     hlt  
  62.   
  63.   
  64.   
  65.     ;//////////////////////////////////////////////  
  66.     ;******drawing left line of our window  
  67.     xor dx,dx  
  68.     xor di,di  
  69.     add di,320  
  70.     imul di,10           ; set y cordinate for line to be drawn  
  71.   
  72.     add di,10            ; set x cordinate for line to be drawn  
  73.   
  74.     mov ax,0x03          ; cyan color  
  75.   
  76. _leftLine_perPixel_Loop:  
  77.   
  78.     mov [es:di],ax  
  79.   
  80.     inc dx  
  81.     add di,320  
  82.     cmp dx,180  
  83.     jbe _leftLine_perPixel_Loop  
  84.   
  85.     hlt   
  86.   
  87.   
  88.     ;//////////////////////////////////////////////  
  89.     ;******drawing right line of our window  
  90.     xor dx,dx  
  91.     xor di,di  
  92.     add di,320  
  93.     imul di,10           ; set y cordinate for line to be drawn  
  94.   
  95.     add di,310           ; set x cordinate for line to be drawn  
  96.   
  97.     mov ax,0x06          ; orange color  
  98.   
  99. _rightLine_perPixel_Loop:  
  100.   
  101.     mov [es:di],ax  
  102.   
  103.     inc dx  
  104.     add di,320  
  105.     cmp dx,180  
  106.     jbe _rightLine_perPixel_Loop  
  107.   
  108.     hlt  
  109.   
  110.   
  111.   
  112.     ;//////////////////////////////////////////////  
  113.     ;******drawing line below top line of our window  
  114.     xor dx,dx  
  115.     xor di,di  
  116.   
  117.     add di,320  
  118.     imul di,27           ; set y cordinate for line to be drawn  
  119.   
  120.     add di,11            ; set x cordinate for line to be drawn  
  121.   
  122.     mov ax,0x05         ; pink color  
  123.   
  124. _belowLineTopLine_perPixel_Loop:  
  125.   
  126.   
  127.     mov [es:di],ax  
  128.   
  129.     inc di  
  130.     inc dx  
  131.     cmp dx,298  
  132.     jbe _belowLineTopLine_perPixel_Loop  
  133.   
  134.     hlt   
  135.   
  136.   
  137.   
  138.     ;***** print window_text & X char  
  139.   
  140.     ;set cursor to specific position  
  141.     mov ah,0x02  
  142.     mov bh,0x00  
  143.     mov dh,0x01         ; y cordinate  
  144.     mov dl,0x02         ; x cordinate  
  145.     int 0x10  
  146.   
  147.     mov si,window_text              ; point si to window_text  
  148.     call _print_YellowColor_String  
  149.   
  150.     hlt  
  151.   
  152.   
  153.   
  154.     ;set cursor to specific position  
  155.     mov ah,0x02  
  156.     mov bh,0x00  
  157.     mov dh,0x02           ; y cordinate  
  158.     mov dl,0x25           ; x cordinate  
  159.     int 0x10  
  160.   
  161.     mov ah,0x0E  
  162.     mov al,0x58           ; 0x58=X  
  163.     mov bh,0x00  
  164.     mov bl,4              ; red color  
  165.     int 0x10  
  166.   
  167.     hlt  
  168.   
  169.     ;set cursor to specific position  
  170.     mov ah,0x02  
  171.     mov bh,0x00  
  172.     mov dh,0x02           ; y cordinate  
  173.     mov dl,0x23           ; x cordinate  
  174.     int 0x10  
  175.   
  176.     mov ah,0x0E  
  177.     mov al,0x5F           ; 0x5F= _   
  178.     mov bh,0x00  
  179.     mov bl,9              ; red color  
  180.     int 0x10  
  181.   
  182.     hlt  
  183.   
  184.   
  185.     ;set cursor to specific position  
  186.     mov ah,0x02  
  187.     mov bh,0x00  
  188.     mov dh,0x05   ; y cordinate  
  189.     mov dl,0x09    ; x cordinate  
  190.     int 0x10  
  191.   
  192.     mov si,hello_world_text  
  193.     call _print_DiffColor_String  
  194.   
  195.     hlt  
  196.   
  197.   
  198.   
  199.     ;set cursor to specific position  
  200.     mov ah,0x02  
  201.     mov bh,0x00  
  202.     mov dh,0x12   ; y cordinate  
  203.     mov dl,0x03  ; x cordinate  
  204.     int 0x10  
  205.   
  206.     mov si,display_text  
  207.     call _print_WhiteColor_String  
  208.   
  209.     hlt   
Here hello_world_text and window_text are defined in first sector.
  1. window_text db 10,'Graphics in OS......'0  
  2. hello_world_text db 10,10'    Hello World!',0  
  3. display_text db '! Welcome to my Operating System !'0   
Download the source code to view complete code for our operating system.