http://blog.chinaunix.net/uid/16979052.html
全部博文(286)
分类: LINUX
2013-03-24 08:10:44
在i386平台下,Linux内核使用了一个相当复杂的启动协议。这要部分归咎于历史原因,因为早期的内核需要被做成一个可启动镜像,其他原因还包括,复杂的计算机内存模型,由于实模式DOS作为主流操作系统而改变了计算机工业的预期发展等等。
当前共有以下Linux/i386启动协议存在着:
旧版内核: |
仅仅支持zImage/Image。一些早期的内核甚至不支持命令行。 |
2.00版协议: |
(内核版本 1.3.73)加入了bzImage和initrd的支持,也拥有了一种正规化的方法来实现启动装载器(* boot loader)和内核间的通信。setup.S建造了一块可移动,但是仍旧可写的传统的安装程序加载区域。 |
2.01版协议: |
(内核版本 1.3.76)加入一系列大量的溢出警告。 |
2.02版协议: |
(内核版本2.4.0-test3-pre3)这是新的命令行协议。它降低了常规的内存使用上限(*见以下MEMORY LAYOUT内存布局介绍)。没有覆盖传统的安装程序区域,因此对于那些使用了来自SMM或者32位BIOS入口地址的EBDA(*Extended BIOS Data Area,拓展BIOS数据域)的系统,这样做可以使得启动更加安全。 |
2.03版协议: |
(内核版本 2.4.18-pre1)明确定义了高端的initrd地址可以被启动装载器使用。 |
2.04版协议: |
(内核版本 2.6.14)将syssize域拓展到四个字节。 |
2.05版协议: |
(内核版本 2.6.20)使得保护模式下的内核部分可被重新定位(* 可被移动) 。同时引入了relocable_kernel和kernel_alignment域。 |
2.06版协议: |
(内核版本 2.6.22)加入了一个新域(* cmdline_size),以存储启动命令行的大小。 |
内存布局
使用了Image或者zImage的启动加载器(the kernel loader),其在传统上内存布局大致如下
图所示:
|
|
0A0000 +------------------------+
| Reserved for BIOS | 未使用,被BIOS
EBDA保留
09A000
+------------------------+
|
Command line
|
| Stack/heap
| 被实模式下的内核代码所使用
098000
+------------------------+
|
kernel setup
| 实模式下的内核代码
090200
+------------------------+
|
kernel boot sector | 历史遗留下来的内核启动扇区
090000
+------------------------+
|
Protected-mode kernel | 内核镜像的主要部分
010000
+------------------------+
|
Boot loader
| - 启动扇区的入口位置 0000:7c00
001000
+------------------------+
|
Reserved for MBR/BIOS |
000800
+------------------------+
|
Typically used by MBR |
000600
+------------------------+
|
BIOS use only
|
000000 +------------------------+
以上的特殊名词翻译
Stack/heap 堆区/栈区
kernel setup 内核安装程序
Boot loader 启动装载器
当使用bzImage的时候,保护模式下的内核部分就被重定位到0x100000(高端内存),而实模式下的内核块(包括启动扇区,安装程序,和堆栈部分)就可以自由定位到0x10000和低段内存区域的任意位置。不幸的是,在2.00和2.01版启动协议中,0x90000以上的内存区域仍旧被内核所使用;2.02版启动协议就解决了这个问题。
人们总想让内存上限——启动装载器可触的低端内存的最高点——尽可能的低,越低越好,这是因为一些新的BIOS已经开始分配相当大的内存区域,这片区域被称为拓展BIOS数据区(EBDA),位于低端内存的顶端。启动装载器会使用BIOS的0x21号中断来判断有多少低端内存可以使用。
不幸的是,如果INT 12h报告的内存大小太少,启动装载器将没有任何用处,此时只能向用户报告错误。因而这个启动加载器根据要求被设计成更小的尺寸。对于zImage或者旧式bzImage版本内核,它们可能会需要在0x90000段写入数据,这时启动装载器应该确保不会占用0x9a000以上内存;太多的BIOS破坏了以上原则(* 指BIOS分配太多区域用作EBDA,而导致低端内存空闲空间过小)。
而现在的bzImage内核已经使用2.02(包括2.02)以后的启动协议,也将建议使用一下内存布局方式:
~
~
| Protected-mode
kernel |
100000 +------------------------+
| I/O memory hole |
0A0000
+------------------------+
| Reserved for BIOS | 尽可能预留更多空闲空间
~
~
|
Command line
| (也可置于 X+10000 标记下)
X+10000
+------------------------+
| Stack/heap | 被实模式下的内核代码所使用
X+08000
+------------------------+
| Kernel setup
| 实模式下的内核代码
| Kernel boot sector | 历史遗留下来的内核启动扇区
X
+------------------------+
| Boot loader
| - 启动扇区的入口位置 0000:7c00
001000
+------------------------+
| Reserved for MBR/BIOS |
000800
+------------------------+
| Typically used by MBR |
000600
+------------------------+
| BIOS use only
|
000000 +------------------------+
... X位置应和boot
loader设计允许的上限一样低。
**** 实模式部分的内核头部
下面的段落中,还有任何关于内核启动工序的描述中,"一个扇区"意指512个字节。他和实际使用的底层媒体中的(* 扇区概念)是独立的,不同的。
装载Linux内核的第一步,应该是装载实模式部分的内核代码(包括启动扇区和安装程序代码),然后检查接着的头部是否位于0x01f1(偏移量)。尽管启动装载器只会选择装载前两个扇区(1K)并且接着检查启动扇区的大小,但是实模式代码合计可以达到32K。
头部的定义可以如一下情况:
偏移/大小 |
协议 |
命名 |
意义 |
01F1/1 |
ALL(1 |
setup_sects |
安装程序的大小(单位:扇区) |
01F2/2 |
ALL |
root_flags |
如果设置,那么root分区将会被加载为只读 |
01F4/4 |
2.04+(2 |
syssize |
32位代码段尺寸(用两个16位字长表示) |
01F8/2 |
ALL |
ram_size |
不可用!-仅被bootsect.S使用 |
01FA/2 |
ALL |
vid_mode |
视频模式控制 |
01FC/2 |
ALL |
root_dev |
默认的根目录所在的设备号 |
01FE/2 |
ALL |
boot_flag |
0xAA魔术数字(* boot sector结束标志) |
0200/2 |
2.00+ |
jump |
JUMP指令 |
0202/4 |
2.00+ |
header |
魔术签名“HdrS” |
0206/2 |
2.00+ |
version |
支持的启动协议版本号 |
0208/4 |
2.00+ |
realmode_swtch |
启动装载器的hook(见下文) |
020C/2 |
2.00+ |
start_sys |
低端地址中的系统位置(0x1000),已经废弃 |
020E/2 |
2.00+ |
kernel_version |
指向内核版本字符串的指针 |
0210/1 |
2.00+ |
type_of_loader |
启动装载器的标志符 |
0211/1 |
2.00+ |
loadflags |
启动协议的选项标志位 |
0212/2 |
2.00+ |
setup_move_size |
将要移动的(* 安装程序)大小(和hook并用) |
0214/4 |
2.00+ |
code32_start |
启动装载器的hook(见下文) |
0218/4 |
2.00+ |
ramdisk_image |
initrd加载位置(由启动装载器给定) |
021C/4 |
2.00+ |
ramdisk_size |
initrd尺寸(由启动装载器给定) |
0220/4 |
2.00+ |
bootsect_kludge |
不可用! - 仅被bootsect.S使用 |
0224/2 |
2.01+ |
heap_end_ptr |
安装程序末尾之后的空闲位置 |
0226/2 |
N/A |
pad1 |
未使用 |
0228/4 |
2.02+ |
cmd_line_ptr |
32位指针,指向内核命令行 |
022C/4 |
2.03+ |
initrd_addr_max |
合法的最高位initrd位置 |
0230/4 |
2.05+ |
kernel_alignment |
kernel需要的物理地址对齐值 |
0234/1 |
2.05+ |
relocatable_kernel |
显示kernel是否可以重定位 |
0235/3 |
N/A |
pad2 |
未使用 |
0238/4 |
2.06+ |
cmdline_size |
内核命令行最大尺寸 |
(1) 考虑到向后兼容性,如果setup_sects域是0,那么他的真实值就是4。
(2) 对于2.04以前的协议,syssize前两个字节并未使用,这意味着bzImage的大小无法确定。
如果在偏移0x202的位置无法找到魔术数字“HdrS”(0x53726448),那么这个启动协议版本可被归结至“旧版内核”。装载一个旧版本内核,就需要假设以下参数:
Image type = zImage
不支持initrd
实模式部分的内核将被装载到0x90000
否则,(* 紧接“HdrS”的)“version”域就会包含启动协议版本。例如,协议版本2.01,“version”域的值为“0x0201”。当在头部设定此域后,你必须确认已经设定了此协议版本使用到的其他域。
**** 详述头部域的细节情况
对于这里的每个域,有些是内核和启动加载器的信息(将在以下标记为“Read”类型),有些
则是将会被启动加载器填写(标记为“Write”),而又有些可能会被启动加载器读取后又更
改(标记为“Modify”)。
通用的启动加载器,其填写的域被标记为(“obligatory”)。将内核加载到非标准化地址的
启动加载器,其填写的域被标记为(“reloc”);而其余的的启动加载器就会忽略这些域。
所有域的字节顺序为从小到大排列(毕竟这是x86)。
Field
name(域的名称): setup_secs
Type(类型值,* 以上有描述): read
Offset/size(偏移/字节数): 0x1f1/1
Protocol(适用协议): ALL
用扇区数表示的安装程序代码大小。如果此域为0,那么真实值即是4。实模式下的代码由
引导扇区(通常为一个扇区大小)和安装程序代码组成。
Field
name: root_flags
Type: modify (optional)
Offset/size:
0x1f2/2
Protocol:
ALL
如果这个域非0,根分区将默认为只读。在这里并不赞成使用此域,用命令行的“ro”和
“rw”选项代替吧。
Field
name: syssize
Type: read
Offset/size: 0x1f4/4 (protocol 2.04+)
0x1f4/2 (protocol ALL)
Protocol:
2.04+
保护模式下的内核代码大小,使用两个16位表示。由于2.04以前的协议中此域只有两字节
长度(*
16位),因此如果LOAD_HIGH标志被设定,内核大小就不会被一味的信任。
Field
name: ram_size
Type: kernel internal
Offset/size:
0x1f8/2
Protocol: ALL
此域已经被废弃。
Field
name: vid_mode
Type: modify (obligatory)
Offset/size:
0x1fa/2
请查看 特殊命令行选项 章节。
Field
name: root_dev
Type: modify (optional)
Offset/size:
0x1fc/2
Protocol:
ALL
默认的根目录分区所在的设备号。不建议使用此域,使用命令行的“root=”来替代它。
Field
name: boot_flag
Type: read
Offset/size: 0x1fe/2
Protocol:
ALL
域的值为0xAA55,这是最接近旧版Linux内核的一个魔术数字。
Field
name: jump
Type: read
Offset/size: 0x200/2
Protocol:
2.00+
包含一个x86跳转指令,是一个带符号的相对于0x202的偏移量,接着是0xEB。这可以用来
决定头部的大小。
Field
name: header
Type: read
Offset/size: 0x202/4
Protocol:
2.00+
域的值为“HdrS”(0x53726448).
Field
name: version
Type: read
Offset/size: 0x206/2
Protocol:
2.00+
启动协议的版本号,使用了(整数部分<<8)+ 小数部分的格式。
比如:0x0204代表2.04版本,0x0a11代表了假想的10.17版本。
Field
name: readmode_swtch
Type: modify (optional)
Offset/size:
0x208/4
Protocol: 2.00+
启动装载器的hook(见以下 高级的启动装载器hook)。
Field
name: start_sys
Type: read
Offset/size: 0x20c/4
Protocol:
2.00+
低端内存中的系统位置(0x1000)。已废弃。
Field
name: kernel_version
Type: read
Offset/size: 0x20e/2
Protocol:
2.00+
如果此项非0,那么它就是一个指针,其值为以空值(NULL)结尾的,可识别的内核版本
号的字符串的地址,再减掉0x200。它用来向用户显示内核的版本。这个值应小于(0x200
*
setup_sects)。
例如,如果此项被设定为0x1c00,内核版本号的字符串就可能在内核文件中偏移0x1e00的
位置找到。当且仅当“setup_sects”值大于等于15的时候,这个数值合法。如下所示:
0x1c00
< 15 * 0x200 (= 0x1e00) 但是
0x1c00
>= 14 * 0x200 (= 0x1c00)
0x1c00 >> 9 =
14,因此setup_secs最小值是15。
Field
name: type_of_loader
Type: write (obligatory)
Offset/size:
0x210/1
Protocol:
2.00+
如果你的启动加载器有一个已经给定的id号(见下表),在这里就可输入0xTV,其中,T
是一个启动加载器的标志符,而V是一个版本号。否则,就在这里输入0xff。
可选的启动加载器的id:
0
LILO (0x00为pre-2.00装载器所保留)
1
Loadlin
2 bootsect-loader (0x20,其他0x2V都被保留)
3
SYSLINUX
4 EtherBoot
5 ELILO
7 GRuB
8 U-BOOT
9 Xen
A
Gujin
B Qemu
如果你有一个已用(* 且未被列出)的启动装载器的ID,请联系
[hpa@zytor.com]
Field
name: loadflags
Type: modify (obligatory)
Offset/size:
0x211/1
Protocol: 2.00+
此域是一个比特掩码。
Bit
0 (read): LOADED_HIGH
- 如果是0,保护模式部分的代码将被加载到0x10000。
- 如果是1, 保护模式部分的代码将被加载到0x100000。
Bit
7 (write):
CAN_USE_HEAP
将此项设置1,可以指明heap_end_ptr定义有效。如果此项清空,一些安装程序的
功能将会被禁用。
Field
name: setup_move_size
Type: modify (obligatory)
Offset/size:
0x212/2
Protocol:
2.00-2.01
当使用2.00或者2.01版协议时,如果实模式部分的内核没有被装载到0x90000,那他将在
加载流程中的靠后部分移动至那里。如果除了实模式代码本身以外,你有另外的数据(例
如,内核命令行)想移动,那么请填写此域。
这块单元是以启动装载器作为开头的几个字节。
如果你的启动协议版本高过2.02(包括2.02),或者实模式代码已经被装载到0x90000,那
么此项可以忽略。
Field
name: code32_start
Type: modify (optional, reloc)
Offset/size:
0x214/4
Protocol:
2.00+
跳转到保护模式的地址。默认下,这应该是内核所加载的位置,且此项被启动加载器来判
断合适的加载地址。
此域可被用作以下用途:
用作启动装载器的hook
如果一个启动装载器没有安装hook,却又将可移动内核加载到非标准化的地址,那么
他必须修改此域来指向要加载的地址位置。
Field
name: ramdisk_image
Type: write (obligatory)
Offset/size:
0x218/4
Protocol:
2.00+
用作初始化的ramdisk或者ramfs的32位线性地址。如果没有初始化的ramdisk/ramfs,请
将此位置0。
Field
name: ramdisk_size
Type: write (obligatory)
Offset/size:
0x21c/4
Protocol:
2.00+
用作初始化的ramdisk或者ramfs的大小。如果没有初始化的ramdisk/ramfs,请
将此位置0。
Field
name: bootsect_kludge
Type: kernel internal
Offset/size:
0x220/4
Protocol: 2.00+
此域已废弃。
Field
name: heap_end_ptr
Type: write (obligatory)
Offset/size:
0x224/2
Protocol:
2.01+
将此域设定为安装程序使用的堆区/栈区的位置(指相对于实模式代码的偏移),再减去
0x200。
Field
name: cmd_line_ptr
Type: write (obligatory)
Offset/size:
0x228/4
Protocol:
2.02+
将此域设定为内核命令行的线性地址。
内核命令行可以被放置在安装程序堆区的末尾和0xA0000之间的任意位置;它不必设在与
实模式代码相同的64K段中。
即便你的启动加载器不支持命令行,也可填写,这时你可以将此地址指向一个空串。如果
此域被置0,内核会假定你的启动装载器不支持2.02以上协议。
Field
name: initrd_addr_max
Type: read
Offset/size: 0x22c/4
Protocol:
2.03+
你的初始化ramdisk/ramfs内容可用的最高地址。2.02和更老的启动协议并没有定义此域,
且他的最大地址是0x37FFFFFF。(这个地址被定义为最高的安全地址,所以,如果你的
ramdisk正好是131072字节大小,而且此域是0x37FFFFFF,你就可以从0x37FE0000启动你
的ramdisk)。
Field
name: kernel_alignment
Type: read (reloc)
Offset/size:
0x230/4
Protocol: 2.05+
内核所需要的对齐位(当relocatable_kernel
= true时可用)。
Field
name: relocatable_kernel
Type: read (reloc)
Offset/size:
0x234/1
Protocol:
2.05+
如果此项非0,则保护模式部分的内核可以被加载到满足kernel_alignment的任意位置。
在加载完成后,启动装载器会将code32_start设定为指向加载的代码位置,或者指向启动
加载器的hook。
Field
name: cmdline_size
Type: read
Offset/size: 0x238/4
Protocol:
2.06+
除去终止符0后的命令行最大长度。意思是,命令行可以包含最大cmdline_size个字符数。
在2.05或更早启动协议版本里,这个最大值是255。
**** 内核命令行
如今内核命令行已经成为了启动加载器和内核通信的重要手段。一些命令行选项同样与启动
装载器自身有关,见如下“特殊的命令行选项”。
内核命令行是一个以空值结尾的字符串。他的最大长度被保存到cmdline_size域。在2.06版
协议以前,最大长度是255个字符。太长的字符串会被内核自动截断。
如果启动协议是2.02或者更新版本,内核命令行的地址将由头部的cmd_line_ptr域给定(见
下文)。这个地址可以是安装程序的堆区与0xA0000之间任意地址。
如果启动协议版本低于2.02,就可使用以下的协议来读取内核命令行:
在偏移0x0020(字)位置,“cmd_line_magic”,填入魔术数字 0xA33F
在偏移0x0022(字)位置,“cmd_line_offset”,填入内核命令行(相对于实模式
部分的内核)的偏移地址。
内核命令行必须包含在setup_move_size覆盖的内存范围内,所以你可能需要修改
这个域。
**** 实模式代码的内存布局
实模式代码需要建立一个堆区/栈区,也要为内核命令行分配相应内存。这些都需要在实模
式代码可达的低1M空间内完成。
要注意:现在的机器都拥有一个相当大的拓展BIOS数据区(EBDA)。因此建议尽量使用低1M空
间中尽可能小的空间。
不幸的是,在下面的情况下就必须使用0x90000内存段:
- 装载一个zImage内核 ((loadflags
& 0x01) == 0)
- 装载一个使用2.01或更早版本协议的内核。
-> 在2.00和2.01版启动协议中,实模式代码会被加载到另一个地址,但也会被
再次重新分配到0x90000。对于“旧版”内核,实模式代码必须被加载到
0x90000。
当加载到0x90000时,避免使用高于0x9a000的内存。
对于2.02或者更高版本启动协议,命令行不必和实模式下安装程序代码放在同一个64K段中;
因此允许堆区/栈区拥有完整的64K段,且可以在其中存放命令行。
内核命令行既不该被存放在实模式代码以下空间,也不应该放置在高端内存中。
**** 启动设置示例
因为是一个设置示例,我们可以假定存在以下实模式内存段的布局:
当装载到低于0x90000的地址时,使用整个段:
0x0000-0x7fff 实模式部分的内核
0x8000-0xdfff 堆区/栈区
0xe000-0xffff 内核命令行
当装载到0x90000处,或者你使用了2.01或者更早版本的协议:
0x0000-0x7fff 实模式部分的内核
0x8000-0x97ff 堆区/栈区
0x9800-0x9fff 内核命令行
这样一个启动装载器应该如此填写头部中的以下域:
C 代码
unsigned long base_ptr; /* base address for real-mode segment */
if ( setup_sects == 0 ) {
setup_sects = 4;
}
if ( protocol >= 0x0200 ) {
type_of_loader = [type code]; /* constant */
if ( loading_initrd ) {
ramdisk_image
=
ramdisk_size
=
}
if ( protocol >= 0x0202 && loadflags & 0x01 )
heap_end = 0xe000;
else
heap_end = 0x9800;
if ( protocol >= 0x0201 ) {
heap_end_ptr = heap_end - 0x200;
loadflags |= 0x80; /* CAN_USE_HEAP */
}
if ( protocol >= 0x0202 ) {
cmd_line_ptr = base_ptr + heap_end;
strcpy(cmd_line_ptr, cmdline);
} else {
cmd_line_magic = 0xA33F;
cmd_line_offset = heap_end;
setup_move_size = heap_end + strlen(cmdline)+1;
strcpy(base_ptr+cmd_line_offset, cmdline);
}
} else {
/* Very old kernel */
heap_end = 0x9800;
cmd_line_magic = 0xA33F;
cmd_line_offset = heap_end;
/* A very old kernel MUST have its real-mode code
loaded at 0x90000 */
if ( base_ptr != 0x90000 ) {
/* Copy the real-mode kernel */
memcpy(0x90000, base_ptr, (setup_sects+1)*512);
base_ptr = 0x90000; /* Relocated */
}
strcpy(0x90000+cmd_line_offset, cmdline);
/* It is recommended to clear memory up to the 32K mark */
memset(0x90000 + (setup_sects+1)*512, 0,
(64-(setup_sects+1))*512);
}
**** 装载内核的剩余部分
32位内核代码从内核文件偏移(setup_sects+1)*512的位置开始(同样,如果setup_sects等
于0,其真实值就是4)。
如果是Image/zImage内核,此段代码应该加载到0x10000,而bzImage内核则应该加载到
0x100000位置。
如果使用2.00或更高协议,而且已经设置loadflags的0x01位(LOAD_HIGH),那么内核就被
认作bzImage内核。
C 代码
is_bzImage = (protocol >= 0x0200) && (loadflags & 0x01);
load_address = is_bzImage ? 0x100000 : 0x10000;
注意Image/zImage可能在达到512K大小,这样就会使用到整个0x10000-0x90000内存范围。
这意味着,迫切需要这些内核将实模式部分装载到0x90000。bzImage内核则提供了更多的灵
活性。
**** 特殊的命令行选项
如果用户键入了启动装载器支持的命令行参数,那么用户可能期望以下命令行选项可以工作。
即便并非所有选项对内核都有意义,他们也不该直接被删除。启动装载器的作者如果需要给
装载器自身提供另外的命令行参数,就应该在Documentation/kernel-parameters.txt注册
这些选项,来确保他们不会和当前的或者即将使用的内核选项相冲突。
vga=
这里的
制其中之一),就是“normal”(0xFFFF),“ext”(0xFFFE),“ask”(0xFFFD)中
的一个。这个值应被填入vid_mode域,因为他会在命令行被解析前被内核使用。
mem=
T,P或者E(代表<<
10, << 20, << 30, << 40, << 50或者 <<
60)。这就指明了
内核文件在内存中的末尾。它影响了initrd可能存放的位置,因为initrd应该被放
置在内核末尾的附近。要注意:这个选项同时作用于内核和启动装载器!
initrd=
指定装载的initrd,
器(比如LILO)甚至不需要这个选项。
另外,有些启动装载器在额定的命令行上添加了以下参数:
BOOT_IMAGE=
要加载的启动镜像。同样,
auto
内核不需要用户外界干预自行启动。
如果这些参数被启动装载器添加,强烈建议将它们放置在用户指定的或者设置项指定的命令
行之前。否则,“init=/bin/sh”跟上auto会让人产生歧义。
**** 启动内核
跳转到内核入口地址,内核就开始运行了。其入口处于相对于实模式内核部分的段地址偏移
0x20的位置。这意思是:如果你将实模式内核加载到0x90000,内核入口就应该是9020:0000。
在入口处,我们将ds,es,ss一起指向实模式内核代码的起始位置(如果代码加载到
0x90000,那这个位置应该是0x9000),sp寄存器应该指向堆区的顶端,而且还要禁用中断。
此外,为了防止内核出现bug,建议启动装载器作出设置: fs
= gs = ds = es = ss。
对于以上的举例,我们这样来实现:
C 代码
/* Note: in the case of the "old" kernel protocol, base_ptr must
be == 0x90000 at this point; see the previous sample code */
seg = base_ptr >> 4;
cli(); /* Enter with interrupts disabled! */
/* Set up the real-mode kernel stack */
_SS = seg;
_SP = heap_end;
_DS = _ES = _FS = _GS = seg;
jmp_far(seg+0x20, 0); /* Run the kernel */
如果启动扇区访问的是一个软驱,建议在内核运行前关闭马达。因为内核启动会保持关闭中
断状态,这样就无法关闭驱动器马达,尤其当装载的内核需要加载软驱的时候(* 此时的软
件驱动器状态未知,无法使用)。
**** 高级的启动装载器hook
如果启动装载器在一个不利的环境下运行(比如在DOS下运行的LOADIN),就有可能不去遵
从标准的内存映射,这样的启动装载器可能使用到了如下的hook。如果是启用了hook,它
会在适当的时间被内核触发。hook应该被认作绝对最后的手段来使用!
醒目:所有的hook在启用时都需要保存%esp,%ebp,%esi和%edi。
realmode_swtch:
在即将进入保护模式之前出发的一个16位实模式段外子程序。默认下这样的程序应
该禁用NMI,所以你的程序也应如此。
code32_start:
在刚刚跃迁到保护模式的时候要跳转到的32位平面模式(flat-mode)例程,但是
此程序应在内核解压缩之前执行。除了CS外其他段地址都不能被确认保存(现在的
内核保存,而老版本没有);你应该自己将他们设置为BOOT_DS。
在完成hook之后,应该在你的启动装载器覆盖hook之前跳转到此域中(* 指
code32_start)(如果条件允许,就执行重定位)。