So far we have gone through the following sequence:
The BIOS did some early hardware
initialization, including the POST. The
MBR (boot0
) was
loaded from absolute disk sector one to address
0x7c00
. Execution control was passed to
that location.
boot0
relocated itself to the
location it was linked to execute
(0x600
), followed by a jump to continue
execution at the appropriate place. Finally,
boot0
loaded the first disk sector from
the FreeBSD slice to address 0x7c00
.
Execution control was passed to that location.
boot1
is the next step in the
boot-loading sequence. It is the first of three boot stages.
Note that we have been dealing exclusively
with disk sectors. Indeed, the BIOS loads
the absolute first sector, while boot0
loads the first sector of the FreeBSD slice. Both loads are to
address 0x7c00
. We can conceptually think of
these disk sectors as containing the files
boot0
and boot1
,
respectively, but in reality this is not entirely true for
boot1
. Strictly speaking, unlike
boot0
, boot1
is not
part of the boot blocks
[3].
Instead, a single, full-blown file, boot
(/boot/boot
), is what ultimately is
written to disk. This file is a combination of
boot1
, boot2
and the
Boot Extender
(or BTX).
This single file is greater in size than a single sector
(greater than 512 bytes). Fortunately,
boot1
occupies exactly
the first 512 bytes of this single file, so when
boot0
loads the first sector of the FreeBSD
slice (512 bytes), it is actually loading
boot1
and transferring control to
it.
The main task of boot1
is to load the
next boot stage. This next stage is somewhat more complex. It
is composed of a server called the “Boot Extender”,
or BTX, and a client, called
boot2
. As we will see, the last boot
stage, loader
, is also a client of the
BTX server.
Let us now look in detail at what exactly is done by
boot1
, starting like we did for
boot0
, at its entry point:
The entry point at start
simply jumps
past a special data area to the label main
,
which in turn looks like this:
sys/boot/i386/boot2/boot1.S
main: cld # String ops inc xor %cx,%cx # Zero mov %cx,%es # Address mov %cx,%ds # data mov %cx,%ss # Set up mov $start,%sp # stack mov %sp,%si # Source mov $0x700,%di # Destination incb %ch # Word count rep # Copy movsw # code
Just like boot0
, this
code relocates boot1
,
this time to memory address 0x700
. However,
unlike boot0
, it does not jump there.
boot1
is linked to execute at
address 0x7c00
, effectively where it was
loaded in the first place. The reason for this relocation will
be discussed shortly.
Next comes a loop that looks for the FreeBSD slice. Although
boot0
loaded boot1
from the FreeBSD slice, no information was passed to it about this
[4],
so boot1
must rescan the
partition table to find where the FreeBSD slice starts. Therefore
it rereads the MBR:
sys/boot/i386/boot2/boot1.S
mov $part4,%si # Partition cmpb $0x80,%dl # Hard drive? jb main.4 # No movb $0x1,%dh # Block count callw nread # Read MBR
In the code above, register %dl
maintains information about the boot device. This is passed on
by the BIOS and preserved by the
MBR. Numbers 0x80
and
greater tells us that we are dealing with a hard drive, so a
call is made to nread
, where the
MBR is read. Arguments to
nread
are passed through
%si
and %dh
. The memory
address at label part4
is copied to
%si
. This memory address holds a
“fake partition” to be used by
nread
. The following is the data in the fake
partition:
sys/boot/i386/boot2/Makefile
part4: .byte 0x80, 0x00, 0x01, 0x00 .byte 0xa5, 0xfe, 0xff, 0xff .byte 0x00, 0x00, 0x00, 0x00 .byte 0x50, 0xc3, 0x00, 0x00
In particular, the LBA for this fake partition is hardcoded to zero. This is used as an argument to the BIOS for reading absolute sector one from the hard drive. Alternatively, CHS addressing could be used. In this case, the fake partition holds cylinder 0, head 0 and sector 1, which is equivalent to absolute sector one.
Let us now proceed to take a look at
nread
:
sys/boot/i386/boot2/boot1.S
nread: mov $0x8c00,%bx # Transfer buffer mov 0x8(%si),%ax # Get mov 0xa(%si),%cx # LBA push %cs # Read from callw xread.1 # disk jnc return # If success, return
Recall that %si
points to the fake
partition. The word
[5]
at offset 0x8
is copied to register
%ax
and word at offset 0xa
to %cx
. They are interpreted by the
BIOS as the lower 4-byte value denoting the
LBA to be read (the upper four bytes are assumed to be zero).
Register %bx
holds the memory address where
the MBR will be loaded. The instruction
pushing %cs
onto the stack is very
interesting. In this context, it accomplishes nothing.
However, as we will see shortly, boot2
, in
conjunction with the BTX server, also uses
xread.1
. This mechanism will be discussed in
the next section.
The code at xread.1
further calls
the read
function, which actually calls the
BIOS asking for the disk sector:
sys/boot/i386/boot2/boot1.S
xread.1: pushl $0x0 # absolute push %cx # block push %ax # number push %es # Address of push %bx # transfer buffer xor %ax,%ax # Number of movb %dh,%al # blocks to push %ax # transfer push $0x10 # Size of packet mov %sp,%bp # Packet pointer callw read # Read from disk lea 0x10(%bp),%sp # Clear stack lret # To far caller
Note the long return instruction at the end of this block.
This instruction pops out the %cs
register
pushed by nread
, and returns. Finally,
nread
also returns.
With the MBR loaded to memory, the actual loop for searching the FreeBSD slice begins:
sys/boot/i386/boot2/boot1.S
mov $0x1,%cx # Two passes main.1: mov $0x8dbe,%si # Partition table movb $0x1,%dh # Partition main.2: cmpb $0xa5,0x4(%si) # Our partition type? jne main.3 # No jcxz main.5 # If second pass testb $0x80,(%si) # Active? jnz main.5 # Yes main.3: add $0x10,%si # Next entry incb %dh # Partition cmpb $0x5,%dh # In table? jb main.2 # Yes dec %cx # Do two jcxz main.1 # passes
If a FreeBSD slice is identified, execution continues at
main.5
. Note that when a FreeBSD slice is found
%si
points to the appropriate entry in the
partition table, and %dh
holds the partition
number. We assume that a FreeBSD slice is found, so we continue
execution at main.5
:
sys/boot/i386/boot2/boot1.S
main.5: mov %dx,0x900 # Save args movb $0x10,%dh # Sector count callw nread # Read disk mov $0x9000,%bx # BTX mov 0xa(%bx),%si # Get BTX length and set add %bx,%si # %si to start of boot2.bin mov $0xc000,%di # Client page 2 mov $0xa200,%cx # Byte sub %si,%cx # count rep # Relocate movsb # client
Recall that at this point, register %si
points to the FreeBSD slice entry in the MBR
partition table, so a call to nread
will
effectively read sectors at the beginning of this partition.
The argument passed on register %dh
tells
nread
to read 16 disk sectors. Recall that
the first 512 bytes, or the first sector of the FreeBSD slice,
coincides with the boot1
program. Also
recall that the file written to the beginning of the FreeBSD
slice is not /boot/boot1
, but
/boot/boot
. Let us look at the size of
these files in the filesystem:
-r--r--r-- 1 root wheel 512B Jan 8 00:15 /boot/boot0 -r--r--r-- 1 root wheel 512B Jan 8 00:15 /boot/boot1 -r--r--r-- 1 root wheel 7.5K Jan 8 00:15 /boot/boot2 -r--r--r-- 1 root wheel 8.0K Jan 8 00:15 /boot/boot
Both boot0
and
boot1
are 512 bytes each, so they fit
exactly in one disk sector.
boot2
is much bigger, holding both
the BTX server and the
boot2
client. Finally, a file called
simply boot
is 512 bytes larger than
boot2
. This file is a
concatenation of boot1
and
boot2
. As already noted,
boot0
is the file written to the absolute
first disk sector (the MBR), and
boot
is the file written to the first
sector of the FreeBSD slice; boot1
and
boot2
are not written
to disk. The command used to concatenate
boot1
and boot2
into a
single boot
is merely
cat boot1 boot2 > boot
.
So boot1
occupies exactly the first 512
bytes of boot
and, because
boot
is written to the first sector of the
FreeBSD slice, boot1
fits exactly in this
first sector. When nread
reads the first
16 sectors of the FreeBSD slice, it effectively reads the entire
boot
file
[6].
We will see more details about how boot
is
formed from boot1
and
boot2
in the next section.
Recall that nread
uses memory address
0x8c00
as the transfer buffer to hold the
sectors read. This address is conveniently chosen. Indeed,
because boot1
belongs to the first 512
bytes, it ends up in the address range
0x8c00
-0x8dff
. The 512
bytes that follows (range
0x8e00
-0x8fff
) is used to
store the bsdlabel
[7].
Starting at address 0x9000
is the
beginning of the BTX server, and immediately
following is the boot2
client. The
BTX server acts as a kernel, and executes in
protected mode in the most privileged level. In contrast, the
BTX clients (boot2
, for
example), execute in user mode. We will see how this is
accomplished in the next section. The code after the call to
nread
locates the beginning of
boot2
in the memory buffer, and copies it
to memory address 0xc000
. This is because
the BTX server arranges
boot2
to execute in a segment starting at
0xa000
. We explore this in detail in the
following section.
The last code block of boot1
enables
access to memory above 1MB
[8]
and concludes with a jump to the starting point of the
BTX server:
sys/boot/i386/boot2/boot1.S
seta20: cli # Disable interrupts seta20.1: dec %cx # Timeout? jz seta20.3 # Yes inb $0x64,%al # Get status testb $0x2,%al # Busy? jnz seta20.1 # Yes movb $0xd1,%al # Command: Write outb %al,$0x64 # output port seta20.2: inb $0x64,%al # Get status testb $0x2,%al # Busy? jnz seta20.2 # Yes movb $0xdf,%al # Enable outb %al,$0x60 # A20 seta20.3: sti # Enable interrupts jmp 0x9010 # Start BTX
Note that right before the jump, interrupts are enabled.
[3] There is a file /boot/boot1
, but it
is not the written to the beginning of the FreeBSD slice.
Instead, it is concatenated with boot2
to form boot
, which
is written to the beginning of the FreeBSD
slice and read at boot time.
[4] Actually we did pass a pointer to the slice entry in
register %si
. However,
boot1
does not assume that it was
loaded by boot0
(perhaps some other
MBR loaded it, and did not pass this
information), so it assumes nothing.
[5] In the context of 16-bit real mode, a word is 2 bytes.
[6] 512*16=8192 bytes, exactly the size of
boot
[7] Historically known as “disklabel”. If you ever wondered where FreeBSD stored this information, it is in this region. See bsdlabel(8)
[8] This is necessary for legacy reasons. Interested readers should see http://en.wikipedia.org/wiki/A20_line.
All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/
Questions that are not answered by the
documentation may be
sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.