[ Curiosity,Experimentation ]

Random stuff from the parallel universe of Ones and Zeroes

Archive for January, 2011

Writing a 16-bit Real mode OS [NASM]

Posted by appusajeev on January 27, 2011

This article is about writing a minimal 16 bit, real modes DOS like operating system that boots off a pen drive and provides a shell to run pure binary executables(aka COM files in the DOS era)  with a custom file system implemented. This means that the OS could run COM files directly if you have one. COM files are pure binary files in the sense that they don’t have a header, contains machine instructions only. For demonstration, I have developed some sample executables which could be run using our OS (apps like clone of unix echo, register dump etc). See the end of the post to see some pictures of the OS in action.

Download Source

The post explains how to

  • Write a bootloader
  • Write a shell, a kernel placeholder
  • Implement a basic filesystem
  • Write the OS to disk
  • Boot from the OS

The OS is written in the open source NASM assembler in Linux. To understand the working, you need to have some understanding about x86 booting process, bootloaders and real mode of processor operation.

Booting Process Basics

When the system is powered on, BIOS pops into action and performs what is known as Power On Self Test(POST) to verify the working of devices, initializing them etc. Immediately after that, POST loads BIOS executable code, present in the BIOS ROM into memory at address which is usually 0xF00000.  POST then places a jump instruction in the first byte of memory (CS:IP = 0). The jump instruction is nothing but a jump to the address 0xF00000 where the BIOS code is loaded.  Now the BIOS code takes control and performs certain operations like setting up the Interrupt Vector Table(IVT), finding a boot device, setting up certain information in RAM(BIOS Data Area), loading the bootloader  etc.

BIOS provides certain basic interrupts for the programmer for basic functions like loading and storing disk sectors, reading keyboard, printing to screen etc. These interrupts are similar to DOS interrupts but are not DOS interrupts.

Back to topic,  once these processes are over, the BIOS iterates through the list of boot devices and according the boot order preferences, the bios searches for a bootloader in each boot device. If a bootloader is found, its loaded into memory and is given control.

A point to note is that whenever system is powered on, whatever be the processor, Core 2 Duo or Core i7 or whatever operates in 16 bit real mode by default until it is explicitly asked to switch to 32  bit protected mode (by setting PE bit in CR0 register and doing a far jump to fix CS to point to a segment descriptor after setting up GDT).

Bootloader Basics

Bootloader is basically a 512 byte piece of machine code that is present in the first sector a boot device.  Bootloader is the first user defined program that’s loaded into memory and given control of. It is the duty of the bootloader to load the OS into memory and pass control to it.

A bootloader must be exactly 512 bytes in size. BIOS identifies  a valid bootloader by means of a signature. The 511th byte of the bootloader should contain the value 0x55 and 512th byte should have the value 0xAA.

A Bootloader will always be loaded at address 0x7C00 in RAM. Usually, this corresponds to CS:IP pair of 00:0x7C00 but some BIOSes set CS:IP as 0x7C0:0 which is essentially points to the same address  but leads to issues in writing bootloader when we have to specify the offset where our code will be loaded.
This can be easily dealt by defining an offset 0 and then jumping to 0x7C0:start , where start is the label of the next instruction following  the far jump. This jump sets CS = 0x7C0

(Note: Physical address = CS x 16 + IP)

Our Bootloader

Our bootloader, present in the first sector of the pendrive  (the mechanism for writing the bootloader and the OS into the pendrive will be discussed later) will be loaded into memory and execution will immediately start.

Our bootloader serves 3 purposes, it displays a welcome message and then loads the OS and file table from the disk and jumps to OS entry point. The os is loaded at address 0x1000 and file table at address 0x2000

Heres our bootloader source

Download full Source

Our Bootloader

Our Bootloader

Bios provides interrupts for displaying characters as well as strings on screen. Here I have used character display interrupt to write a function to print a zero-terminated string like in C.

The interrupt for displaying character on screen is

INT 0x10, BX = character color, AL = character to display, AH = 0x0E

