Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

Writing a 16-bit dummy kernel in C/C++

4.98/5 (80 votes)
13 Mar 2014CPOL16 min read 167K   329  
Understanding FAT file system and kernel programming in C/C++

Introduction

In my previous articles I was only briefing about on how to write a boot loader. That was fun and challenging. I enjoyed it a lot. But after learning how to write a boot-loader I wanted to write much better stuff like embedding more functionality into it. But then the size of the loader kept on increasing more than 512 bytes and obviously I kept on seeing the error "This is not a bootable disk" each time I reboot my system with boot disk.

What is the scope of the article ?

In this article I will try to brief about the importance of file system for our boot-loader and also try to write a dummy kernel which does nothing but display a prompt for the user to type in text. Why am I embedding my boot-loader into a FAT formatted floppy disk and how is it beneficiary to me. As one article is too small to mention about file systems I will do my best to put it as short and simple as possible.

Background

  • This article really helps you a lot if you have prior programming experience in any language. Though this article seems to be fairly introductory, writing programs in Assembly and C can be a daunting task for bootstrapping. If you are new to computer programming then I would suggest you to read a few tutorials on introducing programming and computer fundamentals and then come back to this article.
  • Throughout this article, I will be introducing you to various terminologies related to computers in the way of questions and answers. To be frank, I will write this article as if I am introducing this article to myself. So many question and answer kind of conversations has been put in to make sure that I will understand its importance and purpose in my every day life. Example: What do you mean by computers?, or Why do I need them because I am much smarter than them?

Also you can check out my previous articles to get a basic idea about boot-loader and how to write one in Assembly and C.

Here are the links.

http://www.codeproject.com/Articles/664165/Writing-a-boot-loader-in-Assembly-and-C-Part
http://www.codeproject.com/Articles/668422/Writing-a-boot-loader-in-Assembly-and-C-Part

How are the contents organized?

Here is the break down of the topics for this article.

  • Boot-loader limitation(s)
  • Invoking other files on the disk from Boot-loader
  • FAT File System
  • FAT workflow
  • Development Environment
  • Writing a FAT boot-loader
  • Development Environment
  • Mini-Project - Writing a 16-bit Kernel
  • Testing the Kernel

Boot-loader limitation(s)

In the previous articles I tried to write boot-loader and after printing colored rectangles on screen I wanted to embed more functionality into it. But, the size of 512 bytes was a great limitation for me not to update the code of boot-loader to do more...

The challenges are as below

  • Embed more code in terms of functionality into boot-loader
  • Limit the size of the boot-loader to only 512 bytes.

How am I going to deal with the above?

Let me brief you as below.

Part 1:

  • I will write a program called kernel.c in C Language, making sure that all the extra functionality that I desired to is properly written in it.
  • Compile and save the executable as kernel.bin
  • Now, copy the kernel.bin file to the bootable drive into second sector.

Part 2:

In our boot-loader, all we can do is to load the second sector(kernel.bin) of the bootable drive into the RAM memory at address say 0x1000 and then jump to the location 0x1000 from 0x7c00 to start executing the kernel.bin file.


Below is the picture you can refer to get an idea.

Image 1

Invoking other files on the disk from bootloader

Earlier, we came to know that we can actually pass the control from bootloader(0x7c00) to other location where the disk files like kernel.bin are located in memory and then proceed further. But I have few queries in my mind.

Do you know how many sectors the kernel.bin file will occupy on the disk?

I think that's easy. All we have to do is to do is the following
1 sector = 512 bytes
So, if the size of the kernel.bin is say 512 bytes then it will occupy 1 sector and then if the size is 1024 bytes then 2 sectors and so on...
Now, the point is based on the size of the kernel.bin file you will have to hard code the number of sectors to read for the kernel.bin file in the boot-loader.
This means that in future say, you want to upgrade the kernel by frequently updating the kernel, you also have to remember to make a note of the number of sectors the kernel.bin file will occupy in the bootloader or else the kernel crashes.

What do you think if you want to add more files like office.bin, entertainment.bin, drivers.bin apart from kernel.bin to your bootable drive?

At some point yes, all you will do is to keep on appending the files to your floppy disk one by one and in this process you will have to keep on updating the bootloader about the exact location of each file on the boot disk and also the number of sectors each file is consuming and alot more.
But the point I would like to bring to your notice is that slowly the system is becoming more and more complex and somehow I dint liked it.

How do you know if the files you are appending after the boot-sector one by one are the ones you desired?

All we do is load the respective sector into memory from the boot-loader and then start executing it. But this is not perfect and something is missing.

What is missing?

I think the boot-loader blindly loads the sectors one by one with respect to each file and then starts executing the files. But even before boot-loader tries to load files into memory there should be a way to check if the files are even existing on boot disk or not.

