转]系统启动——Grub篇(一)

GRUB整体分析
总体上我们可以把GRUB看成一个微型的操作系统,他有Shell,支持Script,有文件系统……我们可以把Stage1和Stage1.5看成一个 引导程序,而Stage2则是一个操作系统,只不过这个操作系统是专门用来引导其他操作系统的操作系统,为此,Stage2支持像kernel, initrd,chainloader等等为此目的而设置的内部“命令”。
3.1  GRUB引导操作系统的两种方式
3.1.1 直接引导方式
GRUB同时支持 Linux, FreeBSD, NetBSD 和OpenBSD。如果想要启动其他的操作系统,你必须使用链式启动方式来启动他们[6]。

通常,GRUB直接引导操作系统的步骤如下:

(1) 通过'root'指令来设置GRUB的主设备指向操作系统映像文件所存储的地方。

(2) 通过'kernel'命令来载入该操作系统的核心映像。

(3) 如果需要模块,通过'module'命令来加载模块。

(4) 运行命令'boot'。

Linux, FreeBSD, NetBSD 和 OpenBSD使用相同的方式启动。你可以通过'kernel'命令来装载核心映像,然后运行'boot'命令。如果核心需要一些参数的话,只要在'kernal'命令以后追加就可以了。
3.1.2 链式引导方式
如果你要启动一个不被GRUB直接支持的操作系统(例如: Windows 95),可以通过链式引导启动一个操作系统。通常来说,那个引导程序和所要启动的操作系统是安装在一个分区中的。

主要步骤如下:

(1) 通过'rootnoverify'命令设置GRUB的主设备指向一个扇区。

grub> rootnoverify (hd0,0)

(2) 通过'makeactive'命令来设置在扇区上的'active’标志位。

grub> makeactive

(3) 通过'chainloader'命令来加载引导程序。

grub> chainloader +1

'+1'表明GRUB需要从起始分区读一个扇区。

(4) 运行命令'boot'。


3.2  GRUB引导操作系统的简要流程分析
3.2.1 从计算机启动到GRUB启动操作系统
(1) BIOS执行INT 0x19,加载MBR至0x7c00并跳转执行。如果你安装GRUB到MBR,GRUB的安装程序会把Stage1(512B)拷贝到MBR。视stage2的大小,安装程序会在Stage1中嵌入Stage1_5或者Stage2的磁盘位置信息。
(2) Stage1开始执行,它在进行直接加载Stage1_5或者Stage2并跳转执行。不论是哪种情况,这一步的结果都是Stage2开始运行了。
(3) Stage2这个小型的操作系统终于开始正式运行了!它会把系统切入保护模式,设置好C运行环境(主要是BSS)。他会先找Config文件(就是我们的 Menulist),如果没有的话就执行一个Shell,等待我们输入命令。然后Grub的工作就是输入命令-解析命令-执行命令的循环,当然 Stage2本身是为加载其他操作系统而存在的,所以如果情况允许,在他执行Boot命令以后就会把控制权转交出去。
3.2.2  GRUB的主要启动模块
GRUB 包含如下几个启动模块:两个必须的场景文件,一个叫"Stage 1.5"的可选的场景文件以及2个网络启动的映像文件。首先对他们有一个大致的了解。

Stage1
这是一个基本必须的用来启动GRUB的映像文件。通常,这个文件是被装载到MBR或者启动扇区所在的分区。由于PC的启动扇区的大小为512字节,所以这个映像文件编译以后也必须为512字节。

Stage1的全部的工作是从本地磁盘把Stage 2或者Stage 1.5装载进来。由于对stage1大小的限制,它通过分程序表的形式来编码Stage 2或者Stage 1.5的位置,所以在stage1是不能识别任何文件系统的。

Stage2
这是GRUB的核心映像。它几乎做了除启动它本身以外的所有事情。通常,它被存放为某一种文件系统下,但并非是必须的。

e2fs_stage1_5
fat_stage1_5
ffs_stage1_5
jfs_stage1_5
minix_stage1_5
reiserfs_stage1_5
vstafs_stage1_5
xfs_stage1_5
这些文件被称为stage 1.5,它存在的目的是做为stage1与stage2之间的桥梁,也就是说,stage1载入stage1.5,然后stage1.5载入stage2。
stage1与stage1.5之间的区别是,前者是不识别任何文件系统的但后者识别文件系统(例如 'e2fs_stage1_5' 识别 ext2fs)。所以你可以安全的移动stage2的位置,即使是在GRUB安装完以后。

