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.
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.
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 :-)