What happens if by mistake I have copied a wrong file to second sector of the boot-disk and then updated the boot-loader and then ran?

My system simply crashes and the user will throw away my system as it is not what he wants.
So, in this case all I need is a fixed location on the boot disk where all the file names are written like an index in a book.
My boot-loader will query the index of the floppy looking for the name of the file and if the file name is listed in the index then proceed with loading of file into memory.
Wow!!! that's nice and I like it as it saves a lot actions.


This eliminates a few problems

Earlier boot-loader blindly used to load the sectors that are hard coded in it.

Why should you load the file if you do not know if the file you are loading is correct or not?

What is the solution?

All we have to do is to organize the information on the disk that we have listed above and then start organizing the data and then reprogram our boot-loader so that it can be really efficient in loading files.

This way of organizing data in a large scale is known as File System. There are many types of file systems out there both commercial and free. I will list a few of them as below.

  • FAT
  • FAT16
  • FAT32
  • NTFS
  • EXT
  • EXT2
  • EXT3
  • EXT4

FAT File System

Before I introduce you to file system, there are few terminologies that you need to know about.

In FAT file system, a cluster occupies 1 sector and a sector occupies 512 bytes on storage media. So, 1 cluster is equivalent to 1 sector on a FAT formatted disk drive.

The cluster and sector are the smallest units on a FAT file system.

For ease of use, the FAT file system is divided into four major portions and they are listed as below.

  • boot-sector
  • fat
  • root directory
  • data area

I tried my best to show it to you in the form of a picture for better understanding.

Image 2

Let me brief you about each part now.

Boot Sector:

A boot sector on a FAT formatted disk is embedded with a some information related to FAT so that each time the disk is inserted into a system, its file system is automatically known by the Operating System.

The operating system reads the boot sector of the FAT formatted disk and then parses the required information and then recognizes the type of file system and then starts reading the contents accordingly.

The information about FAT file system that is embedded inside a boot sector is called as Boot Parameter Block.

Boot Parameter Block:

Let me present you the values inside a boot parameter block with respect to boot sector.

Image 3

File Allocation Table:

This table acts like a linked list containing the next cluster value of a file.

The cluster value obtained from FAT for a particular file is useful in two ways.

  • To determine the End of File
    • If the cluster value is in between 0x0ff8 and 0x0fff, then the file does not have data in other sectors( End of File is reached ).
  • To determine the next sector where the file's data is located

Note:

I have mentioned in the picture about FAT table 1 & 2. All you need to remember is that one table is a copy of another. In case data from one is lost or corrupted, the data from the other table can act as a backup. This was the pure intention of introducing two tables rather than one.

Root Directory:

The root directory acts like an index to a list of all file names present on the disk. So the bootloader should search for the file name in the root directory and if it is positive then it can find the first cluster in the root directory and then load the data accordingly.

Now after finding the first cluster from the root directory, the bootloader should use the FAT table to find the next clusters in order to check for the end of file.

Data Area:

This is the area which actually contains the data of the file(s).

Once the proper sector of the file is identified by the program, the data of the file can be extracted from the data area.

FAT workflow

Suppose, say our boot-loader should load kernel.bin file into the memory and then execute it. Now, in this scenario all we have to do is to code the below functionality into our boot-loader.

Compare the first 11 bytes of data with "kernel.bin" starting at offset 0 in root directory table.

If the string matches then extract the first cluster of the "kernel.bin" file at offset 26 in root directory table.

Now you have the starting cluster of the "kernel.bin" file.

All you have to do is to convert the cluster into the respective sector and then load the data into memory.

Now after finding the first sector of the "kernel.bin" file, load into memory and then look up in File Allocation Table for next cluster of the file to check if the file still has data or end of file is reached.

Below is the diagram for your reference.

Image 4

Development Environment

To successfully achieve this task, we need to know about the below. Please refer to my previous articles for more information about them.

  • Operating system (GNU Linux)
  • Assembler (GNU Assembler)
  • Instruction set(x86 family)
  • Writing x86 Instructions on GNU Assembler for x86 Microprocessor.
  • Compiler (C programming language - GNU C compiler GCC)
  • Linker (GNU linker ld)
  • An x86 emulator like bochs used for our testing purposes.

Writing a FAT boot-loader

Below is the code snippet used to execute a kernel.bin file on a FAT formatted disk.

Here is the bootloader

File Name: stage0.S

C++
/*********************************************************************************
 *                                                                               *
 *                                                                               *
 *    Name       : stage0.S                                                      *
 *    Date       : 23-Feb-2014                                                   *
 *    Version    : 0.0.1                                                         *
 *    Source     : assembly language                                             *
 *    Author     : Ashakiran Bhatter                                             *
 *                                                                               *
 *    Description: The main logic involves scanning for kernel.bin file on a     *
 *                 fat12 formatted floppy disk and then pass the control to it   *
 *                 for its execution                                             *
 *    Usage      : Please read the readme.txt for more information               *
 *                                                                               *
 *                                                                               *
 *********************************************************************************/
