neon wrote:The topic of boot loaders is independent of file systems. (That is, it is only "needed" if the operating system "needs" it.) However, the boot loader needs to know how to locate and load the different components of the operating system. If these components are stored as files, then the boot loader needs to know how to locate and load these files -- so it needs to be able to understand the file system used. Likewise, if these components are stored as raw sectors or in some other format (like a ramdisk), the boot loader would need to know how to locate it some other way.
This is sort of the crux of the argument. The real question, with regards to the boot loader, lies in how the boot sector determines which sectors to read.
In most hobbyists first attempts at a boot loader, the values are hard-coded in; if the number or location of the sectors changes, the boot sector itself has to change. It is a workable solution, and one I use in my demonstrator, but not a scalable or stable one, especially if the developer is manually updating the boot sector and/or the stage it loads.
Part of the solution, regardless of whether the disk is formatted in any particular manner or not, is to automate the process of installing the images. Writing a utility - or at least a script or a makefile - that manages the installation of both the boot sector and the next stage (regardless of what the stage is, and how many other stages may follow it), and can automatically patch any changed values into the boot loader, is an oft-overlooked aspect which, realistically, we need to emphasize more in the Wiki. You really do need to write a disk writing and formatting utility of your own, no ifs, ands, or buts, especially if you aren't using an existing file system.
(I think I will need to add something more about this to the wiki, because it is rather dishonest of us to not explain that today. In the past, it wasn't as clear, but now we have no excuses on this point.)
The question becomes, then, how does the utility know how to set or change the constants in the boot sector? The usual solution is to set aside a header (or, I suppose, a footer) at a fixed location in the boot sector itself, with information on the disk geometry and the locations and size of the sectors it next needs to load. This is exactly what the BIOS Parameter Block in the boot sectors for FAT-formatted disks is; it starts at exactly byte 4 in the boot sector of all FAT disks (the first three bytes have to contain a jump past the end of the BPB) and describes the disk in sufficient detail for the boot sector to find the next part.
For example, the
VERBUM BPB is:
Code: Select all
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FAT12 Boot Parameter Block - required by filesystem
OEM_ID db "Verb0.04"
Bytes_Per_Sector dw 0x0200
Sectors_Per_Cluster db 0x01
Reserved_Sectors dw 0x0001
FATs db 0x02
Root_Entries dw 0x00E0
Sectors_Short dw 0x0B40
Media_Descriptor db 0xF0 ; assumes 3.5" 1.44M disk
Sectors_Per_FAT_Short dw 0x0009
Sectors_Per_Track dw 0x0012
Heads dw 0x02
Hidden_Sectors dd 0x00000000
Sectors_Long dd 0x00000000
Sectors_Per_FAT_Long dd 0x00000000
Extension_Flags dw 0x0000
; extended BPB section
Drive_Number db 0x00
Current_Head db 0x00
BPB_Signature db 0x28
Serial_Number dd 0x000001
Disk_Label db "Verbum Boot" ; must be exactly 11 characters
File_System db "FAT12 " ; must be exactly 8 characters
(And yes, this is more than just shameless self-promotion; I had it on hand and knew the names I used for the constants, so it was just the easiest one to use. This discussion does make me think I need to overhaul and extend that demonstrator, however.)
For this discussion, the most relevant constants are
- Heads for the number of read heads (always two in a floppy disk, one for each side of the 'platter', but possibly 16 or more in some hard drives);
- Sectors_Per_Track for the number of sectors per track (or cylinder, for a hard drive, but I am keeping this discussion specific to floppy disks for now); and
- Media_Descriptor for the type of disk (e.g., 5 1/4" 360KB, 3 1/2" 1.44MB), which determines both the size and the number of tracks for floppy disks.
Any boot sector looking to read an OS from a floppy disk will need these details of the drive, whether hard-coded or set when the disk is formatted - even if the disk is just a file read by the emulator. In addition, an OS trying to access a disk, bootable or otherwise, will need these data in order to determine how to read the disk, how larger it is, etc. It gets more complicated when dealing with hard drives, but the same data (and more) is needed.
One more datum that would be needed in many cases is, which sectors which are
not used for files. In FAT, this is held in the
Reserved_Sectors word, which is how many sectors - including the boot sector - are set aside for things like a second stage of the boot loader (or, if you want to really get crafty, the rest of the OS, but I'll discuss later why this may not be a good idea for longer-term concerns) and the File Allocation Tables.
Note that this a count - only the first
n sectors can be reserved in FAT. Thus, if
Reserved_Sectors is set to 16, then there are 15 reserved sectors following the boot sector.
Note also that these values are, for the most part, known when the disk is formatted and the system is installed. Historically at least, most disks
didn't have a working boot sector installed, though they did have a BPB, and the first sector was still reserved.
As I will explain shortly, actual MS-DOS FAT12 disks only used the reserved sectors for the FATs; no code was stored in them. the boot sector used a bit of a trick to find the second stage file,
IO.SYS, without having to parse the whole FAT. The practical upshot of this is that for FAT12,
Reserved_Sectors = 1 + (2 * Sectors_Per_FAT_Short)
and the first FAT started at sector 2.
Continuing with FAT specifically, the File Allocation Tables - of which there are usually two (with the second being a mirror of the first used if the first became corrupted) - follow immediately after the reserved sectors. This means that to find and read the FAT, you need all of the previous values, plus
Sectors_Per_FAT_Short (the
Sectors_Per_FAT_Long entry doesn't apply to FAT12) and
Root_Entries, which indicates how many directory entries (as opposed to file entries) the root directory has allocated.
A number of the other BPB values are either specific to MS-DOS, provided for informational reasons (e.g., the disk label), or not applicable to most floppy disks or simply don't change in general (e.g. the number of sectors per cluster, which is always 1 for most floppies).
Now, I said that MS-DOS had a trick for getting the location of IO.SYS without having to parse the whole FAT. The trick was that for a bootable disk, the hidden IO.SYS (second stage boot loader and basic drivers) and MSDOS.SYS (the closest thing MS-DOS had to a kernel) files had to be the first and second entries in the FAT, and the first three sectors of IO.SYS had to be contiguous (originally, all the sectors for both had to be contiguous, and the first sector of MS-DOS.SYS had to be directly after the end of IO.SYS). This meant that the boot sector could always find the first three sectors of IO.SYS just by loading the address in the first FAT entry, and those three sectors contained the second stage boot loader.
neon wrote:Files systems give the boot loader (1) a consistent way to locate files on disk; (2) to obtain metainformation about the file; (3) obtain the size of the file; (4) load fragmented files; (5) debug logging; and (6) supporting virtual files & file streams. However the files themselves can provide a lot of useful information as well. I.e. you can perform dynamic linking and relocation in the boot loader if you wanted.
they also allow the boot loader proper (as opposed to the boot sector) to be anywhere in the file system, provided the boot sector can find at least enough of the boot loader to load and execute it - as seen with MS-DOS and FAT. So long as the FAT boot loader for an MS-DOS system can find the first entry of the FAT, it can find the start of IO.SYS - and that's all it needed to do. There are several other ways to do this, such as actually having other reserved sectors containing a second stage boot loader capable of actually parsing a FAT, so how you do it is a matter of choice.