nbgrub
这是一个网络启动的映像文件,被类似于以太网启动装载器所使用。它很类似于stage2,但它还要建立网络,然后通过网络来载入配置文件[7]。

pxegrub
这是另一个网络启动的映像文件。
除了格式以外,它和'nbgrub'是一致的。







4 STAGE1模块分析
Stage1模块是整个引导程序的引导模块,是从开机过渡到GRUB的第一个模块。Stage1的代码文件,是源码目录下Stage1/Stage1.S,汇编后便成了一个512字节的Img,被写在硬盘的0面0道第1扇区,作为硬盘的主引导扇区。
4.1 Stage1.h文件分析
在此文件中主要是定义了一些在Stage1.S文件中使用到的一些常量。
关于这些常量的分析如下:
/* 定义了grub的版本号,在stage1中可以识别他们.*/
#define COMPAT_VERSION_MAJOR 3
#define COMPAT_VERSION_MINOR 2
#define COMPAT_VERSION ((COMPAT_VERSION_MINOR << 8) /
| COMPAT_VERSION_MAJOR)

/* MBR最后两个字节的标志*/
#define STAGE1_SIGNATURE 0xaa55

/* BPB (BIOS 参数块BIOS Parameter Block)的结束标记的偏移,他含有对驱动器的低级参数的说明. */
#define STAGE1_BPBEND 0x3e

/* 主版本号的标记的偏移*/
#define STAGE1_VER_MAJ_OFFS 0x3e

/* Stage1启动驱动器的标记的偏移*/
#define STAGE1_BOOT_DRIVE 0x40

/* 强迫使用LBA方式的标记的偏移*/
#define STAGE1_FORCE_LBA 0x41

/* Stage2地址标记的偏移*/
#define STAGE1_STAGE2_ADDRESS 0x42

/* STAGE2扇区的标记的偏移*/
#define STAGE1_STAGE2_SECTOR 0x44

/* STAGE2_段的标记的偏移*/
#define STAGE1_STAGE2_SEGMENT 0x48

/* 使用Windows NT的魔术头标识的偏移*/
#define STAGE1_WINDOWS_NT_MAGIC 0x1b8

/* 分区表起始地址的标记的偏移*/
#define STAGE1_PARTSTART 0x1be

/* 分区表结束地址的标记的偏移*/
#define STAGE1_PARTEND 0x1fe

/* Stage1堆栈段的起始地址*/
#define STAGE1_STACKSEG 0x2000

/* 磁盘缓冲段。磁盘缓冲必须是32K长而且不能跨越64K的边界。*/
#define STAGE1_BUFFERSEG 0x7000

/* 驱动器参数的地址*/
#define STAGE1_DRP_ADDR 0x7f00

/* 驱动器参数的大小*/
#define STAGE1_DRP_SIZE 0x42

/*在BOIS中软盘的驱动器号标志*/
#define STAGE1_BIOS_HD_FLAG 0x80

4.2 Stage1.s文件分析
首先在这个文件的开始部分定义了一些宏。

#define ABS(x) (x-_start+0x7c00)
这个宏计算了直接地址。由于MBR是被加载到0x7c00的位置,所以通过计算可以直接得到x参数的直接地址。这样就可以不依赖于Linker程序。

#define MSG(x) movw $ABS(x), %si;
这个宏用于处理对字符串的载入和响应。