.code16
.text
.globl _start;
_start:
     jmp _boot
     nop
     /*bios parameter block                           description of each entity       */
     /*--------------------                           --------------------------       */
     .byte 0x6b,0x69,0x72,0x55,0x58,0x30,0x2e,0x31    /* oem label                     */
     .byte 0x00,0x02                                  /* total bytes per sector        */
     .byte 0x01                                       /* total sectors per cluster     */
     .byte 0x01,0x00                                  /* total reserved sectors        */
     .byte 0x02                                       /* total fat tables              */
     .byte 0xe0,0x00                                  /* total directory entries       */
     .byte 0x40,0x0b                                  /* total sectors                 */
     .byte 0xf0                                       /* media description             */
     .byte 0x09,0x00                                  /* size in of each fat table     */
     .byte 0x02,0x01                                  /* total sectors per track       */
     .byte 0x02,0x00                                  /* total heads per cylinder      */
     .byte 0x00,0x00, 0x00, 0x00                      /* total hidden sectors          */
     .byte 0x00,0x00, 0x00, 0x00                      /* total big sectors             */
     .byte 0x00                                       /* boot drive identifier         */
     .byte 0x00                                       /* total unused sectors          */
     .byte 0x29                                       /* external boot signature       */
     .byte 0x22,0x62,0x79,0x20                        /* serial number                 */
     .byte 0x41,0x53,0x48,0x41,0x4b,0x49              /* volume label 6 bytes of 11    */
     .byte 0x52,0x41,0x4e,0x20,0x42                   /* volume label 5 bytes of 11    */
     .byte 0x48,0x41,0x54,0x54,0x45,0x52,0x22         /* file system type              */

     /* include macro functions */
     #include "macros.S"

/* begining of main code */
_boot:
     /* initialize the environment */
     initEnvironment 

     /* load stage2 */
     loadFile $fileStage2


/* infinite loop */
_freeze:
     jmp _freeze

/* abnormal termination of program */
_abort:
     writeString $msgAbort
     jmp _freeze

     /* include functions */
     #include "routines.S"

     /* user-defined variables */
     bootDrive : .byte 0x0000
     msgAbort  : .asciz "* * * F A T A L  E R R O R * * *"
     #fileStage2: .ascii "STAGE2  BIN"
     fileStage2: .ascii  "KERNEL  BIN"
     clusterID : .word 0x0000

     /* traverse 510 bytes from beginning */
     . = _start + 0x01fe

     /* append boot signature             */
     .word BOOT_SIGNATURE

This is the main loader file does the following.

  • Initialize all the registers and set up the stack by calling initEnvironment macro.
  • loadFile macro is called to load the kernel.bin file into the memory at address 0x1000:0000 and then pass control to it for further execution.

File Name: macros.S

This is a file which contains all the predefined macros and macro functions.

/*********************************************************************************          *                                                                               *
 *                                                                               *
 *    Name       : macros.S                                                      *
 *    Date       : 23-Feb-2014                                                   *
 *    Version    : 0.0.1                                                         *
 *    Source     : assembly language                                             *
 *    Author     : Ashakiran Bhatter                                             *
 *                                                                               *
 *                                                                               *
 *********************************************************************************/
/* predefined macros: boot loader                         */
#define BOOT_LOADER_CODE_AREA_ADDRESS                 0x7c00
#define BOOT_LOADER_CODE_AREA_ADDRESS_OFFSET          0x0000

/* predefined macros: stack segment                       */
#define BOOT_LOADER_STACK_SEGMENT                     0x7c00

#define BOOT_LOADER_ROOT_OFFSET                       0x0200
#define BOOT_LOADER_FAT_OFFSET                        0x0200

#define BOOT_LOADER_STAGE2_ADDRESS                    0x1000
#define BOOT_LOADER_STAGE2_OFFSET                     0x0000 

/* predefined macros: floppy disk layout                  */
#define BOOT_DISK_SECTORS_PER_TRACK                   0x0012
#define BOOT_DISK_HEADS_PER_CYLINDER                  0x0002
#define BOOT_DISK_BYTES_PER_SECTOR                    0x0200
#define BOOT_DISK_SECTORS_PER_CLUSTER                 0x0001

/* predefined macros: file system layout                  */
#define FAT12_FAT_POSITION                            0x0001
#define FAT12_FAT_SIZE                                0x0009
#define FAT12_ROOT_POSITION                           0x0013
#define FAT12_ROOT_SIZE                               0x000e
#define FAT12_ROOT_ENTRIES                            0x00e0
#define FAT12_END_OF_FILE                             0x0ff8

