Introduction
If you know how an operating system works, it will help you a lot in programming, especially for system programs like device drivers; even for non-system-programming, it can help a lot. And also, I'm sure every one would like to have their own operating system.
You would also learn from this article how to read and write raw sectors from a disk.
Background
In this article, I would explain the first part of building an operating system.
Here is what happens when you start your computer:
- The BIOS (Basic Input Output System – this is a program that comes with any mother board and it is placed in a chip on the mother board) checks all the computer components to make sure that they are all working.
- If all the components are working, the BIOS starts searching for a drive that might have an operating system. (The BIOS can look in hard drives, floppy drives, CD-ROM drives etc. The order the BIOS checks can be set it in the BIOS setup program. To get to the BIOS setup, right when the computer turns on, press the DELETE key until you see the BIOS setup program; in some computers, it can be a different button than DELETE, so look in your mother board specification to find how to get to the BIOS setup program. And also look up (if you don’t now how to do it) how to change the search search of BIOS while looking for an operating system.)
- The BIOS checks the first drive to see if he has a valid BOOT sector. (A disk is divided into little regions that are named sectors. The BOOT sector size is 512 bytes in most drives.) If the drive has a valid BOOT sector, the BIOS loads that sector in to the memory at address 0:7c00 (=31,744) and gives control to that area of the memory.
- Now this little program that was loaded from the BOOT sector continues to load the operating system, and after the operating system is loaded, it initializes the operating system and gives the control to the operating system.
Making a bootable disk
The steps would be like this:
- Take a floppy diskette that you don't need.
- Use the program that comes with this article, BOOTSectorUtility.exe, to copy the file BOOT.bin to the floppy diskette BOOT sector.
- Make sure that your BIOS is set to BOOT from the floppy drive first, so that our operating system would be loaded.
- Restart your computer (make sure that the floppy diskette is in the drive), and watch what our BOOT sector does.
With BOOTSectorUtility.exe, you can also search your regular operating system, by saving the boot sector of the drive of your operating system to a file.
And to read that file, start the command line and type Debug <file path> (if you have a Microsoft operating system).
The Debug command starts a 16 bit debugger (any boot sector is in 16 bit code, because when the computer starts, it is in 16 bit mode, and only after the boot sector is run, can it change the CPU to 32 bit mode or 64 bit mode). Press u <Enter> (u = unassemble) to unassemble the file. Figure 1 shows an example.
Figure 1 Unassembeling 16 bit code.
Reading raw bytes from a drive
You can read raw sectors from a drive like this:
BOOL ReadSector(char chDriveName,char *pBuffer,DWORD nSector)
{
char Buffer[256];
HANDLE hDevice;
DWORD dwBytesReaden;
sprintf(Buffer,"\\\\.\\%c:",chDriveName);
hDevice =
CreateFile(Buffer,
GENERIC_READ,
FILE_SHARE_READ |
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if(hDrive==INVALID_HANDLE_VALUE)
{
return FALSE;
}
if(SetFilePointer(hDevice,
nSector*512,
NULL,
FILE_BEGIN)==0xFFFFFFFF)
return FALSE;
ReadFile(hDevice,
pBuffer,
512,
&dwBytesReaden,
0);
if(dwBytesReaden!=512)
return FALSE;
return TRUE;
}
Making a BOOT program
Now, I will explain the basics of a boot program (to understand this, you need to be familiar with Assembler and Interrupts and Interrupts vector table).
[Interrupts vector table = From Address 0 -> 1,024 holds 256 structures (4 bytes in size) that holds an address in the form: CS:IP = xxxx:xxxx. So you have addresses from INT 1 -> INT 256. Each interrupt has an index into that table. This table is used like this: if you, for example, use the instruction INT 10h
, the CPU checks in index 10h in the interrupt table were the address of the routine is that handles INT 10h
, and the CPU jumps to that address to execute it].
Now, I will explain how to print a string, read sectors, and wait for a key press using only the BIOS.
To print a string, I use the INT 10h
function 0Ah
(AH = 0Ah). To move the cursor, I use the INT 10h
function 2h
(AH = 2h). To read sectors, I use the INT 13h
function 2h
(AH = 2h). To wait for a key stroke, use the INT 16h
function 0h
(AH = 0h).
I used TASM v3.1 and TLINK to make my boot sector, but you can use any x86 16 bit assembler compiler.
(If you can't get a copy of TASM and TLINK, you can send me an e-mail and if it is legal, I would send you a copy of them).
(TASM and TLINK v3.1 were released in 1992 by Borland International).
Now I would explain the steps the BOOT program does.
- Make a stack frame (if not you don't have any stack).
- Set the DS (data segment) so you can access the data.
- In my boot sector, I added this: (Display a message to the user, Wait for a key stroke, Continue).
- Set up the Disk Parameter Block (the Disk Parameter Block is a structure that holds information about the drive, like how much sectors it has etc., the drive controller uses it for knowing how to read the disk in the drive).
- To set the Disk Parameter Block, get its address (its address is pointed by
INT 1Eh
(in the memory, this is 1E x 4 bytes = 78h = 30 x 4 bytes = 120).
This is an example of how to initialize the Disk Parameter Block for a <!--?xml:namespace prefix = st1 /--><st1:metricconverter productid="3.5 inch" w:st="on">3.5 inch (1.44 MB) floppy Disk.
StepRateAndHeadUnloadTime db 0DFh
HeadLoadTimeAndDMAModeFlag db 2h
DelayForMotorTurnOff db 25h
BytesPerSector db 2h
SectorsPerTrack db 12h
IntersectorGapLength db 1bh
DataLength db 0FFh
IntersectorGapLengthDuringFormat db 54h
FormatByteValue db 0F6h
HeadSettlingTime db 0Fh
DelayUntilMotorAtNormalSpeed db 8h
DisketteSectorAddress(as LBA)OfTheDataArea db 0
CylinderNumberToReadFrom db 0
SectorNumberToReadFrom db 0
DisketteSectorAddress (as LBA) OfTheRootDirectory db 0
- And set the address that
INT 1E
points at to the address of the Disk Parameter Block you set up. - Reset the drive (by using the
INT 13h
function 0
). - Start reading the diskette by using the
INT 13h
function 2h
. - Give control to the loaded operating system (this is done by inserting in the code the op code of a jump to were you loaded the operating system. Let's say you loaded the operating system 512 bytes from were the BIOS loaded the BOOT sector (0:7c00h).
db 0E9h
db 512
or you can use another way: call some address, and there, change the return address in the stack to were you want the jump to, like this:
call GiveControlToOS
GiveControlToOS:
Pop ax
Pop ax
Mov ax,CodeSegement
Push ax
mov ax,InstructionPointer
Push ax
ret
- In the end of the boot sector the bytes would be
0x55h 0x0AAh
. If the boot sector doesn’t have this value, the BIOS would not load that boot sector.
After this, the BOOT sector finishes its job and the operating system starts running.
You can download the files I used for my boot sector here, the files are BOOT.asm, BOOT.bin (this is the sector itself).
The BOOT sector program
.MODEL SMALL
.CODE
ORG 7c00h ;Because BIOS loades the OS at
ProgramStart:
jmp start
xCursor db 0
yCursor db 0
nSector db 0
nTrack db 0
nSide db 0
nDrive db 0
nTrays db 0
'Are You Ready to start Loading the OS...',0
szReady db
'Error Reading Drive, Press any Key to reboot...',0
szErrorReadingDrive db
szPlaceMarker db '~~~~',0
szDone db 'Done',0
pOS dw 7E00h
StepRateAndHeadUnloadTime db 0DFh
HeadLoadTimeAndDMAModeFlag db 2h
DelayForMotorTurnOff db 25h
BytesPerSector db 2h
SectorsPerTrack db 18
IntersectorGapLength db 1Bh
DataLength db 0FFh
IntersectorGapLengthDuringFormat db 54h
FormatByteValue db 0F6h
HeadSettlingTime db 0Fh
DelayUntilMotorAtNormalSpeed db 8h
DisketteSectorAddress_as_LBA_OfTheDataArea db 0
CylinderNumberToReadFrom db 0
SectorNumberToReadFrom db 0
DisketteSectorAddress_as_LBA_OfTheRootDirectory db 0
Start:
CLI
mov AX,7B0h
mov SS,ax
mov SP,256
Mov ax,CS
mov DS,ax
XOR AX,AX
MOV ES,AX
STI
Call ClearScreen
LEA AX,szReady
CALL PrintMessage
CALL GetKey
CALL SetNewDisketteParameterTable
CALL DownloadOS
CALL GetKey
CALL FAR PTR GiveControlToOS
ret
PrintMessage PROC
mov DI,AX
Mov xCursor,1
ContinuPrinting:
cmp byte ptr [DI],0
JE EndPrintingMessage
mov AH,2
mov DH,yCursor
mov DL,xCursor
mov BH,0
INT 10h
INC xCursor
mov AH,0Ah
mov AL,[DI]
mov BH,0
mov CX,1
INT 10h
INC DI
JMP ContinuPrinting
EndPrintingMessage:
Inc yCursor
cmp yCursor,25
JNE dontMoveCorsurToBegin
Mov yCursor,0
dontMoveCorsurToBegin:
ret
PrintMessage EndP
GetKey PROC
mov ah,0
int 16h
Ret
GetKey EndP
GiveControlToOS PROC
LEA AX,szDone
Call PrintMessage
CALL GetKey
db 0e9h
dw 512
GiveControlToOS EndP
ClearScreen PROC
mov ax,0600h
mov bh,07
mov cx,0
mov dx,184fh
int 10h
Mov xCursor,0
Mov yCursor,0
Ret
ClearScreen EndP
PrintPlaceMarker PROC
LEA AX,szPlaceMarker
CALL PrintMessage
CALL GetKey
ret
PrintPlaceMarker EndP
SetNewDisketteParameterTable PROC
LEA DX,StepRateAndHeadUnloadTime
MOV WORD PTR CS:[0078h],DX
MOV WORD PTR CS:[007Ah],0000
MOV AH,0
INT 13H
ret
SetNewDisketteParameterTable EndP
DownloadOS PROC
mov nDrive,0
mov nSide,0
mov nTrack,0
mov nSector,1
ContinueDownload:
INC nSector
cmp nSector,19
JNE StayInTrack
CALL PrintPlaceMarker
INC nTrack
mov nSector,1
CMP nTrack,5
JE EndDownloadingOS
StayInTrack:
Call ReadSector
JMP ContinueDownload
EndDownloadingOS:
ret
DownloadOS EndP
ReadSector PROC
mov nTrays,0
TryAgain:
mov AH,2
mov AL,1
mov CH,nTrack
mov CL,nSector
mov DH,nSide
mov DL,nDrive
Mov BX,pOS
INT 13h
CMP AH,0
JE EndReadSector
mov AH,0
INT 13h
cmp nTrays,3
JE DisplayError
INC nTrays
jmp TryAgain
DisplayError:
LEA AX,szErrorReadingDrive
Call PrintMessage
Call GetKey
mov AH,0
INT 19h
EndReadSector:
Ret
ReadSector EndP
END ProgramStart
Points of interest
It took some time until I got a running boot sector. I would list a couple of bugs I had in the beginning while writing my boot sector.
- I didn’t set up a right stack frame.
- I didn’t modify the Disk Parameter Block.
- I loaded the operating system to areas that are used by BIOS routines (and even to the Interpret table).
- In the end of the boot sector, it must have the bytes
0x55h 0x0AAh
(this is a signature that it is a valid bootable sector).
(For using BOOTSectorUtility.exe, you would need the .NET 2.0 Framework installed on your computer. To download the .NET 2.0 Framework, click here.)
If I see that this article interests people, I would write some more articles on this subject.