Our bootloader, OS and executables are stored on  disk . For execution, they need to be loaded to RAM. The bootloader will be loaded by BIOS and the rest we have to load when required through the sector loading interrupt.A block of 512 bytes (in this case, need not always be) is called a sector.
For loading sectors off the disk to RAM, bios provides an interrupt, INT 0x13 and the parameters to set are

AH = 2

DL = drive,  DL = 0x80 for hard disk and this applies to our pen drive

DH = head number

CH = track number

CL= sector number of the sector to be loaded

AL = number of sectors to load

ES = segment to load the sector

BX = offset from ES where the sector is to be loaded

Our  bootloader occupies the first sector, file table in the third sector and OS in the third sector.

The bootloader loads the os into address 0x1000:0 and filetable into address 0x2000

Implementing Filesystem

The shell provided by our os enables the user  to type an executable name and run it. For that , the os must know where exactly each executable is located on disk and this is where the concept of file system surfaces. A file system, in simple words is basically a specification that tells how to locate of a file on disk given its name.

For our purpose, I made a simple filesystem. Each file is mapped to a sector in disk to form a string with following structure


This filetable is loaded to 0x2000:0 by the bootloader this address space is scanned to find the sector where the requested file is stored on disk and that sector is brought to RAM.

Our OS/Shell

So, the bootloader has loaded our OS at 0x1000:0 and filetable at 0x2000:0 and it makes a far jump to 0x1000:0, the OS entry point.  The working of the shell is simple, using BIOS interrupt to reach character, we read the executable name from the user. The filetable loaded at 0x2000:0 is scanned for a match, the associated sector number is read and that sector is loaded into memory at 0x9000:0 and the shell makes a far jump to this address. If the name entered by the user cannot be matched in the file table, an error message is shown.  After the executable completes execution, for it to return back to the OS, it must make a far jump to 0x1000:0, the OS entry point. This step is functionally similar to executing (AH= 4ch, INT 0x21 in DOS)

The interrupt for reading a character from a keyboard is INT 0x16 with AH=0, the read character will be available in AL.

Heres our OS source

Download full Source

Our Shell

Our Shell

Writing the OS to disk

Okay so everything is done, the final thing to do to boot from the pen drive, is to write our os into it. It can’t just be done by copying the files to the pen drive. It doesn’t work in our case for two reasons:

1.   We cannot write the bootloader to the first sector using this method. When we ask the OS to copy a file, it copies the file to some free sectors and add this information to the file table.

2. If were to copy the files to the pendrive, our filesystem has to be known to the host OS, like FAT  which means we have to write code to parse that file system during boot time which, well is an overkill for this os

So, to write the bootloader and custom file system, we need to have low level disk access, for which the obvious choice is Linux.  Linux treats devices like files which can be read and written to. This is a really powerful and useful concept. Commands like ‘dd’ use this concept. The file representing our harddisk would be something like sda, something  like sdc for pen drive, it varies. To know the file allocated, after the pen drive is attached, run dmesg|tail in the terminal.

Now whatever we write to the device file will be written as such into the device which is exactly what we want. To write the bootloader, write the compiled boodloader into the first 512 bytes of the file and this would be written to first 512 bytes of the disk and this can be done using C file operations. Pretty easy, isn’t it. Now to write the file table and OS  to the 2nd and 3rd sectors of the disk, just write these files to the next two 512 bytes of the device file . The same procedure applies to writing the executables to disk

The following C code does the job of writing everything to disk. It takes the path of device file, bootloader, os and list of executable as command line arguments.

Copy Program

Copy Program

Booting the OS

Plug in the  pen drive, find its corresponding device file (use dmesg|tail), open the makefile, substitute its path in dev variable. Then run ‘make’ , reboot the system, choose to boot from mass storage and you could see the sweet sight of our OS booting . At the prompt,  type ‘help’ and hit enter, you could see list of available executables, like reg to dump registers, echo that echoes back a string read from the user

Here are some pics of the OS in action(Click to enlarge).

OS in action

OS in action

Our OS in action

Our OS in action


Posted in Kernel | Tagged: , , | 42 Comments »