/* predefined macros: boot loader                         */
#define BOOT_SIGNATURE                                0xaa55

/* user-defined macro functions */
/* this macro is used to set the environment */
.macro initEnvironment
     call _initEnvironment
.endm
/* this macro is used to display a string    */
/* onto the screen                           */
/* it calls the function _writeString to     */
/* perform the operation                     */
/* parameter(s): input string                */
.macro writeString message
     pushw \message
     call  _writeString
.endm
/* this macro is used to read a sector into  */
/* the target memory                         */
/* It calls the _readSector function with    */
/* the following parameters                  */
/* parameter(s): sector Number               */
/*            address to load                */
/*            offset of the address          */
/*            Number of sectors to read      */
.macro readSector sectorno, address, offset, totalsectors
     pushw \sectorno
     pushw \address
     pushw \offset
     pushw \totalsectors
     call  _readSector
     addw  $0x0008, %sp
.endm
/* this macro is used to find a file in the  */
/* FAT formatted drive                       */
/* it calls readSector macro to perform this */
/* activity                                  */
/* parameter(s): root directory position     */
/*               target address              */
/*               target offset               */
/*               root directory size         */
.macro findFile file
     /* read fat table into memory */
     readSector $FAT12_ROOT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_ROOT_OFFSET, $FAT12_ROOT_SIZE
     pushw \file
     call  _findFile
     addw  $0x0002, %sp
.endm
/* this macro is used to convert the given   */
/* cluster into a sector number              */
/* it calls _clusterToLinearBlockAddress to  */
/* perform this activity                     */
/* parameter(s): cluster number              */
.macro clusterToLinearBlockAddress cluster
     pushw \cluster
     call  _clusterToLinearBlockAddress
     addw  $0x0002, %sp
.endm
/* this macro is used to load a target file  */
/* into the memory                           */
/* It calls findFile and then loads the data */
/* of the respective file into the memory at */
/* address 0x1000:0x0000                     */
/* parameter(s): target file name            */
.macro loadFile file
     /* check for file existence */
     findFile \file

     pushw %ax
     /* read fat table into memory */
     readSector $FAT12_FAT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_FAT_OFFSET, $FAT12_FAT_SIZE

     popw  %ax
     movw  $BOOT_LOADER_STAGE2_OFFSET, %bx
_loadCluster:
     pushw %bx
     pushw %ax
 
     clusterToLinearBlockAddress %ax
     readSector %ax, $BOOT_LOADER_STAGE2_ADDRESS, %bx, $BOOT_DISK_SECTORS_PER_CLUSTER

     popw  %ax
     xorw %dx, %dx
     movw $0x0003, %bx
     mulw %bx
     movw $0x0002, %bx
     divw %bx

     movw $BOOT_LOADER_FAT_OFFSET, %bx
     addw %ax, %bx
     movw $BOOT_LOADER_CODE_AREA_ADDRESS, %ax
     movw %ax, %es
     movw %es:(%bx), %ax
     orw  %dx, %dx
     jz   _even_cluster
_odd_cluster:
     shrw $0x0004, %ax
     jmp  _done 
_even_cluster:
     and $0x0fff, %ax
_done:
     popw %bx
     addw $BOOT_DISK_BYTES_PER_SECTOR, %bx
     cmpw $FAT12_END_OF_FILE, %ax
     jl  _loadCluster

     /* execute kernel */
     initKernel     
.endm
/* parameter(s): target file name            */
/* this macro is used to pass the control of */
/* execution to the loaded file in memory at */
/* address 0x1000:0x0000                     */
/* parameters(s): none                       */
.macro initKernel
     /* initialize the kernel */
     movw  $(BOOT_LOADER_STAGE2_ADDRESS), %ax
     movw  $(BOOT_LOADER_STAGE2_OFFSET) , %bx
     movw  %ax, %es
     movw  %ax, %ds
     jmp   $(BOOT_LOADER_STAGE2_ADDRESS), $(BOOT_LOADER_STAGE2_OFFSET)
.endm 

initEnvironment:

  • This macro is used to setup the segment registers as desired.
  • The number of arguments required to pass are none

Usage: initEnvironment

writeString:

  • This macro is used to display a null terminated string onto the screen.
  • The arguments passed to it is a null terminated string variable.

Usage: writeString <String Variable>

readSector:

  • This macro is used to read a given sector from the disk and then load it to a target address
  • The number of arguments required to pass are 4.

Usage: readSector <sector number>, <target address>, <target address offset>, <total sectors to read>

findFile:

  • This macro is used to check for the existence of a file.
  • The number of arguments required to pass is 1

Usage: findFile <target File name>

clusterToLinearBlockAddress:

  • This macro is used to convert the given cluster id into a sector number.
  • The number of arguments required to pass is 1

Usage: clusterToLinearBlockAddress <cluster ID>

