Information on How disks are made up.
Imagine that the above image is a group of tracks (rings) with eight sectors per track (pie pieces). The first sector number begins on the outside and goes to the inside.
Before DOS version 3.3, DOS used 16 bit integers for the sector numbers. This only allowed 65536 sectors. The default size of a sector is 512 bytes, that only allowed 32meg of disk space. (65536 x 512 bytes = 32meg)
MS-DOS version 3.3 allowed you to partition a hard drive that was larger than 32meg into smaller partitions, but each partition still had the 32meg limit. This allowed you to have (for example) a 64meg disk but you had to access it as if it was 2 different disks.
MS-DOS version 4 allowed up to 2 gigabytes (2000meg) of disk space.
A disk is formatted with four separate areas. The reserved area, the file allocation table (FAT), the root directory, and the files area.
The Reserved Area can be more than one sector long. The first sector is always the boot sector and holds a table defining the length of this area, the amount of entries in the root directory as well as information about the FAT area.
If the disk is bootable, it contains in the boot sector start-up code that the machine runs at bootup. See table below for a typicle MS-DOS boot sector. Items marked with an * are DOS 4.x and higher due to larger disk sizes.
Size in
Offset bytes Item
------------------------------------------------------------
00h 03 JMP instruction (opcode and offset)
03h 08 OEM name and version
(usually the name of the company that
formatted the disk)
0Bh 02 Bytes per sector
0Dh 01 Sectors per cluster
0Eh 02 reserved
10h 01 Number of File Allocation Tables (FATs)
11h 02 Number of Root Directory entries
13h 02 Sectors in logical volume
15h 01 Media descriptor byte
16h 02 Sectors per FAT
18h 02 Sectors per track
1Ah 02 Number of sides
1Ch 02 Number of hidden sectors
1Eh 02 *Number of hidden sectors, cont.
20h 04 *Sectors in logical volume
24h 01 *Physical drive number
25h 01 *reserved
26h 01 *Extended boot record signature
27h 04 *Serial number
2Bh 11 *Volume label
36h 08 *reserved (FAT name)
3Eh Bootstrap loader (no more than 445 bytes in length)
(your code and data and then load the kernel (COMMAND.COM)
.
.
1FEh 02 BootSector ID AA55h
See below for info and source on boot sectors.
The FAT Area holds two mirror images of the File Allocation Area. This is so that if one is damaged, all data is not lost.
This FAT Area is a "map" of the disk. Where each part of a file is located on a disk. Each Fat entry
can be either a 12 bit entry or a 16 bit entry. The first two entries of the FAT are reserved for DOS. The first byte of the FAT is the same as the media descriptor in the BIOS parameter block (BPB). The rest of the bytes are filled with FFh.
Reading the FAT entries for 16 bit FAT's, is quite simple. If you want entry 8, multiply by 2 = offset 16 (remember to use base 0). Reading the Fat entries for 12 bit FAT's is a little more difficult. Two entries occupies 3 bytes, so one entry is 1 1/2 bytes. Let's get entry 8 again. Multiply by 3, then divide by 2. Now, if the desired entry number is even, read in the word (2 bytes) and shift the word right 4 bits. If the entry number is odd, read in the word and AND it by 0FFFh (drop bits 15-12).
Each FAT entry contains a value. See the table below.
12-Bit value 16-bit value Meaning
000h 0000h Unused Cluster
FF0-FF6h FFF0-FFF6h Reserved Cluster
FF7h FFF7h Bad Cluster
FF8-FFFh FFF8-FFFFh Last cluster in file
all other values Next cluster in file
I have included a small CHKDISK program in C that will check a floppy disk for errors. As of this version, it checks the actual sectors for reading, then it checks the FAT entry for each sector/cluster. I plan to add more error checking items like: Check both FAT's for different values, be able to move bad clusters to good clusters, etc. I am even thinking of adding a simple DEFRAG util to it. (look for future versions). You can get the zip file from here (6,874 bytes) (ver 1.00). It contains C source
compilable with QC2.5 or any other C compiler with an inline assembler. This program only works on 3 1/2in - 1.44m floppy disks in the A: drive. You can easily modify it to check the b: drive by changing the appropriate register value. I have commented the code on where and how to do this.
The Root Directory Area is like a table of contents for the disk. It can hold only so many entries depending on the disk size. (7 to 14 sectors per root directory is normal for floppies (32 for hard drives), while 512 entries is the limit on larger floppies and all hard drives).
The Files Area holds all other files. DOS 2.0 and later allowed sub directories in this area also.
A directory entry is 32 bytes in length and contains information about the entry.
Offset Description Size Format
----------------------------------------------------------------------
00h Filename 8 ASCII chars
08h Filename Extension 3 ASCII chars
0Bh Attribute 1 bit coded (6 used, 2 unused)
0Ch Reserved 10 zeros
16h Time 2 word (coded)
18h Date 2 word (coded)
1Ah Starting Cluster # 2 word
1Ch File Size 4 long integer
The filename can be up to 8 chars in length.
The filename extension can be up to 3 chars in length.
The file Attribute is defined as:
Bit #
7 6 5 4 3 2 1 0 Description
. . . . . . . 1 Read-only
. . . . . . 1 . Hidden
. . . . . 1 . . System
. . . . 1 . . . Volume label
. . . 1 . . . . Subdirectory
. . 1 . . . . . Archive
X X . . . . . . Not used
The file Time is encoded as the following:
Time = (Hour x 2048)+(Min x 32)+(Sec + 2)
The file Date is encoded as the following:
Date = ((Year - 1980) x 512) + (Month x 32) + Day
The file Size is something that needs to be explained. This long integer (dword) is the actual size of the file, but it might not, and most of the time isn't the amount of space that it takes up on the disk. Have you ever had a floppy disk that was almost full, say had about 1024 bytes left and copied a file to it that was less the 1024, then to find out that the floppy is now full and won't allow any more space?
This is because when a file is written to a disk, it starts with the beginning of the next available cluster. If the file size is less than a cluster, then the full cluster is used. If the file size is larger than a cluster then it uses all needed whole clusters to store the file while the end of the last cluster is wasted (not used).
If I have a file size of 200 bytes and a cluster size of 512, then the whole cluster is used to save the file, but the last 312 bytes of the cluster do not get used. If I have a file size of 600 bytes and a cluster size of 512, then two whole clusters are used while the last 424 bytes are not used. This could eat up a drive quickly. Imagine having 100 files that were 10 bytes each. The total byte length of the files is 1,000 bytes (~1k) but the total bytes used on the disk with a 512 byte cluster is 51,200 bytes (50k). OUCH!!
If you have any other questions about disk drives, or see that I made a mistake, please let
me know.
******** How to make your own boot code ********
When your computer starts up it will do a self-test and then tries to load the bootsector (512 bytes) of your hard drive/floppy disk in the memory at 0000h:7C00h, and jumps to 0000h:7C00h. You should make a 512 byte long code, but the last word of the 512 bytes should be AA55h because otherwise your computer will not detect that this is a valid bootsector code.
It is safe to PUSH & POP things because SS:SP points to a special reserved stack area (256 bytes).
Also go to this page for a more complete bootsector that will "walk" the root directory and FAT looking for a "kernel" file to load. It will find any given file in the root directory, load it, and execute it.
; Please note: This code is not ready to assemble. It
; is simply an example. You will need to add code to it
; to make it a usable boot sector
; Let's say we have just booted and are going to run a
; simple MBR that loads a boot off of a partition and does
; nothing more (very simple MBR):
ORG 00h
; please note that I use the 'C' style notation
; for hex digits only when it represents a
; physical memory address. All other hex values
; are represented as segment addresses or immediates.
; set up the "move this sector" out of the way
mov ax,07C0h ;
mov ds,ax ; ds:si points to 0x07C00
xor si,si ;
mov es,ax ; es:di points to 0x07C00 + 200h
mov di,200h ;
; now move us out of the way
mov cx,100h ; 256 words
rep
movsw
; now we could jump to the new code many different ways.
; Why not use a relative near jump.
; ($+3 to point to next instruction
; +200h) to point to next "sector" location
jmp ($+3+200h) ; this is a three byte jump
; or could use:
; jmp (offset StartHere + 200h)
; which would probably be better since some assemblers
; might *not* make the above jump a 3 byte jump?
StartHere:
; Now we are out of the way.
; Use a 4 iteration loop and find the active partition
; .....
; (code to give error if no active partition found
; could go here....)
; found active partition
; perspective registers are CHS coded
; set DL from partition record
; (this assumes partition is before the BIOS read limit)
; ES still points to 07C0h
mov ax,0201h ; read the sector
xor bx,bx ; ES:BX -> 07C0:0000h (0x07C00)
int 13h
; now simply jump back to 0x07C00
; we could use all kinds of ways.
; (1)
; jmp short 0 ; that is a zero, not an oh!
; ******** ERROR ******
; *This one rely's on CS = 07C0h, since we did not
; *make sure CS = 07C0h on boot up, we don't want
; *to use it.
; (2)
; remember that NBASM wants: jmp far 0000h,07C0h
jmp far 07C0:0000h ; a far jump
; This one does set up the CS:IP registers for
; the Partition Boot we loaded. However, the partition
; boot will also assume that CS:IP is *NOT* set to
; 07C0:0000h.
; This type of jmp just ensures that it will.
; (3)
; If we made sure we didn't destroy ES:BX above:
; push es
; push bx
; retf
; (4)
; Add your favorite way.
; plenty of room to place your 'print_string' code, etc.
; pad to partition table
partition_table ....
ID_Word ....
.end
Now how about a "Partition Boot"?
; Please note: This code is not ready to assemble. It
; is simply an example. You will need to add code to it
; to make it a usable boot sector
ORG 00h
; If we want a FAT compatible BOOT, we need
; jmp short xxxxx/nop
; to jump over a BPB
jmp short start
nop
; FAT BPB goes here
start:
; set up the data segment registers
mov ax,07C0h ;
mov ds,ax ;
mov es,ax ;
; To be a FAT compatible system, we need
; to check the "reserved area" part of the BPB
; to see how many sectors are used by the boot.
; For this example, let us assume that it is more
; than one.
; Now, most likely we didn't get all of this boot
; code to fit in one sector, so let us load
; the remaining sectors.
; If we guarantee that our read_sector routine is
; in the first sector, then it is already loaded
; by the bios or by the MBR code.
mov ax,1 ; LBA starting sector (zero based)
cwd ; (dx:ax)
mov cx,???? ; count of remaining sectors
mov bx,200h ; start placing them at 07C0:0200h
call read_sectors
; now, let us make sure there is enough room in the
; initial 200h bytes, so lets jump to 07C0:0200h
; (a near relative jump)
jmp short Start_Loader
read_sectors proc near
;
; this procedure takes an LBA sector number from DX:AX
; transforms it to CHS and reads CX count sectors
; to memory starting at ES:BX
; It makes sure we don't wrap BX to zero by adding to ES instead
; of BX each iteration.
; It reads one sector at a time to make sure the BIOS
; isn't one of those that can't read over a track end
;
read_sectors endp
; place other procedures and data here
; no need for a partition table
; pad to xxxx:0510
ID_WORD ; goes here
; this is actually at 07C0:0200h
Start_Loader:
; this is where you could start you loader code.
; please note that since we never assume anything
; about CS:IP and we set ORG to 00h above, we can
; simply use a relative call to the
; Read_Sectors
; routine above and any other routines that might
; be in the first sector.
; done. Loader should have taken over by now.
.end
The following is a simple program to place your new bootsector code to your floppy.
;
; Please note that this is a simple program included for the
; boot sector example. Once the boot sector example is
; written to the boot sector of a floppy disk, all data
; on this disk will be lost due to no BPB.
;
; ** This is for example only **
; It assumes your boot image is named: boot.bin
;
; assemble with NBASM
; NBASM INSTALL
.model tiny
.code
.186
org 100h
start: mov ah,09h ; print start up string
mov dx,offset StartS ;
int 21h ;
xor ah,ah ; get a key
int 16h ;
cmp al,79h ; if 'y' then continue
je short Cont ;
cmp al,59h ; if 'Y' then continue
je short Cont ;
ret ; else exit to DOS
Cont: mov dl,al ; print the letter
mov ah,06 ;
int 21h ;
mov ax,3D02h ; open image file
mov dx,offset imgfile ;
int 21h ;
jnc short FileOK ; if no error cont
mov ah,09h ; else print error string
mov dx,offset ErrorS ;
int 21h ;
ret ; and return to DOS
FileOK: mov bx,ax ; save handle
mov ah,3Fh ; read from file
mov cx,512 ; at most 512 bytes
mov dx,offset Buffer ; in to our buffer
int 21h ;
mov cx,01 ; one sector to write
xor al,al ; drive = a:
xor dx,dx ; start at sector 0
mov bx,offset Buffer ; image file
int 26h ;
pop bx ; clean up stack
mov dx,offset MErrorS ; Assume Error
jc short MError ; carry = error
mov dx,offset SuccessS ; Was not error
MError: mov ah,09h ;
int 21h ;
.exit 00h
StartS db 'Simple Boot sector installer.',13,10,13,10
db 'This program will install the BOOT.BIN file',13,10
db 'to the A: drive. Make sure that the BOOT.BIN is',13,10
db 'in the current directory.',13,10,13,10
db 'Also, this will only work with a 3 1/2" inch floppy disk.',13,10
db "Make sure that that's what's in the drive!",13,10,13,10
db '*** WARNING *** all data on floppy disk will be lost.',13,10,13,10
db 'Continue? [Y/N] ',24h
imgfile db 'boot.bin',0
ErrorS db 13,10,10,'* Error opening BOOT.BIN *',24h
MErrorS db 13,10,10,'**** Error writing to disk ****',24h
SuccessS db 13,10,10,'Successfull',24h
Buffer dup 512,?
.end start