One of the difficulties I faced in the development of an x86 simulator, dax86 was the end-to-end understanding of the OS image structure. This article talks about the creation of the booting block and how it is formed into the OS image as well as how hardware loads and executes it.
Makefile is where we start obviously. It has all the setup for creating different binaries and linked objects required to compose the OS image. As you can see below,
xv6.img is requiring
kernel and executing
dd command to write them. This part has some key information for locating different files, so I’ll come back to it in the later part of this article.
From the codes below, we can see
bootblock is requiring
bootasm.S which is a small asm mainly for entering into the protected mode and
bootmain.c which copies kernel from disk to memory and jumps to it.
Here I’d like to step back a bit and think of how a machine starts from the very beginning. Traditional computer with BIOS executes POST (power-on self test) once the power is on, followed by some configuration for MP from option ROM. After that it tries to find this signature of
0xAA55 at the last two bytes of the first sector of a bootdisk.
Below is the result of running
xxd command on the OS image. We can observe the signature at the address
0x1FE which is 2 bytes before the end of the sector (512 bytes),
The last line of bootblock section of Makefile shown above:
./sign.pl bootblock is actually creating this signature as below:
So we have traced until BIOS finds the bootable signature which is called master boot record (MBR). After this, BIOS copies this sector into memory typically at
0x7C00 and starts execution from there, which is the reason we see linker commands as
$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o. Here
text secion is specified to be
0x7C00 so that the base address for instructions would match the addresses after BIOS loads the sector.
Single sector of data loaded into memory is never enough to include the kernel codes of the OS. Its job is to load more data of the kernel instructions from disk, and it’s left to OS developer to make it work. In xv6, the kernel block is located and loaded from the second sector of the OS image. It took me some time to figure this out as tracing the whole flow involved IO communication with disk.
In the code below, we can see the
offset is sector number with one added to the parameter:
On the OS image creation, it’s specified as below:
seek=1 is the key.
seek=n is the option of
dd command to speficy the number of blocks to be skipped. As the default block size of
dd is 512 bytes which is a sector, we can say
seek=1 is for skipping a sector which is for the boot block. Subsequently as showen in the
bootmain.c code above, xv6 skips the first sector when it loads the kernel codes into its memory.