loadFile:

  • This macro is used to load the target file into memory and then pass the execution control to it.
  • The number of arguments required to pass is 1

Usage: loadFile <target file name>

initKernel:

  • This macro is used to pass the control of execution to a particular address location on RAM
  • The number of arguments required to pass are none.

Usage: initKernel

File Name: routines.S

ASM
/*********************************************************************************
 *                                                                               *
 *                                                                               *
 *    Name       : routines.S                                                    *
 *    Date       : 23-Feb-2014                                                   *
 *    Version    : 0.0.1                                                         *
 *    Source     : assembly language                                             *
 *    Author     : Ashakiran Bhatter                                             *
 *                                                                               *
 *                                                                               *
 *********************************************************************************/
/* user-defined routines */
/* this function is used to set-up the */
/* registers and stack as required     */
/* parameter(s): none                  */
_initEnvironment:
     pushw %bp
     movw  %sp, %bp
_initEnvironmentIn:
     cli
     movw  %cs, %ax
     movw  %ax, %ds
     movw  %ax, %es
     movw  %ax, %ss
     movw  $BOOT_LOADER_STACK_SEGMENT, %sp
     sti
_initEnvironmentOut:
     movw  %bp, %sp
     popw  %bp
ret

/* this function is used to display a string */
/* onto the screen                           */
/* parameter(s): input string                */
_writeString:
     pushw %bp
     movw  %sp   , %bp
     movw 4(%bp) , %si
     jmp  _writeStringCheckByte
_writeStringIn:
     movb $0x000e, %ah
     movb $0x0000, %bh
     int  $0x0010
     incw %si
_writeStringCheckByte:
     movb (%si)  , %al
     orb  %al    , %al
     jnz  _writeStringIn
_writeStringOut:
     movw %bp    , %sp
     popw %bp
ret

/* this function is used to read a sector    */
/* into the target memory                    */
/* parameter(s): sector Number               */
/*            address to load                */
/*            offset of the address          */
/*            Number of sectors to read      */
_readSector:
     pushw %bp
     movw %sp    , %bp

     movw 10(%bp), %ax
     movw $BOOT_DISK_SECTORS_PER_TRACK, %bx
     xorw %dx    , %dx
     divw %bx

     incw %dx
     movb %dl    , %cl

     movw $BOOT_DISK_HEADS_PER_CYLINDER, %bx
     xorw %dx    , %dx
     divw %bx

     movb %al    , %ch
     xchg %dl    , %dh

     movb $0x02  , %ah
     movb 4(%bp) , %al
     movb bootDrive, %dl
     movw 8(%bp) , %bx
     movw %bx    , %es
     movw 6(%bp) , %bx
     int  $0x13
     jc   _abort
     cmpb 4(%bp) , %al
     jc   _abort

     movw %bp    , %sp
     popw %bp
ret

/* this function is used to find a file in   */
/* the FAT formatted drive                   */
/* parameter(s): root directory position     */
/*               target address              */
/*               target offset               */
/*               root directory size         */
_findFile:
     pushw %bp
     movw  %sp   , %bp

     movw  $BOOT_LOADER_CODE_AREA_ADDRESS, %ax
     movw  %ax   , %es
     movw  $BOOT_LOADER_ROOT_OFFSET, %bx
     movw  $FAT12_ROOT_ENTRIES, %dx
     jmp   _findFileInitValues

_findFileIn:
     movw  $0x000b  , %cx
     movw  4(%bp)   , %si
     leaw  (%bx)    , %di
     repe  cmpsb
     je    _findFileOut
_findFileDecrementCount:
     decw  %dx
     addw  $0x0020, %bx
_findFileInitValues:
     cmpw  $0x0000, %dx
     jne   _findFileIn
     je    _abort
_findFileOut:
     addw  $0x001a  , %bx
     movw  %es:(%bx), %ax
     movw  %bp, %sp
     popw  %bp
ret

/* this function is used to convert the given*/
/* cluster into a sector number              */
/* parameter(s): cluster number              */
_clusterToLinearBlockAddress:
     pushw %bp
     movw  %sp    , %bp
     movw  4(%bp) , %ax
_clusterToLinearBlockAddressIn:
     subw  $0x0002, %ax
     movw  $BOOT_DISK_SECTORS_PER_CLUSTER, %cx
     mulw  %cx
     addw  $FAT12_ROOT_POSITION, %ax
     addw  $FAT12_ROOT_SIZE, %ax
_clusterToLinearBlockAddressOut:
     movw  %bp    , %sp
     popw  %bp
ret

_initEnvironment:

  • This function is used to setup the segment registers as desired.
  • The number of arguments required to pass are none

Usage: call _initEnvironment

_writeString:

  • This function is used to display a null terminated string onto the screen.
  • The arguments passed to it is a null terminated string variable.

