Boot Sequence: Difference between revisions

[unchecked revision][unchecked revision]
m (→‎Master Boot Record: Added a link to the main MBR page.)
m (Revise broken linux boot process link to archive.org backup)
 
(14 intermediate revisions by 11 users not shown)
Line 1: Line 1:
==POST==
== POST ==


When a computer is switched on or reset, it runs through a series of diagnostics called POST - '''P'''ower-'''O'''n '''S'''elf-'''T'''est. Part of this sequence is the scanning for a bootable device.
When a computer is switched on or reset, it runs through a series of diagnostics called POST - '''P'''ower-'''O'''n '''S'''elf-'''T'''est. This sequence culminates in locating a bootable device, such as a Floppy disk, CD-ROM or a Hard disk in the order that the firmware is configured to.


== Master Boot Record ==
Old computers would always look for a bootable floppy disk in drive <tt>A:\</tt> first, then for a bootable partition on drive <tt>C:\</tt>. Modern BIOS' allow to configure a scan sequence (e.g., "C, SCSI, A").


The (legacy) BIOS checks bootable devices for a boot signature, a so called magic number. The boot signature is in a boot sector (sector number 0) and it contains the byte sequence 0x55, 0xAA at byte offsets 510 and 511 respectively. When the BIOS finds such a boot sector, it is loaded into memory at <tt>0x0000:0x7c00</tt> (segment 0, address 0x7c00). (However, some BIOS' load to <tt>0x7c0:0x0000</tt> (segment 0x07c0, offset 0), which resolves to the same physical address, but can be surprising. A good practice is to enforce CS:IP at the very start of your boot sector.)
==Master Boot Record==


Execution is then transferred to the freshly loaded boot record. On a floppy disk, all 512 bytes of the boot record (save the last two signature bytes) may contain executable code. On a hard drive, the [[MBR (x86)|Master Boot Record]] (MBR) holds executable code at offset 0x0000 - 0x01bd, followed by table entries for the four [[Partition Table|primary partitions]], using sixteen bytes per entry (0x01be - 0x01fd), and the two-byte signature (0x01fe - 0x01ff).
A device is "bootable" if it carries a boot sector with the byte sequence 0x55, 0xAA in bytes 511 and 512 respectively. When the BIOS finds such a boot sector, it is loaded into memory at a specific location; this is ''usually'' <tt>0x0000:0x7c00</tt> (segment 0, address 0x7c00). However, some BIOS' load to <tt>0x7c0:0x0000</tt> (segment 0x07c0, offset 0), which resolves to the same physical address, but can be surprising.


== Early Environment ==
When the wrong CS:IP pair is assumed, absolute near jumps will not work properly, and any code like <tt>mov ax,cs; mov ds,ax</tt> will result in unexpected variable locations. A good practice is to enforce CS:IP at the very start of your boot sector.


This early execution environment is highly implementation defined, meaning the implementation of your particular BIOS. ''Never'' make any assumptions on the contents of registers: They might be initialized to 0, but they might contain a spurious value just as well. This includes the FLAGS register and the SP register, you may not have a valid stack either! The only thing that's certain, is that the DL register holds the drive code from where your boot code was loaded.
AsmExample:
<source lang=asm>
ORG 0x7C00
jmp 0x0000:start
start:
</source>
:''or''
<source lang=asm>
ORG 0
jmp 0x07C0:start
start:
</source>
Execution is then transferred to the freshly loaded boot record. On a floppy
disk, all 512 bytes of the boot record may contain executable code. (Well,
actually 510 bytes due to the two-byte signature.)


The CPU is currently in [[Real Mode]]. (Unless you are running on one of those rare BIOS' which believe they're doing you a favor by activating [[Protected Mode]] for you. Which means you not only have to write code for activating protected mode on any ''other'' hardware, but should also add a test condition if it's already activated.)
:''Is the above information on floppy disk boot records correct?''
::''I second this information. The 'Bios Parameters Block' of a floppy is something that DOS&FAT enforces, but it's perfectly possible to use a floppy without this (except that you have very little clue about what the floppy will be and that you may fail to read a 720KB floppy without this if you cannot tell your OS it's not a 1.44MB disk ...'' -- PypeClicker


== Kernel ==
On a hard drive, the so-called [MBR (x86)|Master Boot Record] (MBR) holds executable code at offset 0x0000 - 0x01bd, followed by table entries for the four [[Partition Table|primary partitions]], using sixteen bytes per entry (0x01be - 0x01fd), and the two-byte signature (0x01fe - 0x01ff).


Finally, the bootloader loads the kernel into memory and passes control to it.
The layout of the table entries is as follows:


== Loading ==
{| {{wikitable}}
! Offset
! Size (bytes)
! Description
|-
| 0x00
| 1
| Boot Indicator (0x80=bootable, 0x00=not bootable)
|-
| 0x01
| 1
| Starting Head Number
|-
| 0x02
| 2
| Starting Cylinder Number (10 bits) and Sector (6 bits)
|-
| 0x04
| 1
| Descriptor (Type of partition/filesystem)
|-
| 0x05
| 1
| Ending Head Number
|-
| 0x06
| 2
| Ending Cylinder and Sector numbers
|-
| 0x08
| 4
| Starting Sector (relative to beginning of disk)
|-
| 0x0C
| 4
| Number of Sectors in partition
|}

More detailed information about MBR structure and partition types can be found on [http://www.nondot.org/sabre/os/articles/TheBootProcess/ OSRC]. The MBR is usually written by disk partitioning programs such as FDisk, Disk Druid, [http://www.ranish.com/part/ Ranish Partition Manager] etc.

==Early Environment==

This early execution environment is highly implementation defined, meaning the implementation of your particular BIOS. ''Never'' make any assumptions on the contents of registers. They might be initialized to 0, but they might contain a random value just as well.

The CPU is currently in [[Real Mode]], unless you are running on one of those rare BIOS' which believe they're doing you a favor by activating [[Protected Mode]] for you. (Which means you not only have to write code for activating protected mode on any ''other'' hardware, but should also add a test condition if it's already activated.)

==Kernel Image==

Now we jump two steps ahead and look at where we want to go: Our kernel image. Your boot record would be easiest if it could just copy the kernel image from disk to memory and jump to some given offset. Unfortunately, unless you take extra precautions, your compiler adds all sort of startup code, relocation tables etc. To get a "flat binary" that can be loaded in this simple copy-and-run way, you have to tell [[GCC]]:

gcc -c my_kernel.c
ld my_kernel.o -o kernel.bin --oformat=binary -Ttext=0x100000

The <tt>-c</tt> switch tells GCC to stop right after compilation, i.e. not to link the object file.

The <tt>--oformat=binary</tt> switch tells the linker you want your output file to be a plain binary image (no startup code, no relocations, ...).

The <tt>-Ttext=0x100000</tt> tells the linker you want your "text" (code segment) address to start at the 1mb memory mark. Since you do not link in any relocation tables, the linker has to resolve all references at link time, and has to know where the executable will be loaded to.

You are of course obliged to load your kernel image to the correct offset, or the references the linker did set up will be invalid.

:''It appears that some versions of <tt>ld</tt> prefer <tt>-oformat</tt> rather than <tt>--oformat</tt>. If you're trying to use this tutorial on a non-x86 platform, run <tt>ld --help</tt> if weird things occur.''

:''Make also sure that you correctly set the base of '''''all''''' the sections. Especially when using a linker script, omitting to set the base of <tt>.rodata</tt> or one of the others may make your kernel grew up to 1MB suddenly just because the linker has created a file that can contain both a 0-based <tt>.rodata</tt> and a 1MB-based <tt>.text</tt> ... And if you're trying to run a [[Higher Half Kernel]], it may even crash the linker while it tries to create a 3GB binary file full of zeroes...''

:''Additional related problems may occur when using -Ox type parameters to GCC, in this case strings can be moved from <tt>.rodata</tt> to similar named sections. If your linker script doesn't account for this then your image may expand. To prevent this happening, <tt>.rodata*</tt> should be used instead of <tt>.rodata</tt>.''

You may also check the [http://files.osdev.org/mirrors/geezer/osd/gotchas/ OSD gotchas] to look at well-known issues with common OSdev tools.

==Loading==


Now we know ''what'' we have to load, let's see ''how'' we get it loaded.
Now we know ''what'' we have to load, let's see ''how'' we get it loaded.
Line 115: Line 31:
* preparing the runtime environment for the kernel (e.g. setting up stack space);
* preparing the runtime environment for the kernel (e.g. setting up stack space);


You don't have to do things in this order, but all of this has to be done before you can call <tt>kmain()</tt> or whatever you called your kernel entry function.
You don't have to do things in this order, but all of this has to be done before you can call <tt>kmain()</tt>.


To make things worse, GCC generates protected mode executables only, so the code for this early environment is one of the [[C#Things C can't do|Things You Cannot Do With C]].
To make things worse, GCC generates protected mode executables only, so the code for this early environment is one of the [[C#Things C can't do|Things You Cannot Do With C]].
Line 125: Line 41:
* '''Two-stage loading''': Write a ''separate'' stub program which is loaded below the 1mb memory mark, and does everything from the above list.
* '''Two-stage loading''': Write a ''separate'' stub program which is loaded below the 1mb memory mark, and does everything from the above list.


===The Traditional Way===
=== The Traditional Way ===


Traditionally, the MBR relocates itself to <tt>0x0000:0x0600</tt>, determines the active partition from the partition table, loads the first sector of that partition (the "partition boot record") to <tt>0x0000:0x7c00</tt> (hence the previous relocation), and jumps to that address. This is called "chain loading". If you want your self-written boot record to be capable of dual-booting e.g. Windows, it should mimic this behaviour.
Traditionally, the MBR relocates itself to <tt>0x0000:0x0600</tt>, determines the active partition from the partition table, loads the first sector of that partition (the "partition boot record") to <tt>0x0000:0x7c00</tt> (hence the previous relocation), and jumps to that address. This is called "chain loading". If you want your self-written boot record to be capable of dual-booting e.g. Windows, it should mimic this behaviour.


===Easy Way Out===
=== Easy Way Out ===


Unless you really want to be [[Rolling Your Own Bootloader]] (record / stubs) for the educational value, we recommend using readily available [[:Category:bootloaders|bootloaders]].
Unless you really want to be [[Rolling Your Own Bootloader]] (record / stubs) for the educational value, we recommend using readily available [[:Category:bootloaders|bootloaders]].


The most prominent one is [[GRUB]], a two-stage bootloader that not only provides a boot menu with chainloading capability, but initializes the early environment to a well-defined state (including [[Protected Mode]] and reading various interesting information from the BIOS), can load generic executables as kernel images (instead of requiring flat binaries like most other bootloaders), supports optional kernel modules, various file systems, and if <tt>./configure</tt>'d correctly, [[Diskless Booting]].
The most prominent one is [[GRUB]], a two-stage bootloader that not only provides a boot menu with chainloading capability, but initializes the early environment to a well-defined state (including [[Protected Mode]] and reading various interesting information from the BIOS), can load generic executables as kernel images (instead of requiring flat binaries like most other bootloaders), supports optional kernel modules, various file systems, and if <tt>./configure</tt>'d correctly, [[Diskless Booting]].

=== Some methods ===
There are many possible variants to boot. Below is a list of methods but it is possible that there are even more methods:
* You could take an unused partition and load the stage 2 "raw"
* You could place the stage 2 between MBR and start of the first partition
* You could (as Lilo did) write a kernel file, then use a tool to detect the sectors (or clusters). Then let stage 1 load the sectors from the list.
* DOS and Windows do it this way: Create an empty filesystem (format it) and then place the kernel in the first file, and the shell in the second file in the empty rootdir. So the loader simply loads the first entry in the rootdir and then the second.
* Old Linux was booting from floppy disk. The first sector ("boot") loaded the second stage in "raw" mode = without filesystem (The scond stage was"setup", in the sectors directly behind "boot") The second stage did setup the system (video mode, memory map, etc.) and then loaded the real kernel image (packed in tgz/ bz).
* Several years ago here was a bootloader (called "nuni") which switched to pmode and loaded a file, all in one bootsector


== See Also ==
== See Also ==

=== Articles ===

=== Threads ===


=== External Links ===
=== External Links ===
* [http://duartes.org/gustavo/blog/post/how-computers-boot-up Jun 2008: How Computers Boot Up] by Gustavo Duarte.
* [http://duartes.org/gustavo/blog/post/how-computers-boot-up Jun 2008: How Computers Boot Up] by Gustavo Duarte.
* [http://duartes.org/gustavo/blog/post/kernel-boot-process Jun 2008:The Kernel Boot Process] by Gustavo Duarte.
* [http://duartes.org/gustavo/blog/post/kernel-boot-process Jun 2008:The Kernel Boot Process] by Gustavo Duarte.
* IBM developerWorks' [http://www.ibm.com/developerworks/library/l-linuxboot/index.html Inside the Linux boot process] a very good, illustrated overview from BIOS to userspace.
* IBM developerWorks' [https://web.archive.org/web/20190402174801/https://developer.ibm.com/articles/l-linuxboot/ Inside the Linux boot process] a very good, illustrated overview from BIOS to userspace.

[[Category:Booting]]
[[Category:Bootloaders]]
[[Category:Bootloaders]]
[[Category:X86]]
[[Category:X86]]

Latest revision as of 05:29, 16 May 2024

POST

When a computer is switched on or reset, it runs through a series of diagnostics called POST - Power-On Self-Test. This sequence culminates in locating a bootable device, such as a Floppy disk, CD-ROM or a Hard disk in the order that the firmware is configured to.

Master Boot Record

The (legacy) BIOS checks bootable devices for a boot signature, a so called magic number. The boot signature is in a boot sector (sector number 0) and it contains the byte sequence 0x55, 0xAA at byte offsets 510 and 511 respectively. When the BIOS finds such a boot sector, it is loaded into memory at 0x0000:0x7c00 (segment 0, address 0x7c00). (However, some BIOS' load to 0x7c0:0x0000 (segment 0x07c0, offset 0), which resolves to the same physical address, but can be surprising. A good practice is to enforce CS:IP at the very start of your boot sector.)

Execution is then transferred to the freshly loaded boot record. On a floppy disk, all 512 bytes of the boot record (save the last two signature bytes) may contain executable code. On a hard drive, the Master Boot Record (MBR) holds executable code at offset 0x0000 - 0x01bd, followed by table entries for the four primary partitions, using sixteen bytes per entry (0x01be - 0x01fd), and the two-byte signature (0x01fe - 0x01ff).

Early Environment

This early execution environment is highly implementation defined, meaning the implementation of your particular BIOS. Never make any assumptions on the contents of registers: They might be initialized to 0, but they might contain a spurious value just as well. This includes the FLAGS register and the SP register, you may not have a valid stack either! The only thing that's certain, is that the DL register holds the drive code from where your boot code was loaded.

The CPU is currently in Real Mode. (Unless you are running on one of those rare BIOS' which believe they're doing you a favor by activating Protected Mode for you. Which means you not only have to write code for activating protected mode on any other hardware, but should also add a test condition if it's already activated.)

Kernel

Finally, the bootloader loads the kernel into memory and passes control to it.

Loading

Now we know what we have to load, let's see how we get it loaded.

If booting from hard drive, you have only 446 bytes available for your boot record. Looking at the list of things to do before your kernel image can run, you will agree that this is not much:

  • determine which partition to boot from (either by looking for the active partition, or by presenting the user with a selection of installed operating systems to chose from);
  • determine where your kernel image is located on the boot partition (either by interpreting the file system, or by loading the image from a fixed position);
  • load the kernel image into memory (requires basic disk I/O);
  • enable protected mode;
  • preparing the runtime environment for the kernel (e.g. setting up stack space);

You don't have to do things in this order, but all of this has to be done before you can call kmain().

To make things worse, GCC generates protected mode executables only, so the code for this early environment is one of the Things You Cannot Do With C.

There are several approaches to this problem:

  • Geek loading: Squeeze everything from the above list into the boot record. This is next to impossible, and does not leave room for any special-case handling or useful error messages.
  • One-stage loading: Write a stub program for making the switch, and link that in front of your kernel image. Boot record loads kernel image (below the 1mb memory mark, because in real mode that's the upper memory limit!), jumps into the stub, stub makes the switch to Protected Mode and runtime preparations, jumps into kernel proper.
  • Two-stage loading: Write a separate stub program which is loaded below the 1mb memory mark, and does everything from the above list.

The Traditional Way

Traditionally, the MBR relocates itself to 0x0000:0x0600, determines the active partition from the partition table, loads the first sector of that partition (the "partition boot record") to 0x0000:0x7c00 (hence the previous relocation), and jumps to that address. This is called "chain loading". If you want your self-written boot record to be capable of dual-booting e.g. Windows, it should mimic this behaviour.

Easy Way Out

Unless you really want to be Rolling Your Own Bootloader (record / stubs) for the educational value, we recommend using readily available bootloaders.

The most prominent one is GRUB, a two-stage bootloader that not only provides a boot menu with chainloading capability, but initializes the early environment to a well-defined state (including Protected Mode and reading various interesting information from the BIOS), can load generic executables as kernel images (instead of requiring flat binaries like most other bootloaders), supports optional kernel modules, various file systems, and if ./configure'd correctly, Diskless Booting.

Some methods

There are many possible variants to boot. Below is a list of methods but it is possible that there are even more methods:

  • You could take an unused partition and load the stage 2 "raw"
  • You could place the stage 2 between MBR and start of the first partition
  • You could (as Lilo did) write a kernel file, then use a tool to detect the sectors (or clusters). Then let stage 1 load the sectors from the list.
  • DOS and Windows do it this way: Create an empty filesystem (format it) and then place the kernel in the first file, and the shell in the second file in the empty rootdir. So the loader simply loads the first entry in the rootdir and then the second.
  • Old Linux was booting from floppy disk. The first sector ("boot") loaded the second stage in "raw" mode = without filesystem (The scond stage was"setup", in the sectors directly behind "boot") The second stage did setup the system (video mode, memory map, etc.) and then loaded the real kernel image (packed in tgz/ bz).
  • Several years ago here was a bootloader (called "nuni") which switched to pmode and loaded a file, all in one bootsector

See Also

External Links