How To Make a Basic OS
AmazingMech2418 (1039)

How To Make a Basic OS


In this tutorial, I will not teach you how to make a bunch of if statements in Python or Node.js or Java, but will teach you how to make a real OS, although it won't be able to do much. Basic experience with NASM x86 Assembly, but it is not required since this will explain everything needed along the way.

Installing Packages

To create an OS, you need two packages: NASM and QEMU. NASM is the Assembler we will use and QEMU is the VM we will use to run the OS.

You can use this basic Bash script to automatically download them if needed.

if ! which nasm > /dev/null; then install-pkg nasm; fi
if ! which qemu-system-x86_64 > /dev/null; then install-pkg qemu; fi

Next up, you need to install the required libraries.

Installing Libraries

There are a number of libraries needed to run everything in QEMU. Thanks to @CSharpIsGud , this is fairly easy...

Using the following Bash script, you can install all of the required libraries.

mkdir libtmp; cd libtmp; wget; unzip VolantOS; mv qemu/lib* ../; mv qemu/*.bin ../; mv qemu/*.rom ../; cd ..; rm -rf libtmp

You only need to run this once, so if you put it in your file, you can comment it out after running once.

Setting Up The File System

Now, we need to set up the proper files for this all to work.

First, create a folder named iso and inside that, create a folder named boot. You also, in the root directory, need to create two files: Link.ld and Loader.asm. Link.ld tells the linker how to put together the binary file so it can be used and Loader.asm is your main Assembly code.

Setting Up The Linker File

Inside Link.ld, put the following code:

ENTRY(entry)                /* the name of the entry label */

    . = 0x00100000;          /* the code should be loaded at 1 MB */

    .text ALIGN (0x1000) :   /* align at 4 KB */
        *(.text)             /* all text sections from all files */

    .rodata ALIGN (0x1000) : /* align at 4 KB */
        *(.rodata*)          /* all read-only data sections from all files */

    .data ALIGN (0x1000) :   /* align at 4 KB */
        *(.data)             /* all data sections from all files */

    .bss ALIGN (0x1000) :    /* align at 4 KB */
        *(COMMON)            /* all COMMON sections from all files */
        *(.bss)              /* all bss sections from all files */

Of course, this is copied from @CSharpIsGud 's tutorial which was in turn copied from LittleOSBook, so I cannot really explain all of this that well.

The Assembly Template

To start working on your OS, you need to follow a basic template which makes the code bootable. Here is the template:

global entry

MAGIC equ 0x1BADB002 ; These tell the bootloader that we are bootable
FLAGS equ 0

; Now, setting up some basic constants to use in our program
SYS_READ  equ 3
STDOUT    equ 1
STDIN     equ 2
SYS_EXIT  equ 1
WRITESTART equ 0xB8000

; Macros will go here

section .data
        ; Nothing here for now...

section .text:
align 4 ; Align everything after this to a 4 byte boundary, which the boot header needs to be aligned to
        dd MAGIC ; dd means "define double word", a word is usually 2 bytes on most computers, so a dword is 4 bytes. You can think of a word as being a short in C and a dword being an int.
        dd FLAGS
        dd SUM

jmp loop ; For now we won't do anything but loop forever.

This is just a basic template so that you can start writing your OS... Now, for the actual coding!

Making The OS

All of the code in this section will be done in the Loader.asm file.

Declaring Variables

First off, in the .data section, we need to set up a few variables. In the section, you should add the following code.

  msg dd "                              HelloWorld OS 0.0.1                              " ; Title message
  len equ $ - msg ; Get length
  msg2 dd "HelloWorld OS is a basic operating system that does nothing but print this text." ; Line 1
  len2 equ $ - msg2 ; Get length
  msg3 dd 'HelloWorld OS is named after the common practice to make your first program simply say "Hello World".' ; Line 2
  len3 equ $ - msg3 ; Get Length
  position dd 0 ; Required
  temp dd 0 ; Required
  templen dd 0 ; Required
  line dd 0 ; Required

All of the variables that have "Required" commented next to them are absolutely required for this. The other lines are messages to be printed and you can feel free to change them in any way you want.


Now, we need to create a few macros for this program to run!

Printing Characters

We need to have a way to print characters at a specified position. We can use a macro for that!

