论坛首页 编程语言技术论坛

Linux 启动协议 v20070523

浏览 5770 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-09-20  
C

Linux 启动协议 v20070523



作者:H. Peter Anvin [hpa@zytor.com]
译者:Wick [wickmaycry@gmail.com]

- *为译者注
- 尊重他人劳动,分享社会成果。转载请保留文章出处和文中所有链接。
----------
本文章原作者H. Peter Anvin是瑞典人,作为SYSLINUX的作者,还负责了klibc和NASM等自
由软件项目。
文章对存在的启动协议进行简要概括,对Linux内核分析的起步有很大的帮助,读者也可以
借此理解到纷繁变换的协议给内核代码带来的稍许变化。
由于本人水平有限,如若读者对文中词汇翻译有任何意见或者不同见解,敬请指正。
----------

在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+

跳转到保护模式的地址。默认下,这应该是内核所加载的位置,且此项被启动加载器来判
断合适的加载地址。

此域可被用作以下用途:
  1. 用作启动装载器的hook
  2. 如果一个启动装载器没有安装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 代码
  1. unsigned long base_ptr; /* base address for real-mode segment */
  2. if ( setup_sects == 0 ) {
  3. setup_sects = 4;
  4. }
  5. if ( protocol >= 0x0200 ) {
  6. type_of_loader = [type code]; /* constant */
  7. if ( loading_initrd ) {
  8. ramdisk_image = <initrd_address>[intrd_address]; /* constant */ </initrd_address>
  9. ramdisk_size = <initrd_size>[intrd_size]; /* constant */ </initrd_size>
  10. }
  11. if ( protocol >= 0x0202 && loadflags & 0x01 )
  12. heap_end = 0xe000;
  13. else
  14. heap_end = 0x9800;
  15. if ( protocol >= 0x0201 ) {
  16. heap_end_ptr = heap_end - 0x200;
  17. loadflags |= 0x80; /* CAN_USE_HEAP */
  18. }
  19. if ( protocol >= 0x0202 ) {
  20. cmd_line_ptr = base_ptr + heap_end;
  21. strcpy(cmd_line_ptr, cmdline);
  22. } else {
  23. cmd_line_magic = 0xA33F;
  24. cmd_line_offset = heap_end;
  25. setup_move_size = heap_end + strlen(cmdline)+1;
  26. strcpy(base_ptr+cmd_line_offset, cmdline);
  27. }
  28. } else {
  29. /* Very old kernel */
  30. heap_end = 0x9800;
  31. cmd_line_magic = 0xA33F;
  32. cmd_line_offset = heap_end;
  33. /* A very old kernel MUST have its real-mode code
  34. loaded at 0x90000 */
  35. if ( base_ptr != 0x90000 ) {
  36. /* Copy the real-mode kernel */
  37. memcpy(0x90000, base_ptr, (setup_sects+1)*512);
  38. base_ptr = 0x90000; /* Relocated */
  39. }
  40. strcpy(0x90000+cmd_line_offset, cmdline);
  41. /* It is recommended to clear memory up to the 32K mark */
  42. memset(0x90000 + (setup_sects+1)*512, 0,
  43. (64-(setup_sects+1))*512);
  44. }


**** 装载内核的剩余部分


32位内核代码从内核文件偏移(setup_sects+1)*512的位置开始(同样,如果setup_sects等
于0,其真实值就是4)。
如果是Image/zImage内核,此段代码应该加载到0x10000,而bzImage内核则应该加载到
0x100000位置。

如果使用2.00或更高协议,而且已经设置loadflags的0x01位(LOAD_HIGH),那么内核就被
认作bzImage内核。

C 代码
  1. is_bzImage = (protocol >= 0x0200) && (loadflags & 0x01);
  2. load_address = is_bzImage ? 0x100000 : 0x10000;


注意Image/zImage可能在达到512K大小,这样就会使用到整个0x10000-0x90000内存范围。
这意味着,迫切需要这些内核将实模式部分装载到0x90000。bzImage内核则提供了更多的灵
活性。


**** 特殊的命令行选项


如果用户键入了启动装载器支持的命令行参数,那么用户可能期望以下命令行选项可以工作。
即便并非所有选项对内核都有意义,他们也不该直接被删除。启动装载器的作者如果需要给
装载器自身提供另外的命令行参数,就应该在Documentation/kernel-parameters.txt注册
这些选项,来确保他们不会和当前的或者即将使用的内核选项相冲突。

vga=<mode></mode>
这里的<mode>不是一个整数(在C语言表示法中,应是十进制,八进制或者十六进
制其中之一),就是“normal”(0xFFFF),“ext”(0xFFFE),“ask”(0xFFFD)中
的一个。这个值应被填入vid_mode域,因为他会在命令行被解析前被内核使用。

mem=<size></size>
<size>是用C语言表示法定义的整形,后面可以追加(大小写不敏感的)K,M,G,
T,P或者E(代表<< 10, << 20, << 30, << 40, << 50或者 << 60)。这就指明了
内核文件在内存中的末尾。它影响了initrd可能存放的位置,因为initrd应该被放
置在内核末尾的附近。要注意:这个选项同时作用于内核和启动装载器!

initrd=<file></file>
指定装载的initrd,<file>显然是和启动装载器是独立的文件,而且一些启动装载
器(比如LILO)甚至不需要这个选项。

另外,有些启动装载器在额定的命令行上添加了以下参数:

BOOT_IMAGE=<file></file>
要加载的启动镜像。同样,<file>也是和bootloader独立的。

auto
内核不需要用户外界干预自行启动。

如果这些参数被启动装载器添加,强烈建议将它们放置在用户指定的或者设置项指定的命令
行之前。否则,“init=/bin/sh”跟上auto会让人产生歧义。

</file></file></size></mode>

**** 启动内核


跳转到内核入口地址,内核就开始运行了。其入口处于相对于实模式内核部分的段地址偏移
0x20的位置。这意思是:如果你将实模式内核加载到0x90000,内核入口就应该是9020:0000。

在入口处,我们将ds,es,ss一起指向实模式内核代码的起始位置(如果代码加载到
0x90000,那这个位置应该是0x9000),sp寄存器应该指向堆区的顶端,而且还要禁用中断。
此外,为了防止内核出现bug,建议启动装载器作出设置: fs = gs = ds = es = ss。

对于以上的举例,我们这样来实现:

C 代码
  1. /* Note: in the case of the "old" kernel protocol, base_ptr must
  2. be == 0x90000 at this point; see the previous sample code */
  3. seg = base_ptr >> 4;
  4. cli(); /* Enter with interrupts disabled! */
  5. /* Set up the real-mode kernel stack */
  6. _SS = seg;
  7. _SP = heap_end;
  8. _DS = _ES = _FS = _GS = seg;
  9. 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)(如果条件允许,就执行重定位)。
   发表时间:2007-09-21  
现在都干起翻译来了啊 支持!
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics