Information on How disks are made up.

Disk Layout

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

      ; 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?


      ; 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 ....

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

      ; FAT BPB goes here

      ; 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

; 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.

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

.model tiny
           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