Usage:

  • pushw <String Variable>
  • call _writeString
  • addw $0x02, %sp

readSector:

  • This macro is used to read a given sector from the disk and then load it to a target address
  • The number of arguments required to pass are 4.

Usage:

  • pushw <sectorno >
  • pushw <address >
  • pushw <offset >
  • pushw <totalsectors>
  • call _readSector
  • addw $0x0008, %sp

findFile:

  • This function is used to check for the existence of a file.
  • The number of arguments required to pass is 1

Usage:

  • pushw <target file variable>
  • call _findFile
  • addw $0x02, %sp

clusterToLinearBlockAddress:

  • This macro is used to convert the given cluster id into a sector number.
  • The number of arguments required to pass is 1

Usage:

  • pushw <cluster ID>
  • call _clusterToLinearBlockAddress
  • addw $0x02, %sp

loadFile:

  • This macro is used to load the target file into memory and then pass the execution control to it.
  • The number of arguments required to pass is 1

Usage:

  • pushw <target file>
  • call _loadFile
  • addw $0x02, %sp

File Name: stage0.ld

This file is used to link the stage0.object file during the link time.

/*********************************************************************************
 *                                                                               *
 *                                                                               *
 *    Name       : stage0.ld                                                     *
 *    Date       : 23-Feb-2014                                                   *
 *    Version    : 0.0.1                                                         *
 *    Source     : assembly language                                             *
 *    Author     : Ashakiran Bhatter                                             *
 *                                                                               *
 *                                                                               *
 *********************************************************************************/
SECTIONS
{
     . = 0x7c00;
     .text :
     {
          _ftext = .;
     } = 0
}

File Name: bochsrc.txt

This is the configuration file required to run the bochs emulator which is used to serve for testing purposes.

megs: 32
floppya: 1_44=../iso/stage0.img, status=inserted
boot: a
log: ../log/bochsout.txt
mouse: enabled=0 


Mini-Project - Writing a 16-bit Kernel

The below file is the source code of the dummy kernel that is being introduced as part of the testing process. All we have to do is to compile the source utilizing the make file and see if it gets loaded by the bootloader or not.
A splash screen with a dragon image is displayed in text and then a welcome screen followed by a command prompt is displayed for the user to type in anything.
There are no commands or utilities written in there to execute but just for our testing purpose this kernel is introduced which is worth nothing as of now.

File Name: kernel.c

C++
/*********************************************************************************
 *                                                                               *
 *                                                                               *
 *    Name       : kernel.c                                                      *
 *    Date       : 23-Feb-2014                                                   *
 *    Version    : 0.0.1                                                         *
 *    Source     : C                                                             *
 *    Author     : Ashakiran Bhatter                                             *
 *                                                                               *
 *    Description: This is the file that the stage0.bin loads and passes the     *
 *                 control of execution to it. The main functionality of this    *
 *                 program is to display a very simple splash screen and a       *
 *                 command prompt so that the user can type commands             *
 *    Caution    : It does not recognize any commands as they are not programmed *
 *                                                                               *
 *********************************************************************************/
/* generate 16 bit code                                                 */
__asm__(".code16\n");
/* jump to main function or program code                                */
__asm__("jmpl $0x1000, $main\n");

#define TRUE  0x01
#define FALSE 0x00

char str[] = "$> ";

/* this function is used to set-up the */
/* registers and stack as required     */
/* parameter(s): none                  */
void initEnvironment() {
     __asm__ __volatile__(
          "cli;"
          "movw $0x0000, %ax;"
          "movw %ax, %ss;"
          "movw $0xffff, %sp;"
          "cld;"
     );

     __asm__ __volatile__(
          "movw $0x1000, %ax;"
          "movw %ax, %ds;"
          "movw %ax, %es;"
          "movw %ax, %fs;"
          "movw %ax, %gs;"
     );
}

/* vga functions */
/* this function is used to set the   */
/* the VGA mode to 80*24              */
void setResolution() {
     __asm__ __volatile__(
          "int $0x10" : : "a"(0x0003)
     );
}

/* this function is used to clear the */
/* screen buffer by splitting spaces  */
void clearScreen() {
     __asm__ __volatile__ (
          "int $0x10" : : "a"(0x0200), "b"(0x0000), "d"(0x0000)
     );
     __asm__ __volatile__ (
          "int $0x10" : : "a"(0x0920), "b"(0x0007), "c"(0x2000)
     );
}

/* this function is used to set the   */
/* cursor position at a given column  */
/* and row                            */
void setCursor(short col, short row) {
     __asm__ __volatile__ (
          "int $0x10" : : "a"(0x0200), "d"((row <<= 8) | col)
     );
}