然后程序从_start程序入口开始执行,此入口在内存中的位置为CS:IP 0:0x7c00。随后对一系列的变量进行了初始化。设置了起始的扇区、磁道和柱面,并设置了他们的起始位置。同时还设置了stage1的版本号。通过设 置boot_drive变量,来设置从那个盘来载入stage2。如果此变量设置成0xff则从默认的启动驱动器中来载入stage2。然后指定了 stage2的起始地址是0x8000,起始段是0x800,起始的扇区号是1。也就是说stage2起始位置是被存放在0柱面,0磁道,第2扇区上的 [8]。
程序从real_start入口开始真正执行。首先设置了数据段以及堆栈段的偏移为0,然后设置stage1的堆栈的起始地址为 STAGE1_STACKSEG即0x2000。随后打开中断。然后检查是否设置了启动的磁盘。即boot_drive变量是否为0xff,如果非 0xff则保存设置的磁盘号到dl寄存器中,并压入堆栈保存。同时在屏幕上显示GRUB字样。然后检查此启动磁盘是否是软盘,如果是软盘则直接跳转到 CHS模式不用检测是否支持LBA模式。然后检测所启动的磁盘是否支持LBA模式。接着程序分成两块,一块是LBA模式,一块是CHS模式。
LBA英文全名为Logical Block Addressing,中文名称为逻辑区块寻址[9]。LBA所指的是一种磁盘设备的寻址技术,它是利用逻辑映对的方式来指定磁盘驱动器的扇区,目前个人 计算机所使用的传输接口中,增强型 IDE (Enhanced IDE) 和 SCSI 均使用逻辑区块寻址方式。传统的硬盘寻址技术是采取实体寻址(physical mapping、physical addressing)的方式,以磁盘上的实际结构,直接作为资料区块地址的结构。但由于初期在设计实体寻址方式时,硬盘容量只有5、10、20 MB等等小容量机种,所以设计出来的最大的寻址能力,只能到1024个磁柱(cylinder)、16个磁头(head)、63个扇区(sector)。 以每个扇区(sector)512字节(bytes)计算,实体寻址的方式最多只能使用512×63×1024×16=528482304字节 (528MB)的硬盘空间。但是由于磁性储存技术不断的提升,硬盘容量大幅增加的情况之下,这样的限制让使用者必须将硬盘画分为多个区块,使用上非常的不 方便。
因此硬件厂商研究出了LBA逻辑寻址方式,也就是计算机系统并没有将资料存放地点的相关记录,应对到硬盘上资料实际存放的位置。而是由IDE控制电路和 BIOS负责转换寻址(mapping)资料的记录位置表。经过转换后的记录方式,是将第1个磁柱上的第1条磁道的第1个扇区编号为0,第二个扇区编号为 1,以此类推……,假设1条磁道有2000个扇区,那么第2000个扇区的编号就是1999。第2条磁道上的第1个扇区就是2000,如此一直线性排列下 去。以逻辑区块的方式来寻址的硬盘,最多可达16383磁柱,最大磁头数为16个,每轨扇区有63区,扇区大小为512字节,所支持之硬盘空间为512× 63×16383×16=8455200768字节(8.4GB)[10]。
在ATA的接口规格中,定义了使用28位来寻址,因此计算出来,它可以支持到224×512=137GB的容量。不过不幸的,BIOS并无法配合,它使用 24位来寻址(也就是LBA模式)。所以根本之道,就是改变BIOS对中断13h的支持,因此后来的BIOS就设计了加强版的中断13h。一口气使用了 64位来对硬盘做寻址,因此可以支持到264×512=9.4TB,相当于3万亿倍的8.4GB[11]。
如果是LBA模式下读取,首先对先前定义的磁盘的一些参数进行了定义,为以后调用INT 13做准备。使用INT13的0x42功能,把磁盘内容读到内存中。设置ah为0x42为功能号,设置dl寄存器来设置磁盘号,si为记录磁盘一系列信息 的地址偏移量,磁盘信息中包括了要读入的柱面号、磁道号以及扇区号。程序然后调用BOIS INT 13中断将启动磁盘上的第二扇区上的内容读到内存中的STAGE1_BUFFERSEG处,在Stage1.h中定义STAGE1_BUFFERSEG为 0x7000。即将第二扇区上的内容读到内存中的0x7000处。读入成功的话跳转到COPY_BUFFER处,如果读取失败则尝试使用CHS模式读入。
与LBA模式不同的是,调用BIOS INT 0x13中断中的0x2号功能,设置ah寄存器为0x2,al为扇区数,cl的位6,位7和ch组合为磁道号,cl的0-5位为扇区号,dh为磁头号, dl为驱动器号(其中0x80为硬盘,0x0为软驱)。es:bx为数据缓冲区的地址。但所起的功能与前面提到的LBA模式是类似的,也是将第二扇区中的 内容读到内存中的0x7000处,作为缓存。然后跳转到COPY_BUFFER处。
最后调用COPY_BUFFER将刚刚读入的扇区转移到stage2_address。即转移到0x8000处。
4.3 Stage1模块功能综述
由于对Stage1文件容量的限制,所以Stgae1所做的工作相对来说比较有限。它首先被BOIS装载到内存中的0x7c00处,然后通过调用BOIS INT13中断,把第启动驱动器中第二扇区上的内容读到内存中的0x7000处,然后通过调用COPY_BUFFER将其转移到了内存中0x8000的位置上。这个被读入的第二扇区上的内容,就是下面将要分析的Start.s功能模块。