%macro printchar 2 ; The macro printchar will take 2 arguments
    mov eax, WRITESTART ; Set eax to the memory position of the start of text output
    mov ebx, [position] ; Now, we take the value of position and put it into ebx.
    add eax, ebx ; Now, we add ebx to eax to get the current memory position.
    mov ebx, %1 ; Now, we set ebx to the character we are printing.
    mov [eax], ebx ; And we set the given memory position to ebx to print the character.
    mov ecx, eax ; Now, we move eax to ecx for some more memory manipulation to set the color
    add ecx, 1 ; We increase ecx by 1 since we need to go to the next byte for the color data.
    mov edx, %2 ; Here, we set edx to the color we want.
    mov [ecx], edx ; And here we set the given memory position to edx to set the color.
    mov eax, [position] ; Lastly, these last 3 lines of the macro add 2 to the position variable.
    add eax, 2
    mov [position], eax

The comments explain how this works. Now, we need to be able to clear the screen.

Clearing the Screen

Now that we can print single characters, we need to be able to clear the screen! We need two macros for that!

First off, the macro to clear a given number of characters!

%macro clearscreen 1 ; The clearscreen macro takes 1 argument: the number of characters to clear.
    mov eax, %1 ; We move the number of characters to eax...
    mov [templen], eax ; ... and we use that to set templen.
    %%clearscreen: ; This label will be used for a loop.
    printchar ' ', 0 ; For each iteration of the loop, we print a space character with the default color, 0, to clear that character's position on the screen
    mov esi, [templen] ; Now, we need to move templen to esi...
    sub esi, 1 ; ..and subtract 1...
    mov [templen], esi ; ... and move that back to templen so we can decrement the variable.
    mov al, [templen] ; And now, we need to move templen to al so we can compare it.
    cmp al, 0 ; This checks if al (from templen) is 0.
    jne %%clearscreen ; If it is not 0, it iterates through the loop again. If it is 0, the macro ends.

Once again, the comments explain this macro.

Now, we need to use this to clear the entire screen.

This is a crude way of doing it, but it works!

%macro clearall 0
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000
   clearscreen 4000

This just repeats the clearscreen macro multiple times. Since the al register is only a 8-bit register (at least I think... It might be 16-bit), there are limits to the amount it can go, so each 4000 doesn't actually clear 4000 digits.

This macro will be used for clearing the screen at the start of the program.

Printing Strings

Now, we need a macro to print strings.

%macro printstr 3 ; This takes 3 arguments, the string, the length, and the color.
    mov eax, %1 ; These two lines move the string to temp.
    mov [temp], eax
    mov eax, %2 ; And these move the length to templen.
    mov [templen], eax
    %%printstart: ; This is the start of a loop.
    mov esi, [temp] ; Here, we get the current character...
    mov esi, [esi]
    printchar esi, %3 ; ... and print it with the given color.
    mov esi, [temp] ; Now, we increment temp so we can get to the next character
    add esi, 1
    mov [temp], esi
    mov esi, [templen] ; And we decrement templen...
    sub esi, 1
    mov [templen], esi
    mov al, [templen] ; ... and compare it to 0 to see if we need to go through another iteration of the loop.
    cmp al, 0
    jne %%printstart

Moving Lines

Now, we need one last macro...

%macro gotoline 1
    mov eax, %1 ; Move the line number to eax ...
    imul eax, 160 ; ... and multiply by 160 (characters per line) ... 
    mov [position], eax ; ... and set the position to that.

This macro will allow you to skip to certain lines.

Now that we have the macros, we can start programming the rest of the OS!

Programming the OS

Now that we have the macros, we need to use them in the .text section.

Currently we have

jmp loop ; For now we won't do anything but loop forever.

but we will need to change this code.

The code for this basic OS is

   clearall ; Clear the screen
   mov eax, 0 ; Now, we need to reset the character position.
   mov [position], eax
   printstr msg, len, 0b00011010 ; Blue background, green text.
   gotoline 4 ; Go to line 4
   printstr msg2, len2, 10 ; Green text
   gotoline 6 ; Go to line 6
   printstr msg3, len3, 10 ; Green text
jmp loop ; For now we won't do anything but loop forever.

Of course, you can change things like the color, number of messages, order of messages, etc. This is just a basic OS like HelloWorld OS which can be found here:

Compiling and Running QEMU

To compile and run QEMU, you simply use the following Bash script:

nasm -f elf32 Loader.asm -o Loader.o
ld -T Link.ld -melf_i386 Loader.o -o ./iso/boot/kernel
qemu-system-x86_64 -kernel ./iso/boot/kernel

The next steps will be adding in C and then user input, so stay tuned to my next OSs and the tutorials for those next steps!

If you have any questions about this tutorial, please just ask!

Also, @DynamicSquid , here is a ping since you said you wanted this tutorial!

You are viewing a single comment. View All