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-Parthttp://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.
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.
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.
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.
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
.code16
.text
.globl _start;
_start:
jmp _boot
nop
.byte 0x6b,0x69,0x72,0x55,0x58,0x30,0x2e,0x31
.byte 0x00,0x02
.byte 0x01
.byte 0x01,0x00
.byte 0x02
.byte 0xe0,0x00
.byte 0x40,0x0b
.byte 0xf0
.byte 0x09,0x00
.byte 0x02,0x01
.byte 0x02,0x00
.byte 0x00,0x00, 0x00, 0x00
.byte 0x00,0x00, 0x00, 0x00
.byte 0x00
.byte 0x00
.byte 0x29
.byte 0x22,0x62,0x79,0x20
.byte 0x41,0x53,0x48,0x41,0x4b,0x49
.byte 0x52,0x41,0x4e,0x20,0x42
.byte 0x48,0x41,0x54,0x54,0x45,0x52,0x22
#include "macros.S"
_boot:
initEnvironment
loadFile $fileStage2
_freeze:
jmp _freeze
_abort:
writeString $msgAbort
jmp _freeze
#include "routines.S"
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
. = _start + 0x01fe
.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.
#define BOOT_LOADER_CODE_AREA_ADDRESS 0x7c00
#define BOOT_LOADER_CODE_AREA_ADDRESS_OFFSET 0x0000
#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
#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
#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
#define BOOT_SIGNATURE 0xaa55
.macro initEnvironment
call _initEnvironment
.endm
.macro writeString message
pushw \message
call _writeString
.endm
.macro readSector sectorno, address, offset, totalsectors
pushw \sectorno
pushw \address
pushw \offset
pushw \totalsectors
call _readSector
addw $0x0008, %sp
.endm
.macro findFile file
readSector $FAT12_ROOT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_ROOT_OFFSET, $FAT12_ROOT_SIZE
pushw \file
call _findFile
addw $0x0002, %sp
.endm
.macro clusterToLinearBlockAddress cluster
pushw \cluster
call _clusterToLinearBlockAddress
addw $0x0002, %sp
.endm
.macro loadFile file
findFile \file
pushw %ax
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
initKernel
.endm
.macro initKernel
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
_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
_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
_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
_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
_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.
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
__asm__(".code16\n")
__asm__("jmpl $0x1000, $main\n")
#define TRUE 0x01
#define FALSE 0x00
char str[] = "$> "
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;"
)
}
void setResolution() {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0003)
)
}
void clearScreen() {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0200), "b"(0x0000), "d"(0x0000)
)
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0920), "b"(0x0007), "c"(0x2000)
)
}
void setCursor(short col, short row) {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0200), "d"((row <<= 8) | col)
)
}
void showCursor(short choice) {
if(choice == FALSE) {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0100), "c"(0x3200)
)
} else {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0100), "c"(0x0007)
)
}
}
void initVGA() {
setResolution();
clearScreen();
setCursor(0, 0);
}
void getch() {
__asm__ __volatile__ (
"xorw %ax, %ax\n"
"int $0x16\n"
)
}
short getchar() {
short word;
__asm__ __volatile__(
"int $0x16" : : "a"(0x1000)
)
__asm__ __volatile__(
"movw %%ax, %0" : "=r"(word)
)
return word
}
void putchar(short ch) {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0e00 | (char)ch)
)
}
void printString(const char* pStr) {
while(*pStr) {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0e00 | *pStr), "b"(0x0002)
)
++pStr
}
}
void delay(int seconds) {
__asm__ __volatile__(
"int $0x15" : : "a"(0x8600), "c"(0x000f * seconds), "d"(0x4240 * seconds)
)
}
int strlength(const char* pStr) {
int i = 0;
while(*pStr) {
++i;
}
return i;
}
void splashScreen(const char* pStr) {
showCursor(FALSE);
clearScreen();
setCursor(0, 9);
printString(pStr);
delay(10);
}
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);
}
}
}
}
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 screenThe 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
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.
Screen: 2
This is the welcome screen of the kernel
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.
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.
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 :)