Making the Boot Loader go Faster
First published on the PARSE Software Devices website September 19th, 2004
© Copyright 2004 by Robert Krten, all rights reserved.
Synopsis:
This article describes how to modify the QNX-supplied master boot record (MBR) bootloader (the "primary bootloader"),
and the secondary boot loader, to minimize the boot up time for deeply embedded systems.
It assumes that your x86 box has a BIOS which then transfers control to the primary bootloader.
The speedup is accomplished by modifying ("patching") the bootloaders.
Background
When the PC boots, the x86 processor goes to the reset vector
located at the last 16 bytes of memory. This reset vector
is located in the BIOS that comes with your computer.
The BIOS is responsible for setting up the various chipsets
for your board, performing memory tests, etc., and finally
determining what kind of mass storage devices you have.
When all these activites are completed, the BIOS then transfers
control to the first block of the first valid mass media storage
device. (You select in the BIOS the particular search order that
the BIOS uses to determine which device that is -- for purposes of
discussion, we'll assume it's the first EIDE device).
This loader is called the "Primary Boot Loader", and its job is
to load the "next thing" (usually the operating system, or another loader).
Most primary boot loaders give you the option to load one of several
different operating systems, depending on how many partitions you have
on your hard disk.
The primary boot loader that comes with QNX 6 allows you to press a
button to indicate which disk you wish to boot from, and/or which
partition on the disk you wish to boot.
In a deeply embedded system, of course, there's no real requirement
to choose an operating system -- you simply want to transfer control
to the one and only operating system and boot as quickly as possible.
We will discuss the modification necessary to accomplish this.
Once the primary boot loader has selected an operating system to boot,
and we'll assume it's QNX 6 for our discussion here, the primary boot
loader loads the secondary boot loader.
The secondary boot loader is similar to the primary boot loader -- its
job is to load the "next thing" -- in this case, the operating system
startup code.
The secondary boot loader that comes with QNX 6 allows you to press a
button to indicate if you wish to load the operating system image from
the file "/.boot" or from the file "/.altboot" -- again, not something
you wish to waste time on in a deeply embedded system.
Modification of Primary Boot Loader
So the first thing we want to do is remove the delay from the primary
boot loader, so that it goes "right away" to the secondary boot loader.
I'm somewhat cautious by nature, so instead of entirely "removing"
the ability for the user to specify which partition and disk they
wish to boot from, I simply made the timeout much smaller than the
default. This also minimizes the amount of code change, which is always
a Good Thing.
If you disassemble the primary boot loader, located in the first 512
bytes of the hard disk ("head -c -n512 /dev/hd0 | hd -v" will get you a HEX dump), you'll see
the following code:
009d b9 48 00 mov cx, 0048 ; load timeout constant into CX
00a0 b4 01 mov ah, 01 ; INT 16 service AH=1 "Test if keyboard character available"
00a2 55 push bp ; save
00a3 51 push cx ; save
00a4 cd 16 int 16 ; do the BIOS call
00a6 59 pop cx ; restore
00a7 5d pop bp ; restore
00a8 75 17 jnz 00c1 ; goto 00C1 if a key is available
00aa 06 push es ; save ES, we're going to go look at the BIOS data segment
00ab ba 40 00 mov dx, 0040
00ae 8e c2 mov es, dx
00b0 26 es:
00b1 8b 16 6c 00 mov dx, [006c] ; get current value of BIOS tick counter (0040:006C)
00b5 26 es:
00b6 3b 16 6c 00 cmp dx, [006c] ; is it the same?
00ba 74 f9 jz 00b5 ; yes, try again
00bc 07 pop es
00bd e2 e1 loop 00a0 ; no, some time has elapsed, go try for another key again
The above is a disassembly of just one of the many boot loaders provided by QNX -- here's
another one:
0067 b9 48 00 mov cx, 0048 ; the same, just at a different offset
006a b4 01 mov ah, 01
006c 55 push bp
006d 51 push cx
006e cd 16 int 16
0070 59 pop cx
0071 5d pop bp
0072 75 18 jnz 008c ; note different relative offset value (0x18 vs 0x17)
0074 06 push es
0075 ba 40 00 mov dx, 0040
0078 8e c2 mov es, dx
007a 26 es:
007b 8b 16 6c 00 mov dx, [006c]
007f 26 es:
0080 3b 16 6c 00 cmp dx, [006c]
0084 74 f9 jz 007f
0086 07 pop es
0087 e2 e1 loop 006a
Almost the same, but at a different location, and with a different relative jump offset
at address 0x0072.
The point of this is -- BEWARE. Make sure you understand what you are looking for
in the bootloader BEFORE patching your disk. A good thing to do is to either
disassemble the code yourself and look for the characteristic loop based on CX
and the int 0x16 BIOS call, or just be supremely confident that you can restore your
disk :-)
Basically, the idea is that CX contains a timeout value (0x48, or 72 decimal) that's used
in the loop between 0x00A0 and 0x00BD (or 0x006A and 0x0087 in the second example).
When this loop count reaches zero, it indicates that
no characters were detected and the primary boot loader should go off and do whatever its
normal default action is -- which is booting the partition identified in the partition
table as the default boot partition.
Since this is exactly what we want, all we need to do is adjust the timeout value to be
smaller.
Consulting the IBM PC/AT Technical Reference manual, we see that 0040:006C is identified as "TIMER_LOW",
and commented as "low word of timer count". This value gets bumped 18.2 times per
second, so the CX value of 0x48 is approximately 72 / 18.2 = 3.95 seconds.
If we simply patch location 0x009E (or 0x0068) from 0x48 to something much smaller, like say "1" or "2",
this will meet the requirements of greatly decreasing the bootup time.
Modification of the Secondary Boot Loader
The secondary boot loader is the one that prompts us with "Hit Esc for .altboot", and then loads
the operating system image.
Again, we want to minimize the delay here.
If you disassemble the secondary boot loader, located in the first
512 bytes of the partition ("head -c -n512 /dev/hd0t79 | hd -v" will get you a HEX dump), you'll
see the following:
007f b9 24 00 mov cx, 0024 ; load timeout constant into CX
0082 b8 00 01 mov ax, 0100 ; INT 16 service AH=1 "Test if keyboard character available"
0085 cd 16 int 16
0087 75 12 jnz 009b ; if key present, go to 009B
0089 1e push ds
008a 31 c0 xor ax, ax
008c 8e d8 mov ds, ax
008e 8b 16 6c 04 mov dx, [046c] ; get current value of BIOS tick counter (0000:046C (same as 0040:006C))
0092 3b 16 6c 04 cmp dx, [046c] ; changed?
0096 74 fa jz 0092 ; nope, wait for change
0098 1f pop ds
0099 e2 e7 loop 0082 ; yes changed, go back to CX loop at 0082
Again, be careful to ensure that your bootloader is identical to the sample shown here!
Here's another common one -- exact same, just at a slightly different location...
0071 b9 24 00 mov cx, 0024
0074 b8 00 01 mov ax, 0100
0077 cd 16 int 16
0079 75 12 jnz 008d
007b 1e push ds
007c 31 c0 xor ax, ax
007e 8e d8 mov ds, ax
0080 8b 16 6c 04 mov dx, [046c]
0084 3b 16 6c 04 cmp dx, [046c]
0088 74 fa jz 0084
008a 1f pop ds
008b e2 e7 loop 0074
Here, CX is loaded with 0x24 (36 decimal) which works out to 36/18.2 or 1.98 seconds.
Simply patching this number with a far smaller number accomplishes our goal.
I hope this tutorial has been helpful.
Remember, if you screw up your system, miss your deadline, and you get fired, it's your fault -- if you make your system
boot faster and you get a raise, it's my fault :-)
Enjoy
-RK
|