/* this function is used enable and   */
/* disable the cursor                 */
void showCursor(short choice) {
     if(choice == FALSE) {
          __asm__ __volatile__(
               "int $0x10" : : "a"(0x0100), "c"(0x3200)
          );
     } else {
          __asm__ __volatile__(
               "int $0x10" : : "a"(0x0100), "c"(0x0007)
          );
     }
}

/* this function is used to initialize*/
/* the VGA to 80 * 25 mode and then   */
/* clear the screen and set the cursor*/
/* position to (0,0)                  */
void initVGA() {
     setResolution();
     clearScreen();
     setCursor(0, 0);
}

/* io functions */
/* this function is used to get a chara*/
/* cter from keyboard with no echo     */
void getch() {
     __asm__ __volatile__ (
          "xorw %ax, %ax\n"
          "int $0x16\n"
     );
}

/* this function is same as getch()    */
/* but it returns the scan code and    */
/* ascii value of the key hit on the   */
/* keyboard                            */
short getchar() {
     short word;

     __asm__ __volatile__(
          "int $0x16" : : "a"(0x1000)
     );

     __asm__ __volatile__(
          "movw %%ax, %0" : "=r"(word)
     );

     return word;
}

/* this function is used to display the*/
/* key on the screen                   */
void putchar(short ch) {
     __asm__ __volatile__(
          "int $0x10" : : "a"(0x0e00 | (char)ch)
     );
}

/* this function is used to print the  */
/* null terminated string on the screen*/
void printString(const char* pStr) {
     while(*pStr) {
          __asm__ __volatile__ (
               "int $0x10" : : "a"(0x0e00 | *pStr), "b"(0x0002)
          );
          ++pStr;
     }
}

/* this function is used to sleep for  */
/* a given number of seconds           */
void delay(int seconds) {
     __asm__ __volatile__(
          "int $0x15" : : "a"(0x8600), "c"(0x000f * seconds), "d"(0x4240 * seconds)
     );
}

/* string functions */
/* this function isused to calculate   */
/* length of the string and then return*/
/* it                                  */
int strlength(const char* pStr) {
     int i = 0;

     while(*pStr) {
          ++i;
     }
     return i;
}

/* UI functions */
/* this function is used to display the */
/* logo                                 */
void splashScreen(const char* pStr) {
     showCursor(FALSE);
     clearScreen();
     setCursor(0, 9);
     printString(pStr);
     delay(10);
}

/* shell */
/* this function is used to display a   */
/* dummy command prompt onto the screen */
/* and it automatically scrolls down if */
/* the user hits return key             */
void shell() {
     clearScreen();
     showCursor(TRUE);
     while(TRUE) {
          printString(str);
          short byte;
          while((byte = getchar())) {
               if((byte >> 8)  == 0x1c) {
                    putchar(10);
                    putchar(13);
                    break;
               } else {
                    putchar(byte);
               }
          }
     }
}

/* this is the main entry for the kernel*/
void main() {
     const char msgPicture[] = 
             "                     ..                                              \n\r"
             "                      ++`                                            \n\r"
             "                       :ho.        `.-/++/.                          \n\r"
             "                        `/hh+.         ``:sds:                       \n\r"
             "                          `-odds/-`        .MNd/`                    \n\r"
             "                             `.+ydmdyo/:--/yMMMMd/                   \n\r"
             "                                `:+hMMMNNNMMMddNMMh:`                \n\r"
             "                   `-:/+++/:-:ohmNMMMMMMMMMMMm+-+mMNd`               \n\r"
             "                `-+oo+osdMMMNMMMMMMMMMMMMMMMMMMNmNMMM/`              \n\r"
             "                ```   .+mMMMMMMMMMMMMMMMMMMMMMMMMMMMMNmho:.`         \n\r"
             "                    `omMMMMMMMMMMMMMMMMMMNMdydMMdNMMMMMMMMdo+-       \n\r"
             "                .:oymMMMMMMMMMMMMMNdo/hMMd+ds-:h/-yMdydMNdNdNN+      \n\r"
             "              -oosdMMMMMMMMMMMMMMd:`  `yMM+.+h+.-  /y `/m.:mmmN      \n\r"
             "             -:`  dMMMMMMMMMMMMMd.     `mMNo..+y/`  .   .  -/.s      \n\r"
             "             `   -MMMMMMMMMMMMMM-       -mMMmo-./s/.`         `      \n\r"
             "                `+MMMMMMMMMMMMMM-        .smMy:.``-+oo+//:-.`        \n\r"
             "               .yNMMMMMMMMMMMMMMd.         .+dmh+:.  `-::/+:.        \n\r"
             "               y+-mMMMMMMMMMMMMMMm/`          ./o+-`       .         \n\r"
             "              :-  :MMMMMMMMMMMMMMMMmy/.`                             \n\r"
             "              `   `hMMMMMMMMMMMMMMMMMMNds/.`                         \n\r"
             "                  sNhNMMMMMMMMMMMMMMMMMMMMNh+.                       \n\r"
             "                 -d. :mMMMMMMMMMMMMMMMMMMMMMMNh:`                    \n\r"
             "                 /.   .hMMMMMMMMMMMMMMMMMMMMMMMMh.                   \n\r"
             "                 .     `sMMMMMMMMMMMMMMMMMMMMMMMMN.                  \n\r"
             "                         hMMMMMMMMMMMMMMMMMMMMMMMMy                  \n\r"
             "                         +MMMMMMMMMMMMMMMMMMMMMMMMh                      ";
     const char msgWelcome[] = 
             "              *******************************************************\n\r"
             "              *                                                     *\n\r"
             "              *        Welcome to kirUX Operating System            *\n\r"
             "              *                                                     *\n\r"
             "              *******************************************************\n\r"
             "              *                                                     *\n\r" 
             "              *                                                     *\n\r"
             "              *        Author : Ashakiran Bhatter                   *\n\r"
             "              *        Version: 0.0.1                               *\n\r"
             "              *        Date   : 01-Mar-2014                         *\n\r"
             "              *                                                     *\n\r"
             "              ******************************************************";
     initEnvironment(); 
     initVGA();
     splashScreen(msgPicture);
     splashScreen(msgWelcome);

     shell(); 

     while(1);
}

