Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1833803
  • 博文数量: 286
  • 博客积分: 3713
  • 博客等级: 少校
  • 技术积分: 2275
  • 用 户 组: 普通用户
  • 注册时间: 2012-01-11 09:47
个人简介

http://blog.chinaunix.net/uid/16979052.html

文章分类

全部博文(286)

文章存档

2018年(1)

2017年(16)

2016年(9)

2015年(17)

2014年(15)

2013年(112)

2012年(116)

分类: LINUX

2013-03-24 08:10:44

i386平台下,Linux内核使用了一个相当复杂的启动协议。这要部分归咎于历史原因,因为早期的内核需要被做成一个可启动镜像,其他原因还包括,复杂的计算机内存模型,由于实模式DOS作为主流操作系统而改变了计算机工业的预期发展等等。

当前共有以下Linux/i386启动协议存在着:

旧版内核:

仅仅支持zImage/Image。一些早期的内核甚至不支持命令行。

2.00版协议:

(内核版本 1.3.73)加入了bzImageinitrd的支持,也拥有了一种正规化的方法来实现启动装载器(* boot loader)和内核间的通信。setup.S建造了一块可移动,但是仍旧可写的传统的安装程序加载区域。

2.01版协议:

(内核版本 1.3.76)加入一系列大量的溢出警告。

2.02版协议:

(内核版本2.4.0-test3-pre3)这是新的命令行协议。它降低了常规的内存使用上限(*见以下MEMORY LAYOUT内存布局介绍)。没有覆盖传统的安装程序区域,因此对于那些使用了来自SMM或者32BIOS入口地址的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_kernelkernel_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.002.01版启动协议中,0x90000以上的内存区域仍旧被内核所使用;2.02版启动协议就解决了这个问题。

人们总想让内存上限——启动装载器可触的低端内存的最高点——尽可能的低,越低越好,这是因为一些新的BIOS已经开始分配相当大的内存区域,这片区域被称为拓展BIOS数据区(EBDA),位于低端内存的顶端。启动装载器会使用BIOS0x21号中断来判断有多少低端内存可以使用。

不幸的是,如果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.01version”域的值为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或者ramfs32位线性地址。如果没有初始化的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.002.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 = [intrd_address]; /* constant */

  9. ramdisk_size = [intrd_size]; /* constant */

  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或更高协议,而且已经设置loadflags0x01位(LOAD_HIGH),那么内核就被
认作bzImage内核。

C 代码

  1. is_bzImage = (protocol >= 0x0200) && (loadflags & 0x01);

  2. load_address = is_bzImage ? 0x100000 : 0x10000;



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

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


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

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

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

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

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

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

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

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

**** 启动内核


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

在入口处,我们将dsesss一起指向实模式内核代码的起始位置(如果代码加载到
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
)(如果条件允许,就执行重定位)。

阅读(2142) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~