5  START模块分析
从上一章节的分析中我们看到,Stage1的是完成了一个MBR所需要完成的任务,但GRUB并没有直接就通过Stage1直接载入GRUB的内核,而是 通过Stage1载入了另一个模块到0x8000处。根据对源代码的分析,发现被载入的这个模块就是下面需要分析的第二个模块,即Start.S模块。

5.1 Start.s 模块功能分析
在程序的开始部分,仍然是对程序定义了一些宏。
#ifdef STAGE1_5
# define ABS(x) (x-_start+0x2000)
#else
# define ABS(x) (x-_start+0x8000)
#endif
可以发现,如果定义了STAGE1_5则程序的起始地址是0x2000,而如果没有定义STAGE1_5程序起始的地址正好是0x8000。所以我判断, 在Stage1后载入内存的程序部分就是Start.s所编译以后的512字节的映象文件。关于STAGE1_5的部分暂时先不进行分析,这里暂且跳过。

宏 “#define MSG(x) movw $ABS(x), %si;”的作用是在屏幕上显示字符串。
接着就是程序的入口_start。由于是紧接着Stage1被载入内存中的,所以它的起始地址就是0x8000,并且它仍然将使用Stage1模块留下来 的寄存器以及变量等信息。如果设置STAGE1_5变量则在屏幕上显示“Loading stage1.5”,如果没有设置这个变量则显示“Loading stage2”。然后读入需要读入的扇区的数目。接着进入一个bootloop的循环,如果需要读入的扇区不为0,则继续循环,直到当需要读入的扇区数目 为0时,循环结束。在这个循环中,使用与Stage1中的方法相同,判断了驱动器磁盘所支持的读写模式,根据不同的磁盘所支持的不同模式,跳转到相应的部 分去读取磁盘上的扇区到内存中去。如果磁盘支持的是LBA模式则跳转到lba_mode部分读取相应的扇区,如果磁盘不支持LBA模式,则跳转到 chs_mode部分,通过CHS模式来读入把磁盘中的扇区读入到内存中。首先是把读到的扇区读到内存中的0x7000处缓存起来,然后通过调用 copy_buffer子程序,把缓存中的内容复制到目标地址,即0x8200开始的地方。与Stage1中的一样,在Start.s中也有一个记录地址 的数据结构,不同的是在Stage1中只有一项,而Start.S记录的是一个地址的链表,称为Blocklist,该链表的结点都记录了一个连续 sectors的集合。

lastlist:
.word 0
.word 0
. = _start + 0x200 - BOOTSEC_LISTSIZE
/*加0x200是由于Start.s编译完以后也是一个512字节的映象文件。*/

/* 初始化了第一个数据列表*/
blocklist_default_start:
.long 2 /* 记录了从第3个扇区开始*/
blocklist_default_len:
/* 这个参数记录了需要读取多少个扇区 */
#ifdef STAGE1_5
.word 0 /* 如果设置了STAGE1_5标志,则不读入*/
#else
.word (STAGE2_SIZE + 511) >> 9 /*读入Stage2所占的所有扇区*/
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220 /*如果设置STAGE1_5则从0x220开始读入*/
#else
.word 0x820 /*如果没有设置STAGE1_5则从0x820开始读入*/
#endif

firstlist:

当把所有需要读入的扇区都读入以后,程序进入bootit子程序块。然后程序进行跳转,如果设置了STAGE1_5标志,则跳转到0x2200执行,如果没有设置STAGE1_5标志,则跳转到0x8200处继续执行。


5.2 Start 模块功能综述
通过对Start.s文件的分析,我们可以看到。Start模块主要是做了一件事情,就是把Stage2或者Stage1_5模块从磁盘装载到内存中。如 果是直接装载Stage2的话,是装载在内存的0x8200处,如果装载Stage1_5的话,是装载在内存的0x2200处。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值