Let me brief about the functions:

initEnvironment():

  • This is the function used to set the segment registers and then set-up the stack.
  • The number of arguments required are none.
  • Usage: initEnvironment();

setResolution():

  • This function is used to set the video mode to 80*25.
  • The number of arguments required are none.
  • Usage: setResolution();

clearScreen():

  • This function is used to fill the screen buffer with spaces.
  • The number of arguments required are none
  • Usage: clearScreen();

setCursor():

  • This function is used to set the cursor position at a given location on the screen.
  • The number of arguments required are 2.
  • Usage: setCursor(column, row);

showCursor():

  • This function is used to enable or disable the cursor based on the user's choice.
  • The number of arguments required is 1
  • Usage: showCursor(1);

initVGA():

  • This function is used to set the video resolution to 80*25 and then clear the screen and finally set the cursor at (0,0) on the screen.
  • The number of arguments required are none
  • Usage: initVGA();

getch():

  • This function is used to get a keystroke from the user with no echo.
  • The number of arguments required are none
  • Usage: getch();

getchar():

  • This function is used to return the key scan code and the respective ascii code
  • The number of arguments required are none.
  • Usage: putchar();

putchar():

  • This function is used to display a character onto the screen
  • The number of arguments required is 1.
  • Usage: putchar(character);
  • printString():

  • This function is used to return the key scan code and the respective ascii code
  • The number of arguments required are none.
  • Usage: getchar();
  • delay():

  • This function is used to display a null terminated string
  • The number of arguments required is 1
  • Usage: printString(null terminated string variable);
  • strlength():

  • This function is used to return the length of a null terminated string
  • The number of arguments required is 1.
  • Usage: strlength(null terminated string variable);
  • splashScreen():

  • This function is used to display some fancy image onto the screen for a while.
  • The number of arguments required is 1.
  • Usage: splashScreen(null terminated string variable);
  • shell():

  • This function is used to display a prompt onto the screen
  • The number of arguments required are none.
  • Usage: shell();
  • Below are the screen shots of the kernel that has been loaded by the bootloader.

    Testing the Kernel

    Using the Source Code:

    Attached is the file sourcecode.tar.gz which contains the required source files and also the necessary directories to generate the binaries.

    So please make sure that you are the super user of the system and then start unzipping the files into a directory or folder.

    Make sure that you install bochs-x64 emulator and GNU bin-utils to proceed further with compiling and testing the source code.

    Below is the directory structure you will see once you extract the files from the zip.

    There should be 5 directories

    • bin
    • iso
    • kernel
    • log
    • src

    Once the environment is ready, make sure you open a terminal and then run the following commands

    • cd $(DIRECTORY)/src
    • make -f Makefile test
    • bochs

    Screen shots for your reference:

    Screen1:

    This is the first screen that is being displayed while a kernel is executing.

    Image 5

    Screen: 2

    This is the welcome screen of the kernel

    Image 6

    Screen: 3

    This is the command prompt I have some how tried to display on the screen so that the user can input some text.

    Image 7

    Screen: 4

    This is the screen shot of the commands entering by the user and the screen scrolls as required when the user hits return key.

    Image 8

    Also please let me know if you are facing any issues. I would be more than happy to help.

    Conclusion:

    I hope this article gives you a picture of using a file system and its importance in operating system. Also, hope this article helps you writing a boot-loader to parse a file system and how to write a 16-bit kernel in C/C++. If you like the code you can try to edit the code and then try embedding more functionality into it.


    It should be fun doing this. See you again :)

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)