GRUB2及启动过程详解

更新时间:2024-06-02 04:57:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

GRUB2及启动过程详解

作者: 南瓜剪子 2016年7月28日

1. 概述 ............................................................................................................................................ 2 2. CentOS7中GRUB2 ..................................................................................................................... 2 3. GRUB2 的 bootstrap image文件 .............................................................................................. 3

3.1. boot.img ............................................................................................................................ 3 3.2. diskboot.img ...................................................................................................................... 3 3.3. kernel.img .......................................................................................................................... 4 3.4. core.img ............................................................................................................................. 4 3.5. *.mod ................................................................................................................................. 4 4. 对比GRUB Legacy ...................................................................................................................... 5

4.1. stage1 ................................................................................................................................ 5 4.2. *_stage1_5 ........................................................................................................................ 5 4.3. stage2 ................................................................................................................................ 5 5. grub2-mkimage定制core.img ................................................................................................... 6 6. GRUB2安装 ................................................................................................................................ 7

6.1. Linux是怎么命名设备(/dev/sda) ..................................................................................... 7 6.2. MBR(Master Boot Record) ................................................................................................. 8 6.3. MBR分区表DPT(Disk Partition Table) ............................................................................. 9 6.4. LBA and CHS..................................................................................................................... 14

6.4.1. CHS(cylinders-heads-sectors) ................................................................................ 14 6.4.2. LBA(Logical Block Address) .................................................................................... 14 6.5. boot.img和core.img安装 ............................................................................................. 14 6.6. 硬盘总结 ........................................................................................................................ 17 7. 启动过程................................................................................................................................... 19

7.1. BIOS ................................................................................................................................. 19 7.2. GRUB2中boot.img ......................................................................................................... 20

7.2.1. Step1无条件跳转 ................................................................................................ 20 7.2.2. Step2 初始化 ....................................................................................................... 20 7.2.3. Step3 判断硬盘是否支持LBA还是只支持CHS ................................................ 22 7.2.4. Step4 采用LBA加载core.img第一个扇区 ....................................................... 23 7.2.5. Step5 拷贝core.img第一个扇区到内存指定位置 ............................................ 25 7.2.6. Step6 执行core.img第一条语句 ....................................................................... 26 7.2.7. Step7 boot.img总结 ............................................................................................. 26 7.3. GRUB2中core.img.......................................................................................................... 28

7.3.1. diskboot.img .......................................................................................................... 28 7.3.2. GRUB2 core Startup .............................................................................................. 34 7.3.3. GRUB主函数即GRUB主要功能 ......................................................................... 37 7.3.4. 加载Linux Kernel ................................................................................................. 38

7.4. Linux kernel...................................................................................................................... 46 8. 结束语....................................................................................................................................... 47 9. 参考文献................................................................................................................................... 47

1. 概述

计算机系统上电之后由固化到ROM中的BIOS(UEFI)进行操作,然后导入硬盘MBR中Boot Loader并调到其运行,由Boot Loader对操作系统内核进行加载,并将控制权交给操作系统.下图简单描述了CentOS7的启动过程(CentOS7使用GRUB2作为Boot Loader):

现在的Boot Loader有很多类型,大致如下: 1.对Linux来说GRUB2占据了统治地位

2.对Windows来说Bootmgr/BCD占据了统治地位 3.对Apple系统缺省的是BootX

作为一名计算机工程师,了解Boot Loader的原理是很重要。本文着重介绍GRUB2及其启动流程.读完本文,相信读者能够对Boot Loader原理有一定深刻理解,并能大致知道其他Boot Loader的作用.

本文在介绍一些基本知识后,在第7章会引入基本的反汇编过程来讲解GRUB2的启动流程,因为只有直接读代码才能深入学习原理.需要读者:

- 有一定的汇编语言知识,如果读者对汇编不敢兴趣,可以跳过相关章节 - 同时也需要读者有一定C语言知识

本文稍微有点长,但都是作者本人的经验总结,如果静下心读完,相信对你会有帮助,相比其它GRUB一厚本书,能节约你很多时间。但由于作者水平有限,有不足之处忘谅解。

2. CentOS7中GRUB2

GRUB2是GNU下面的一个项目,有关其详细信息,请参考官方网站: http://www.gnu.org/software/grub/. 相对于GRUB2, 原先的GRUB(i.e. version 0.9x)被称为: GRUB Legacy. 官网上说明: GRUB Legacy is no longer being developed.

从 1.x 开始的新版本就称为 GRUB 2(注意,虽然称为GRUB2,但其版本号却是从1.x开始 1.99也是GRUB2). 现在各种最新发行版本的Linux(包括CentOS7)都采用GRUB2.

在CentOS7上可以查看安装的GRUB2版本

[root@controller ~]# rpm -qa | grep \grub2-2.02-0.34.el7.centos.x86_64

在CentOS7下面/boot/grub2目录可以看到有关GRUB2相关文件. 在/boot/grub2/i386-pc目录下是GRUB2 bootstrap images文件,该目录大部分文件是以.mod结

尾,这些文件为GRUB2模块文件,最后在该目录下面有以下两个image文件:

[root@controller i386-pc]# ls -lrt *.img

-rw-r--r--. 1 root root 26618 7\月 11 23:33 core.img -rw-r--r--. 1 root root 512 7\月 11 23:33 boot.img

以上两个img文件都会被grub2-install命令安装到硬盘上相应位置(在硬盘什么位置后面会说),很有意思的是请注意两点

1) boot.img固定为512Byte 2) core.img小于32KByte

如果你觉得好奇,可以用file命令查看这些image文件类型

[root@controller i386-pc]# file boot.img

boot.img: x86 boot sector; partition 4: ID=0xd4, starthead 205, startsector 4277266767, 0 sectors, code offset 0x63

[root@controller i386-pc]# file core.img core.img: data

[root@controller i386-pc]# file xfs.mod

xfs.mod: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

对于boot.img file命令明确指出其为x86 boot sector,而mod文件却是ELF文件,接下来让我们进一步了解这些bootstrap image文件

3. GRUB2 的 bootstrap image文件

GRUB2有以下各种bootstrap images文件,大伙应悉知,这些image会被grub2-install安装到硬盘相应的位置,当BIOS完成后,他们会被加载并引导系统继续完成启动.

3.1. boot.img

在PC BIOS系统中,这个image是GRUB2第一个被运行的.它被写在MBR(Master Boot Record)或者在分区(partition)的boot sector中.因为MBR或PC boot sector是固定512字节,这个文件的大小也固定为512byte.

boot.img功能很简单,主要是读磁盘中core.img中的第一个扇区(sector)到内存中并跳到该部分运行(如果是硬盘启动,那么该扇区就是下面要介绍的diskboot.img).因为只有512字节,boot.img不能够加载文件系统(比如CentOS7中XFS或其它Linux的EXT4等等),并且只能是从硬盘固定的位置加载.

3.2. diskboot.img

当从硬盘启动的时候这是core.img第一个扇区(sector)的内容,主要功能是读剩下的core.img到内存中并开始运行kernel.img. 同样diskboot.img没有文件系统的功能(XFS,EXT4等),当他读取剩余的core.img时候,依然从硬盘固定位置读取.

根据启动的介质不同,类似diskboot.img文件有很多,在安装GRUB时候选用其中一个,目前大部分都是从硬盘启动diskboot.img

1.cdboot.img: 从CD-ROM启动

2.pxeboot.img: 从PXE网络启动

3.Lnxboot.img: 如果从其他bootloader比如LILO(用image=’ section)启动,这个image使GRUB看上去像一个Linux内核.

3.3. kernel.img

这个文件包含了GRUB2基本的运行时支撑:对设备及文件的框架,环境变量,恢复模式下的命令行等等.一般我们不会直接使用它,但是它是core.img中必不可少的一部分.

3.4. core.img

这个是GRUB的核心.他是被grub2-mkimage命令生存,包含了kernel.img以及一些必须必要的modules. 通常core.img包含了足够的模块(modules)为了访问XFS/EXT4文件系统/boot/grub2目录,并且在运行时加载从文件系统(XFS)所有剩余的模块,这些剩余模块包含启动目录处理,加载操作系统等等功能.

模块化的设计思路最主要的目的是使core.img保持足够小,目前disk限制core.img安装必须小于32KB. core.img一般被安装在硬盘特殊区域:embedding area(夹层区),这个区一般有32K限制,我们在后面会介绍什么是夹层区.

3.5. *.mod

所有GRUB其他部分被称为模块,他们大部分被core.img在运行时自动动态加载,其中一小部分被整合到core.img中,这小部分是必须,比如文件系统支持(xfs.mod)

模块可以手工加载,请参考insmod command(在本文不作介绍,请查阅相关文档)

注意:如果对以上各种image文件还是不清楚,请继续往下读,后面会有详细说明。

4. 对比GRUB Legacy

GRUB2 与GRUB Legacy不同,很多人都熟悉GRUB Legacy里面的stage1, stage1_5,stage2等等概念,而在GRUB2里面,这些被各种bootstrap image文件替代:

boot.img diskboot.img core.img

大伙应悉知以上三个images

4.1. stage1

GRUB Legacy中stage1相当于GRUB 2中boot.img, 他们完成相同的功能.

4.2. *_stage1_5

GRUB Legacy中Stage 1.5包含了足够的文件系统(XFS或EXT4)代码而允许Stage2从文件系统中直接载入系统,就这个意义上来说很像GRUB 2中的core.img.

不过core.img功能更加强大,它提供了恢复shell,使能够在不能加载其他模块(modules)情况下(比如partition number已经改变)能够人工恢复。Core.img能够被灵活的创建,允许从LVM或者RAID加载模块.

GRUB Legacy 能够运行stage1和stage2而不运行stage1.5, 但是对GRUB2来说,core.img是必须的。

4.3. stage2

GRUB 2 没有对应stage2的image. 它从/boot/grub加载相应的模块。

5. grub2-mkimage定制core.img

上一章介绍的core.img至少包含了一下几个部分 1) diskboot.img 2) kernel.img

3) required *.mod

他们被grub2-mkimage命令整合成core.img文件,实际上当运行完该命令最终生成的是上一章中/boot/grub2/i386-pc目录下的两个image文件

1) boot.img 2) core.img

有兴趣的读者请参照man grub2-mkimage查阅相关信息,定制自己的core.img:

[lanzhou@controller image]$ grub2-mkimage -O i386-pc -p /boot/grub2 -v -o core.img xfs grub2-mkimage: info: the total module size is 0x26e4.

grub2-mkimage: info: reading /usr/lib/grub/i386-pc/kernel.img. grub2-mkimage: info: locating the section .text at 0x0. grub2-mkimage: info: locating the section .rodata at 0x5494. grub2-mkimage: info: locating the section .data at 0x6448. grub2-mkimage: info: locating the section .bss at 0x6bd0. grub2-mkimage: info: reading /usr/lib/grub/i386-pc/fshelp.mod. grub2-mkimage: info: reading /usr/lib/grub/i386-pc/xfs.mod. grub2-mkimage: info: kernel_img=0x2649700, kernel_size=0x6bd0. grub2-mkimage: info: the core size is 0x5085.

grub2-mkimage: info: reading /usr/lib/grub/i386-pc/lzma_decompress.img. grub2-mkimage: info: reading /usr/lib/grub/i386-pc/diskboot.img. grub2-mkimage: info: writing 0x200 bytes. grub2-mkimage: info: writing 0x5bc5 bytes.

core.img是内核的名字,生成在当前目录下, i386-pc是镜像的格式, xfs是加入内核的模块。常用模块有:

xfs: 支持 xfs 文件系统(CentOS7缺省的文件系统) ext2: 支持 ext2 文件系统

但是我们一般不直接运行该命令,一般运行grub2-install. 该命令会自动调用grub2-mkimage.

6. GRUB2安装

当一切准备好后,我们可运行以下命令

grub2-install /dev/sda

去安装grub2(缺省的--boot-directory=/boot/)

不过一般的CentOS7已经安装好了GRUB2,除非你感兴趣,不用运行上面命令.

在继续后面的内容之前,下面有几个概念大伙必须知道:

6.1. Linux是怎么命名设备(/dev/sda)

比如/dev/sda5,如下图所示

1) SCSI硬盘

第一个SCSI接口硬盘:/dev/sda 第二个SCSI接口硬盘:/dev/sdb

2) USB

如果只有一块硬盘,那么第一个USB盘也可能是 /dev/sdb (usb存储设备也目前在内核中在两种驱动方法,一种是模拟SCSI硬盘,另一种是非模拟SCSI硬盘,前一种目前比较多)

3) SATA

如果只有一个SATA硬盘,第一个SATA硬盘也是: /dev/sda

4) IDE硬盘

第一个IDE接口主盘: 第一个IDE接口从盘: 第二个IDE接口主盘: 第二个IDE接口从盘:

注意:

/dev/hda /dev/hdb /dev/hdc /dev/hdd

1) 所以只靠/dev/sda无法知道设备类型,也许是SCSI, SATA或者U盘,但是一般来说/dev/sda都会是SCSI硬盘, 本文不在详述SCSI,IDE,SATA区别,请参考相关资料 2)有关分区,见下文6.3DPT

6.2. MBR(Master Boot Record)

MBR,即主引导记录,是对IBM兼容机的硬盘或者可移动磁盘分区时,在驱动器最前端的一段引导扇区,其地址为

采用CHS寻址: MBR固定为硬盘的0柱面、0磁头、1扇区 采用LBA寻址: MBR固定LBA0

(注意,我们将在后面介绍CHS与LBA,目前普片采用LBA寻址)

MBR长度为512字节,它一般由三个部分组成: 主引导程序

硬盘分区表DPT(Disk Partition table) 固定4个分区,每分区16byte 分区有效标志, 以55AA结尾的MBR

Structure of a classical generic MBR

Address Hex +000hex +1BEhex +1CEhex +1DEhex +1EEhex +1FEhex +1FFhex

Dec +0 +446 +462 +478 +494 +510 +511

Bootstrap code area

Partition entry 1 Partition entry 2 Partition entry 3 Partition entry 4

55hex

Boot signature[a]

AAhex

Total size: 446 + 4*16 + 2

512 2

Partition table (for primary partitions)

16 16

Description

Size (bytes) 446 16 16

本表摘自:https://en.wikipedia.org/wiki/Master_boot_record 注意

1) 由于后面支持EFI及GPT,上述结构有些变化,但在本文不作描述,有兴趣的读者可以参看相关EFI及GPT文档

2) 上文中的boot.img将会被grub2-install命令拷贝到MBR主引导程序部分(也就是Bootstrap code area),在后面将会详细分析boot.img

==================================================== 工具: 导出并查看MBR

====================================================

我们可以用DD命令把MBR从硬盘中导出来

[root@controller image]# dd if=/dev/sda of=mbr.bin bs=1 count=512 512+0 records in 512+0 records out

512 bytes (512 B) copied, 0.00173968 s, 294 kB/s

然后我们可以使用hexdump查看其内容

[root@controller image]# hexdump -C mbr.bin

00000000 eb 63 90 10 8e d0 bc 00 b0 b8 00 00 8e d8 8e c0 |.c..............| 00000010 fb be 00 7c bf 00 06 b9 00 02 f3 a4 ea 21 06 00 |...|.........!..| 00000020 00 be be 07 38 04 75 0b 83 c6 10 81 fe fe 07 75 |....8.u........u| 00000030 f3 eb 16 b4 02 b0 01 bb 00 7c b2 80 8a 74 01 8b |.........|...t..| 00000040 4c 02 cd 13 ea 00 7c 00 00 eb fe 00 00 00 00 00 |L.....|.........| 00000050 00 00 00 00 00 00 00 00 00 00 00 80 01 00 00 00 |................| 00000060 00 00 00 00 ff fa 90 90 f6 c2 80 74 05 f6 c2 70 |...........t...p| 00000070 74 02 b2 80 ea 79 7c 00 00 31 c0 8e d8 8e d0 bc |t....y|..1......| 00000080 00 20 fb a0 64 7c 3c ff 74 02 88 c2 52 be 05 7c |. ..d|<.t...R..|| 00000090 b4 41 bb aa 55 cd 13 5a 52 72 3d 81 fb 55 aa 75 |.A..U..ZRr=..U.u| 000000a0 37 83 e1 01 74 32 31 c0 89 44 04 40 88 44 ff 89 |7...t21..D.@.D..| 000000b0 44 02 c7 04 10 00 66 8b 1e 5c 7c 66 89 5c 08 66 |D.....f..\\|f.\\.f| 000000c0 8b 1e 60 7c 66 89 5c 0c c7 44 06 00 70 b4 42 cd |..`|f.\\..D..p.B.| 000000d0 13 72 05 bb 00 70 eb 76 b4 08 cd 13 73 0d 5a 84 |.r...p.v....s.Z.| 000000e0 d2 0f 83 de 00 be 85 7d e9 82 00 66 0f b6 c6 88 |.......}...f....| 000000f0 64 ff 40 66 89 44 04 0f b6 d1 c1 e2 02 88 e8 88 |d.@f.D..........| 00000100 f4 40 89 44 08 0f b6 c2 c0 e8 02 66 89 04 66 a1 |.@.D.......f..f.| 00000110 60 7c 66 09 c0 75 4e 66 a1 5c 7c 66 31 d2 66 f7 |`|f..uNf.\\|f1.f.| 00000120 34 88 d1 31 d2 66 f7 74 04 3b 44 08 7d 37 fe c1 |4..1.f.t.;D.}7..| 00000130 88 c5 30 c0 c1 e8 02 08 c1 88 d0 5a 88 c6 bb 00 |..0........Z....| 00000140 70 8e c3 31 db b8 01 02 cd 13 72 1e 8c c3 60 1e |p..1......r...`.| 00000150 b9 00 01 8e db 31 f6 bf 00 80 8e c6 fc f3 a5 1f |.....1..........| 00000160 61 ff 26 5a 7c be 80 7d eb 03 be 8f 7d e8 34 00 |a.&Z|..}....}.4.| 00000170 be 94 7d e8 2e 00 cd 18 eb fe 47 52 55 42 20 00 |..}.......GRUB .| 00000180 47 65 6f 6d 00 48 61 72 64 20 44 69 73 6b 00 52 |Geom.Hard Disk.R| 00000190 65 61 64 00 20 45 72 72 6f 72 0d 0a 00 bb 01 00 |ead. Error......| 000001a0 b4 0e cd 10 ac 3c 00 75 f4 c3 00 00 00 00 00 00 |.....<.u........| 000001b0 00 00 00 00 00 00 00 00 a7 36 08 00 00 00 80 20 |.........6..... | 000001c0 21 00 83 dd 1e 3f 00 08 00 00 00 a0 0f 00 00 dd |!....?..........| 000001d0 1f 3f 8e fe ff ff 00 a8 0f 00 00 58 f0 00 00 00 |.?.........X....| 000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| 00000200

黑色部分为启动代码

蓝色部分为分区表DPT(下面会介绍) 红色部分为分区有效标志55AA

6.3. MBR分区表DPT(Disk Partition Table)

DPT作为MBR中的一部分,已经使用了好长一段时间,直到最近GPT及EFI的出现。 但是DPT作为一个经典,还是有很多东西学习,在这一章我简要介绍一下DPT.

==================================================== 工具: fdisk

==================================================== linux上经典的工具fdisk就是对有关的DPT进行操作。

[root@controller sbin]# fdisk -l /dev/sda

Disk /dev/sda: 8589 MB, 8589934592 bytes, 6777216 sectors Units = sectors of 1 * 512 = 512 bytes

Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk label type: dos Disk identifier: 0x000836a7

Device Boot Start End Blocks Id System /dev/sda1 * 2048 1026047 512000 83 Linux /dev/sda2 1026048 16777215 7875584 8e Linux LVM

可以看到我的CentOS7上面/dev/sda这个SCSI硬盘包含了两个分区: sda1, sda2 注意其中sda1从2048 sector(LBA2048)开始

1) 我们说了MBR在第一个sector, 所有第一个分区sda1不能从0sector开始 2) 从很早前dos,第一个分区只能从LBA64开始,

3) 而从LBA2048开始是为了2011年后采用高级格式化的硬盘4k分区对齐

fdisk输出单位:

1 block = 1024bytes = 2 sectors

1 sector = 512bytes

==================================================== DPT解析

==================================================== 如果把上一章6.2MBR中导出的mbr.bin HEX中的DPT对比查看

partition1: 80 20 21 00 83 dd 1e 3f 00 08 00 0000 a0 0f 00 partition2: 00 dd 1f 3f 8e fe ff ff 00 a8 0f 0000 58 f0 00 partition3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 partition4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

对照下面两张表翻译过来 引导标志 分区类型 partition1 partition1 0x80(活动) 0x00 0x83(Linux) 0x8e(Linux LVM) 起始扇区 0x0800 (2048 sectors) 0x0fa800 (1026048 sectors) 总扇区 0xfa000 (1024000 sectors) 0xf05800 (15751168 sectors) 与fdisk输出完全一致

==================================================== DPT编码

==================================================== DPT中各自段意义 MBR偏移 偏移 长度 0x01BE 0x01BF 0x01C0 0x01C1 0x01C2 0x01C3 0x01C4 0x01C5 0x01C6 0x01CA

分区类型描述(Partition Type Indicator) 代码 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x08 0x09 0x0A 0x0B 0x0C 0x0E 0x0F 0x10 0x11 0x12 0x14 0x16 0x17 0x18 0x1B 0x1C 0x1E 0x24 类型 Dos或Windows不允许使用,视为非法 FAT12 XENIX root XENIX usr FAT16小于32MB Extended FAT16大于32MB AIX AIX bootable OS/2 Boot Manage Windows 95 FAT32 Windows 95 FAT32 Windows 95 FAT16 Windows 95 Extended(>8GB) OPUS Hidden FAT12 Compaq diagnost Hidden FAT16 <32M Hidden FAT16 Hidden HPFS/NTFS AST Windows swap Hidden FAT32 Hidden FAT32 partition(Using LBA-mode INT 13 extensions) Hidden LBA VFAT partition NEC DOS 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x0C 1Byte 1Byte 6位 10位 1Byte 1Byte 6位 10位 4Byte 4Byte 含义 引导标志(Boot Indicator):指明该分区是否是活动分区 起始磁头(Start Head) 起始扇区(Start Sector): 只用0~5位,后面两位被起始柱面使用 起始柱面(Start Cylinder): 共10位,最大值1023 分区类型描述(Partition Type Indicator),分区类型 结束磁头(End Head) 结束扇区(End Sector): 只用0~5位,后面两位被结束柱面使用 结束柱面(End Cylinder): 共10位,最大值1023 本分区之前使用的扇区数(Sectors Preceding Partition) 本分区的总扇区数(Sectors in partition) 0x3C 0x40 0x41 0x42 0x4D 0x4E 0x4F 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x5C 0x61 0x63 0x64 0x65 0x70 0x75 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x8e 0x93 0x94 0xA0 0xA5 0xA6 0xA7 0xB7 0xB8 0xC0 0xC1 0xC4 0xC6 0xC7 Partition Magic Venix 80286 PPC PreP Boot SFS QNX4.x QNX4.x 2 part QNX4.x 3 part OnTrack DM OnTrack DM6 Aux CP/M OnTrack DM6 Aux OnTrack DM6 EZ-Drive Golden Bow Priam Edisk Speed Stor GNU HURD or Sys Novell Netware Novell Netware Disk Secure Mult PC/IX Old Minix Minix/Old Linux Linux swap Linux OS/2 hidden C: Linux Extended NTFS volume Set NTFS volume Set Linux LVM Amoebba Amoebba BBT IBM Thinkpad hidden BSD/386 Open BSD NextSTEP BSDI fs BSDI swap DR-DOS/Novell Dos secured partition DR-DOS/sec DR-DOS/sec DR-DOS/sec Syrinx rdnd0xDB 0xE1 0xE3 0xE4 0xEB 0xF1 0xF2 0xF4 0xFE 0xFF CP/M/CTOS DOS access DOS R/0 SpeedStor BeOS fs Speed Stor DOS 3.3 + secondary partition Speed Stor LAN Step BBT 采用DPT同一块硬盘可以安装多种文件系统而互不影响

==================================================== 扩展分区

==================================================== 按照DPT中要求,linux最多限制有4个主分区 1)也就是你可以把硬盘分为4个主分区 /dev/sda1 (主分区) /dev/sda2 (主分区) /dev/sda3 (主分区) /dev/sda4 (主分区)

如果需要把硬盘分为多4个分区,那么必须使用扩展分区,扩展分区做多1个,在扩展分区下面在设立若干个逻辑分区: /dev/sda1 (主分区) /dev/sda2 (主分区) /dev/sda3 (扩展分区)

/dev/sda5 (逻辑分区) /dev/sda6 (逻辑分区) /dev/sda7 (逻辑分区)

注意:

1)扩展分区的分区类型描述(Partition Type Indicator)为0x85 2)逻辑分区并不是从sda4开始,sda4固定分配给主分区

6.4. LBA and CHS

关于硬盘的寻址与定位有两种方式:CHS是24位寻址,LBS是可以支持64位寻址

6.4.1. CHS(cylinders-heads-sectors)

CHS寻址模式将硬盘划分为磁头(Heads)、柱面(Cylinder)、扇区(Sector)。

柱面(Cylinder):所有磁片中半径相同的同心磁道构成“柱面\,意思是这一系列的磁道垂直叠在一起,就形成一个柱面的形状。简单地理解,柱面数=磁道数。

磁头(Heads):每张磁片的正反两面各有一个磁头,一个磁头对应一张磁片的一个面。因此,用第几磁 头就可以表示数据在哪个磁面。

扇区(Sector):将磁道划分为若干个小的区段,就是扇区。虽然很小,但实际是一个扇子的形状,故称为扇区。每个扇区的容量为512字节。

知道了磁头数、柱面数、扇区数,就可以很容易地确定数据保存在硬盘的哪个位置。也很容易确定硬盘的容量,其计算公式是:

硬盘容量=磁头数×柱面数×扇区数×512字节

CHS限制1: BIOS INT 13h 接口

柱面地址10位 1024 磁头地址8位 256 扇区地址6位 63 (扇区从1开始,并不是从0开始) 共24位的寻址方式, 最大硬盘空间8GB CHS限制2: IDE(ATA)

柱面地址16位 65536 磁头地址4位 16 扇区地址6位 63

共24位的寻址方式, 最大硬盘空间127.5GB

CHS目前基本上不再使用,由于24位寻址空间的局限太大,目前普片采用LBA方式

采用CHS寻址,MBR地址为0柱面、0磁头、1扇区

6.4.2. LBA(Logical Block Address)

LBA是非常单纯的一种寻址模式﹔从0开始编号来定位区块,第一区块LBA=0,第二区块LBA=1,依此类推。这种寻址模式取代了原先操作系统必须面对存储设备硬件构造的方式。最具代表性的首推CHS(cylinders-heads-sectors,磁柱-磁头-扇区)寻址模式

LBA最大的好处能偷突破CHS 24bit限制,老式设备支持28bit LBA, 新设备一般支持48bit LBA

采用LBA寻址,MBR地址为LBA-0

6.5. boot.img和core.img安装

在了解上面的基础知识后,在回到我们的GRUB2,我们知道boot.img将会拷贝到MBR,也就是LBA-0的位置,那么core.img将会被放置在什么地方?MBR与GPT有不同解决方案:

请参考http://www.gnu.org/software/grub/manual/grub.html#BIOS-installation

1)MBR

在MBR和第一个partition之间有一段空白磁盘空间。大伙还记得5.3 DPT中fdisk命令查看第一个分区的位置从2048sector(LBA2048)开始,那么前面之一段都是空白。

这个空白磁盘空间有各种名称\但是有至少必须32Kbyte,一般来说core.img将会被拷贝到这个区域中

2) GPT

GPT作为UEF的一部分,也能够被使用在BIOS平台上,但是必须得有一个专门的GPT分区,这个分区至少32KB大小,建议1M,名字为BIOS Boot Partition,然后core.img拷贝到这个分区。

采用GPT方案有好处,不会使用\区域。

本文中将描述第一种MBR方案,也就是当运行grub2-install命令,至少会做两件事情

1) 拷贝boot.img到LBA-0的MBR引导代码中

2) 拷贝core.img到LBA-1开始到LBA-31中的空闲代码中

==================================================== 深入研究mbr.bin

====================================================

除了上面的,其实grub2-install还完成了其它的工作,有兴趣可以研究下,在这里进行一个额外的对比给大家看看:

1)mbr.bin (我们已经在6.2MBR中使用DD命令导出的MBR) 2)/boot/grub2/i386-pc/boot.img

然后使用命令hexdump导出文件

[root@controller i386-pc]# hexdump -Cv boot.img

00000000 eb 63 90 00 00 00 00 00 00 00 00 00 00 00 00 00 |.c..............| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 00 00 00 00 00 00 00 00 00 80 01 00 00 00 |................| 00000060 00 00 00 00 ff fa eb 05 f6 c2 80 74 05 f6 c2 70 |...........t...p| 00000070 74 02 b2 80 ea 79 7c 00 00 31 c0 8e d8 8e d0 bc |t....y|..1......| 00000080 00 20 fb a0 64 7c 3c ff 74 02 88 c2 52 be 05 7c |. ..d|<.t...R..|| 00000090 b4 41 bb aa 55 cd 13 5a 52 72 3d 81 fb 55 aa 75 |.A..U..ZRr=..U.u| 000000a0 37 83 e1 01 74 32 31 c0 89 44 04 40 88 44 ff 89 |7...t21..D.@.D..| 000000b0 44 02 c7 04 10 00 66 8b 1e 5c 7c 66 89 5c 08 66 |D.....f..\\|f.\\.f| 000000c0 8b 1e 60 7c 66 89 5c 0c c7 44 06 00 70 b4 42 cd |..`|f.\\..D..p.B.| 000000d0 13 72 05 bb 00 70 eb 76 b4 08 cd 13 73 0d 5a 84 |.r...p.v....s.Z.| 000000e0 d2 0f 83 de 00 be 85 7d e9 82 00 66 0f b6 c6 88 |.......}...f....| 000000f0 64 ff 40 66 89 44 04 0f b6 d1 c1 e2 02 88 e8 88 |d.@f.D..........|

00000100 f4 40 89 44 08 0f b6 c2 c0 e8 02 66 89 04 66 a1 |.@.D.......f..f.| 00000110 60 7c 66 09 c0 75 4e 66 a1 5c 7c 66 31 d2 66 f7 |`|f..uNf.\\|f1.f.| 00000120 34 88 d1 31 d2 66 f7 74 04 3b 44 08 7d 37 fe c1 |4..1.f.t.;D.}7..| 00000130 88 c5 30 c0 c1 e8 02 08 c1 88 d0 5a 88 c6 bb 00 |..0........Z....| 00000140 70 8e c3 31 db b8 01 02 cd 13 72 1e 8c c3 60 1e |p..1......r...`.| 00000150 b9 00 01 8e db 31 f6 bf 00 80 8e c6 fc f3 a5 1f |.....1..........| 00000160 61 ff 26 5a 7c be 80 7d eb 03 be 8f 7d e8 34 00 |a.&Z|..}....}.4.| 00000170 be 94 7d e8 2e 00 cd 18 eb fe 47 52 55 42 20 00 |..}.......GRUB .| 00000180 47 65 6f 6d 00 48 61 72 64 20 44 69 73 6b 00 52 |Geom.Hard Disk.R| 00000190 65 61 64 00 20 45 72 72 6f 72 0d 0a 00 bb 01 00 |ead. Error......| 000001a0 b4 0e cd 10 ac 3c 00 75 f4 c3 00 00 00 00 00 00 |.....<.u........| 000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 24 12 |..............$.| 000001c0 0f 09 00 52 be bd 7d 31 c0 cd 13 46 8a 0c 84 c9 |...R..}1...F....| 000001d0 75 0f be da 7d e8 cc ff eb 96 46 6c 6f 70 70 79 |u...}.....Floppy| 000001e0 00 bb 00 70 8e c3 31 db b8 01 02 b5 00 b6 00 cd |...p..1.........| 000001f0 13 72 d4 b6 01 b5 4f e9 f1 fe 00 00 00 00 55 aa |.r....O.......U.| 00000200

然后可以文本比较工具进行比较,贴图如下,左边是/boot/grub2/i386-pc/boot.img

大致有四处不同

1) 0x0003~0x0040, /boot/grub2/i386-pc/boot.img全为0, 在真正的磁盘MBR中,被grub2-install填入了必要的数据。

注意:0x0000~0x0002为eb 63 60汇编为jmp short 0x0067 ,中间这部分被grub2-insall装填部分数据,暂时不知道是为什么使用

2) 0x0066~0x0067, /boot/grub2/i386-pc/boot.img 为eb 05, 被grub2-install修改为90 90 90 为nop指令,这里修改了两个nop指令

3) 0x01b8~0x01bb, /boot/grub2/i386-pc/boot.img 00 00 00 00, 被grub2-install修改为 a7 36 08

00,这部分代码请参考https://en.wikipedia.org/wiki/Master_boot_record 其中的Structure of a modern standard MBR, 我拷贝到这里作参考

被修改的这4个Byte[a7 36 08 00] 为32 bit Disk Signature,它是可选的, 我想可能是为了UEFI使用。

4) 0x01be~0x01fd, DPT硬盘分区表部分,保持不变grub2-install不会去修改

如果大家有兴趣可以去研究代码,知道grub2-install还有很多工作,这里就不在描述。

6.6. 硬盘总结

一块SCSI硬盘逻辑结构简单来说如下图所示:

LBA0: 固定存储MBR,一般GRUB2的boot.img就存放在这里

LBA1~LBA2047: 被称为“Embedding Area”“MBR Gap”,一般GRUB2的core.img就存放这里 LBA2048后面为我的两个硬盘分区,他们放在连续的区域

7. 启动过程

在前面介绍相关的知识,咱们回归到主题,现在开始启动过程

7.1. BIOS

系统上电之后,由固化在ROM里面的BIOS代码运行,进行硬件检查及初始化工作。有关BIOS具体运行原理在这里不作描述,有兴趣的请参考相关书籍。

最近的EFI作为BIOS的替代或升级,支持更多的功能,在这里也不做描述。

BIOS运行的最后两步操作我们必须知道

1) 加载LBA-0(或者CHS的0柱面、0磁头、1扇区)的MBR,共512字节到内存中0x7C00位置

2) 从内存0x7C00位置运行

我们知道拷贝到MBR中的正式GRUB2 boot.img,从这个时候开始,GRUB2正式登上历史舞台。但是请注意,并不是说BIOS就全部退出舞台,BIOS依然为GRUB2提供底层服务,比如硬盘读取等等(通过BIOS INT13 功能), GRUB2在一穷二白基础上还离不开BIOS

============================================ 地址0x7C00

============================================

这里有个问题,为什么BIOS会加载MBR到0x7C00的位置,而不是其他的位置? 答案请参考:

http://stackoverflow.com/questions/2058690/what-is-significance-of-memory-at-00007c00-to-booting-sequence

这个问题是应该由最初的IBM PC BIOS的设计者们(软件及硬件)来回答,但是简单的答案可能是0x7C00是距离地下最初32K安装内存有1K大小(512Bytes给MBR以及另外的512Bytes作运行时堆栈使用)

7.2. GRUB2中boot.img

BIOS加载MBR到0x7C00并从该处运行的时候,实际上就是boot.img开始运行了,我们的工作来了,我们将介绍boot.img将要干什么,可以从官方网站http://www.gnu.org/software/grub/.去使用git下载源码, 源码是采用AT&T风格的汇编。

这里我们采用反汇编的方式取得Intel风格代码 1) 我们已经在6.2MBR中使用DD命令导出的MBR 2) 安装nasm

[root@controller i386-pc]# yum install nasm Loaded plugins: fastestmirror, langpacks Loading mirror speeds from cached hostfile * base: mirrors.btte.net * extras: mirrors.163.com * updates: mirrors.btte.net

Package nasm-2.10.07-7.el7.x86_64 already installed and latest version Nothing to do

3) 进行反汇编

[root@controller image]# ndisasm -o 0x7c00 mbr.bin > mbr.asm

这里加入-o 0x7c00是告诉反汇编器这段代码在0x7c00处运行,使生成的mbr.asm更加容易理解

在这里我把反汇编的代码mbr.asm作为附件供参考,后面的boot.S是官方源码:

mbr.asmboot.S

注意:boot.S是AT&T汇编语法,反编译的mbr.asm是Intel汇编语法,两者有区别,如果感兴趣,请参阅相关文档进行学习。下面详细讲解主要流程。

7.2.1. Step1无条件跳转

/*位于0x7C00的语句就是一条跳转语句, 跳转到0x7c65处*/

00007C00 EB63 jmp short 0x7c65

7.2.2. Step2 初始化

/* 禁止中断 目前是不安全的*/

00007C65 FA cli 00007C66 90 nop 00007C67 90 nop

/*

* 注意,BIOS在把控制权交给MBR前,会在DL中设置正确的Bit位,表明MBR来源 * DL = 00h 1st floppy disk(“drive A:”) * DL = 01h 2nd floppy disk(“drive B:”) * DL = 80h 1st hard disk * DL = 81h 2nd hard disk

* 因为我们是磁盘加载的MBR,所以我们dl里面存的是0x80。 * 下面这段应为来自官方源代码注释

* This is a workaround for buggy BIOSes which don't pass boot * drive correctly. If GRUB is installed into a HDD, check if * DL is masked correctly. If not, assume that the BIOS passed * a bogus value and set DL to 0x80, since this is the only * possible boot drive. If GRUB is installed into a floppy, * this does nothing (only jump).

* 意思是有些BIOS类型并没有正确的设置DL寄存器,我们检测是否DL置了0x80bit位 * 如过不是,我们手工设置为DL为0x80

* 注意test 相当于用and(位与)判断,实际上是测试Bit位是否被置上 */

00007C68 F6C280 test dl,0x80 00007C6B 7405 jz 0x7c72

/* 如果DL不在区间0-0x0f and 0x80-0x8f. 那么需要设置DL为0x80 * 如果DL在区间0x10-0x7f,那么不需要设置DL为0x80 */

00007C6D F6C270 test dl,0x70 00007C70 7402 jz 0x7c74 00007C72 B280 mov dl,0x80 00007C74 EA797C0000 jmp word 0x0:0x7c79

/* ax清零,ds赋值0,ss赋值0 */

00007C79 31C0 xor ax,ax 00007C7B 8ED8 mov ds,ax 00007C7D 8ED0 mov ss,ax

/*设置堆栈:设置了实模式下的堆栈段地址(栈顶位置) 0x2000*/

00007C7F BC0020 mov sp,0x2000

/*恢复中断 再次安全*/

00007C82 FB sti

/* 从内存0x7c64读入AL, 在0x7c64,我们值为0xFF, 比较两个数为相等 * 那么不执行0x7c8a

* 这里只是检查我们是否有forced disk reference */

00007C83 A0647C mov al,[0x7c64] 00007C86 3CFF cmp al,0xff

00007C88 7402 jz 0x7c8c 00007C8A 88C2 mov dl,al

7.2.3. Step3 判断硬盘是否支持LBA还是只支持CHS

/*在前面我们介绍了CHS及LBA, 这两种对硬盘读寻址有很大不同

*在加载后续GRUB2 core.img的前,我们必须判断硬盘是否支持LBA还是只支持CHS*/

/* save drive reference first thing! */

00007C8C 52 push dx

/* set %si to the disk address packet */

00007C8D BE057C mov si,0x7c05

/* 调用BIOS INT13功能,检查硬盘是否支持LBA

* 通过 BIOS 调用 INT 0x13 来确定是否支持扩展,LBA 扩展功能分两个子集 , 如下 : 第一个子集提供了访问大硬盘所必须的功能 , 包括:

**************************************************************** 1.检查扩展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0×80 ~ 0xff ) 2.扩展读 : ah = 42h 3.扩展写 : ah = 43h 4.校验扇区 : ah = 44h 5.扩展定位 : ah = 47h 6.取得驱动器参数 : ah = 48h

**************************************************************** 第二个子集提供了对软件控制驱动器锁定和弹出的支持 ,包括:

**************************************************************** 1.检查扩展 : ah = 41h 2.锁定/解锁驱动器 : ah = 45h 3.弹出驱动器 : ah = 46h 4.取得驱动器参数 : ah = 48h 5.取得扩展驱动器改变状态: ah = 49h

****************************************************************

我们采用的是ah=41h,bx=0x55aa,dl=0x80,所以是检查扩展是否存在。这个操作会改变 CF标志位的值。如果支持LBA,那么CF=0,否则CF=1。 */

00007C90 B441 mov ah,0x41 00007C92 BBAA55 mov bx,0x55aa 00007C95 CD13 int 0x13

/*

* %dl may have been clobbered by INT 13, AH=41H. * This happens, for example, with AST BIOS 1.04. */

00007C97 5A pop dx 00007C98 52 push dx

/*

* 下面的三个跳转语句都到0x7cd8位置,那个位置是CHS部分 * 有下面几种情况

* 1) 探查结果 CF=1(0x7c99 jc 0x7cd8),二话不说,跳转到CHS模式

* 2) CF=0是否就采用LBA呢?也不一定,还需要判断bx==0x55aa,如果不等则CHS模式 * 3)有一个FORCE_LBA Byte(INT13返回寄存器cx中),如果这个位是1,那么直接采用LBA MODE */

00007C99 723D jc 0x7cd8 00007C9B 81FB55AA cmp bx,0xaa55 00007C9F 7537 jnz 0x7cd8 00007CA1 83E101 and cx,byte +0x1 00007CA4 7432 jz 0x7cd8

/* 由于现在硬盘都是LBA模式,这里我们就不在对CHS模式介绍了,直接进入LBA*/

7.2.4. Step4 采用LBA加载core.img第一个扇区

/* 对于BIOS LBA读来说,最重要的是设置SI所在内存块的值

由si及其偏移量指向的内存保存着磁盘参数块

SI在上面7.2.3中被设置为0x7c05,根据下面的偏移量来设置 如下:

****************************************************************** 偏移 地址

大小

位数

描述

值 0x10 0x00 0x01

00h 0x7c05 BYTE 8 01h 0x7c06 BYTE 8 02h 0x7c07 WORD 16 04h 0x7c09 DWORD 32 08h 0x7c0d QWORD 64

数据块的大小 (10h or 18h) 保留,必须为0

传输数据块数,传输完成后保存传输的块数 传输时的数据缓存地址

0x7000:[0x0000]

起始绝对扇区号(即起始扇区的LBA号码) 0x01

****************************************************************** 下面的很多代码都是在初始化SI 所指向的内存

注意[si+0x4]表示的是 segment:offset pointer to the memory buffer to which sectors will be transferred (note that x86 is little-endian: if declaring the segment and offset separately, the offset must be declared before the segment) */

/* 下面这个首先把内存中si+0x4的位置,传输数据时的数据缓存地址 * 也就是0x7c09~0x7c0a 16bit置为0 */

00007CA6 31C0 xor ax,ax 00007CA8 894404 mov [si+0x4],ax

/* AX现在值为0x0001*/

00007CAB 40 inc ax

/* set the mode to non-zero 也就是把内存[si-1]0x7c04的值置为1 * (也就是mode被置1,表示LBA扩展读;如果是0,就是CHS寻址读) */

00007CAC 8844FF mov [si-0x1],al

/* 把内存0x7c07-0x7c08 16bit 的值置为1

* 也就是需要传输数据块数,这里我们将要传输core.img中第一个扇区 */

00007CAF 894402 mov [si+0x2],ax

/* the size and the reserved byte * 把内存0x7c05的值置为0x10 (固定) * 把内存0x7c06的值置为0x00 (固定)*/

00007CB2 C7041000 mov word [si],0x10

/* the absolute address 起始绝对扇区号(即起始扇区的LBA-1号码) 0x7c0d ~ 0x7c11 32bit: 内存0x7c5c值为01 00 00 00 0x7c12 ~ 0x7c15 32bit: 内存0x7c60 值为 00 00 00 00

*最后就是制定LBA地址为LBA1,也就是第二个扇区,在前面我么说了这个/

00007CB6 668B1E5C7C mov ebx,[0x7c5c] 00007CBB 66895C08 mov [si+0x8],ebx 00007CBF 668B1E607C mov ebx,[0x7c60] 00007CC4 66895C0C mov [si+0xc],ebx

/* the segment of buffer address 传输时的数据缓存地址 * 也就是0x7c0b~0x7c0c 16bit置为0x7000 * 0x7c09~0x7c0a 16bit置为在前面已经被置为0 * 数据缓存地址为 :0x7000:[0x0000]

* 注意0x7000为段地址,偏移为0x0000,实际物力地址将是0x70000 */

00007CC8 C744060070 mov word [si+0x6],0x7000

/*

* 调用BIOS功能\ * 从硬盘读指定扇区到内存中

* 参数如下 %ah = 0x42 (在上面一节中2.扩展读: ah = 42h) * * * * */

%dl = drive number (在7.2.2中已经被置为 0x80) %ds:%si = segment:offset of disk address packet (DS在7.2.2中已经被初始化为0, SI在上面被置为0x7c05) %al = 0x0 成功; err code on failure

* 返回:

00007CCD B442 mov ah,0x42 00007CCF CD13 int 0x13

/* LBA 扩展读失败,可能不支持LBA,跳转采用CHS方式 */

00007CD1 7205 jc 0x7cd8

/* 进入下一步,准备拷贝刚读入的core.img第一个扇区数据到指定位置 */

00007CD3 BB0070 mov bx,0x7000 00007CD6 EB76 jmp short 0x7d4e

/* 总结,上面这段这么多的汇编代码就是完成一个功能:

调用BIOS INT14 ah=0x42 把硬盘LBA-1的512字节传输到内存0x7000:[0x0000]位置 注意:0x7000:[0x0000]实际上是段地址:[段内偏移]实际地址为 0x70000 */

7.2.5. Step5 拷贝core.img第一个扇区到内存指定位置

/*

* We need to save %cx and %si because the startup code in * kernel uses them without initializing them. */

00007D4E 60 pushaw 00007D4F 1E push ds

/* 循环256次:0x100*/

00007D50 B90001 mov cx,0x100

/* bx 在上面被置为0x7000, 也就是从硬盘拷贝过来的地址*/

00007D53 8EDB mov ds,bx 00007D55 31F6 xor si,si

/* di 是我们目标地址0x8000, 也就是core.img要从这个位置开始执行*/

00007D57 BF0080 mov di,0x8000

/* 初始化es为0*/

00007D5A 8EC6 mov es,si

/* 重复前固定操作,清除方向标志位*/

00007D5C FC cld

/* 开始循环 move string word

ds:[si] (0x7000:[0x0000])移到es:[di](0x0000:[0x8000]) 一次一个word,也就是2 byte,循环256次共512byte*/

00007D5D F3A5 rep movsw

00007D5F 1F pop ds 00007D60 61 popaw

/* 总结,上面这段这么多的汇编代码就是完成一个功能:

* 把core.img第一个扇区从内存0x70000位置拷贝到0x8000位置 */

==================================================== 学习: 0x7000:[0x0000]表示什么意思

====================================================

物理地址就是地址总线上提供的20位地址信息,8086 CPU 能提供20位的地址信息,可直接对1M个存储单元进行访问,而CPU内部可用来提供地址信息的寄存器都是16位。 那怎样用16位寄存器来实现20位地址寻址呢。答案就是使用了下面的:

段地址:[段内偏移地址]

那么我们就使用之前说20位的地址信息可以对1M个内存单元进行访问,就是说编址00000H~FFFFFH,而段寄器CS,DS,SS,ES即存放了这些地址的高4位,如比如我们的上面所说的地址0x70000:

ds便会存储0x7000高16bit信息,这即为段地址。 si存储段内偏移0x0000低16bit信息。

知道了段地址与段内偏移,那么我们可以计算实际地址: 物 理地址=段地址*10H+段内偏移地址。

段地址乘以10H是因为段地址当时是取高四位得到的,所以还原后要让段地址左移4位(10H = 10000B),例如(ds)= 0x7000,(si)= 0x0000,则物理地址为0x7000*0x10+0x0000 = 0x70000。

==================================================== 为什么要分两步拷贝

====================================================

也许有人会问,为什么在读硬盘的时候不直接读到0x8000位置,要先读到0x70000位置,然后再从0x70000拷贝到0x8000位置? 这个问题读者可以自行思考一下,请参考:

http://stackoverflow.com/questions/21339410/why-doesnt-grub2-load-the-diskboot-img-to-address-0x8000-directly

有时候会觉得答案很吃惊噢

7.2.6. Step6 执行core.img第一条语句

/* 跳转下一步,准备执行core.img第一个语句:也就是diskboot.img第一个 内存0x7c5a处存储的值为 00 80, 也就是地址0x8000*/

00007D61 FF265A7C jmp word [0x7c5a]

7.2.7. Step7 boot.img总结

boot.img的功能是是将硬盘LBA-1(0柱面,0磁道,2扇区)的512字节搬移到0x8000处。 这部分代码就是core.img的第一个扇区,一般是GRUB2的diskboot.img

有关boot.img其它的代码,在这里不再详解,大伙可以自行研究一下。

7.3. GRUB2中core.img

下面进入GRUB2最主要的部分,也就是core.img, 当然core.img我们了解到包含了diskboot.img, kernel.img以及必须的*.mod. 下面我们先来看看diskboot.img

7.3.1. diskboot.img

diskboot一般是core.img的第一个代码,位于第一个扇区,当如如果启动的不是硬盘,那么core.img第一段代码可能是cdboot.img(如果你重光盘启动)。本文我们主要介绍diskboot.img

整个diskboot的作用很简单,是从LBA-2(0柱面 0磁道 3扇区) 开始拷贝若干个扇区到内存。起始扇区号,扇区个数 内存目的地址都在diskboot中定义。

==================================================== 三个diskboot

====================================================

diskboot是被整合到core.img,所有在/boot/grub2/i386-pc/目录下并没有diskboot.img,但是你可以去/usr/lib/grub/i386-pc目录下有diskboot.img,我们可以作一个对比:

1) /usr/lib/grub/i386-pc/diskboot.img

2) /boot/grub2/i386-pc/core.img 最开始512byte 3) 硬盘LBA-1的512byte (lba1.bin)

下面我们导出硬盘LBA-1的512byte,并hexdump

[root@controller image]# dd if=/dev/sda of=lba1.bin bs=512 skip=1 count=1

上述命令跳过LBA-0导出LBA-1的512字节

[root@controller image]# hexdump -Cv lba1.bin >lba1.hex

Hexdump diskboot.img

[root@controller image]# cd /usr/lib/grub/i386-pc

[root@controller i386-pc]# hexdump -Cv diskboot.img > /home/lanzhou/image/diskboot.hex

Hexdump core.img

[root@controller i386-pc]# cd /boot/grub2/i386-pc

[root@controller i386-pc]# hexdump -Cv core.img > /home/lanzhou/image/core.hex

比较上面三个

除了0x01fc这个Byte不一样,其余完全一样

[注意,0x01fc这个地方是被grub2-install所修改,指示core.img剩余还有多少扇区]

====================================================

反汇编

====================================================

diskboot.img是core.img第一个扇区代码,在被boot.img加载到0x8000位置并被执行,下面开始讲解diskboot.img 同样进行反汇编

[root@controller image]# ndisasm -o 0x8000 lba1.bin > lba1.asm

这里以附件形式给出所有代码(lba1.asm是反编译后的,diskboot.S是官方源码)

lba1.asmdiskboot.S

7.3.1.1. Step1 初始化

/*

* 我们在这里继续使用boot.img的stack

* 并且认为一些寄存器已经在boot.img中设定了正确的值 */

/* save drive reference first thing! */

00008000 52 push dx

/* this sets up for the first run through \

00008001 BFF481 mov di,0x81f4

/* di为0x81f4,那个位置的值为0x02,那么把该值拷贝到ebp * ebp中现在存储的是core.img的第二个扇区的LBA地址

* 注意,core.img的第一个扇区就是本代码diskboot.img */

00008004 668B2D mov ebp,[di]

/* 内存[di+8] 0x81fc存储的是有core.img剩下有多少扇区将要被读入 * 内存0x81fc的值为0x0067,表明core.img后面还有67个扇区需要被载入 * 代码在这里判断一下是否为0,如果为0表示不需要继续载入core.img * 直接跳转到到0x80f1处Step4-bootit) 不用加载硬盘*/

00008007 837D0800 cmp word [di+0x8],byte +0x0 0000800B 0F84E200 jz word 0x80f1

7.3.1.2. Step2) 从硬盘读取core.img第二个扇区开始的若干扇区

/* 首先检查硬盘是LBA模式还是CHS模式*/

0000800F 807CFF00 cmp byte [si-0x1],0x0

/* 如果是CHS模式,直接挑转 0x805b,这里不在介绍CHS模式*/

00008013 7446 jz 0x805b

/* 转载硬盘起始地址,共64bit, [di]内存为0x81f4.其值为2,硬盘地址为LBA-2 */

00008015 668B1D mov ebx,[di] 00008018 668B4D04 mov ecx,[di+0x4]

/* LBA读取最大扇区数为0x7f,这是因为Phoenix EDD限制 * 这里我没有去查什么是Phoenix EDD*/

0000801C 6631C0 xor eax,eax 0000801F B07F mov al,0x7f

/* 检查下我们需要拷贝读取的硬盘扇区数是否超过0x7f * 也就是BIOS INT13 ah=0x42一次组多读取0x7F扇区 * 但是GRUB不会超过这个数,当前我用来讲解的版本是67 */

00008021 394508 cmp [di+0x8],ax 00008024 7F03 jg 0x8029

/* 如果没有超过了,我们就把总扇区数保存起来,AX存储当前读写的扇区数*/

00008026 8B4508 mov ax,[di+0x8]

/* [di+0x8] 存储总的扇区数 * AX存储当前这次需要读取扇区数 *

* 运行该条指令后, [di+0x8] 存储剩余的扇区数,还需要继续读取的 * * 一般来说只需要读取一次,这条指令后,[di+0x8] 将存储为 0*/

00008029 294508 sub [di+0x8],ax

/* [di]为内存0x81f4,该地存储已经读取的扇区数, 也就是下次需要读取的起始地址 */

0000802C 660105 add [di],eax

0000802F 6683550400 adc dword [di+0x4],byte +0x0

/* 参照下表设置

SI在上面7.2.3中被设置为0x7c05,根据下表设置

****************************************************************** 偏移 地址

大小

位数

描述

值 0x10 0x00 0x67

00h 0x7c05 BYTE 8 01h 0x7c06 BYTE 8 02h 0x7c07 WORD 16 04h 0x7c09 DWORD 32 08h 0x7c0d QWORD 64 /* 大小和保留 设置为0x0010

00008034 C7041000 mov word [si],0x10

数据块的大小 (10h or 18h) 保留,必须为0

传输数据块数,传输完成后保存传输的块数 传输时的数据缓存地址

0x7000:[0x0000]

起始绝对扇区号(即起始扇区的LBA号码) 0x01

******************************************************************/

/* 传输的数据块数放ax中,在这个例子中为0x67 */

00008038 894402 mov [si+0x2],ax

/* 硬盘拷贝的起始扇区为LBA-2 ebx, ecx在前面已经设置过*/

0000803B 66895C08 mov [si+0x8],ebx 0000803F 66894C0C mov [si+0xc],ecx

/* 依然拷贝到内存0x7000:[0x0000]处地址*/

00008043 C744060070 mov word [si+0x6],0x7000 00008048 50 push ax /*保存ax=0x67 */ 00008049 C744040000 mov word [si+0x4],0x0

/*

* BIOS call \ * Call with %ah = 0x42 * * * */

0000804E B442 mov ah,0x42 00008050 CD13 int 0x13

%dl = drive number

%ds:%si = segment:offset of disk address packet %al = 0x0 on success; err code on failure

* Return:

/* 如果读取错误,跳转到0x8105进行处理,在这里就不做介绍 */

00008052 0F82AF00 jc word 0x8105

/* 准备拷贝buffer */

00008056 BB0070 mov bx,0x7000 00008059 EB66 jmp short 0x80c1

/* 注意,也许一次没有从硬盘读取完,还需要继续读取,但是下次读取硬盘必须等待buffer 拷贝结束*/

7.3.1.3. Step3) 拷贝缓冲区

/* 注意,从硬盘读取的数据放在0x70000位置, 我们需要拷贝到一个区域*/ /* load addresses for copy from disk buffer to destination * [di+0xa]=0x81f4+0xa=0x81fe初始化内存存放为 20 08 * es 寄存器第一次值为0x0820 /

000080C1 8E450A mov es,[di+0xa]

/* 恢复 %ax =0x67*/

000080C4 58 pop ax

/* 计算下一个可能目标地址 (presuming512 byte sectors!) */

000080C5 C1E005 shl ax,byte 0x5/* shift %ax five bits to the left 0x67 */ 000080C8 01450A add [di+0xa],ax /* 更新[di+0xa]位0x8200 */

/* 保存地址寄存器 */

000080CB 60 pushaw 000080CC 1E push ds

/* 计算拷贝的长度 */

000080CD C1E003 shl ax,byte 0x3 /* ax X 8*/ 000080D0 89C1 mov cx,ax

000080D2 31FF xor di,di /* 目的地址置0 */ 000080D4 31F6 xor si,si /* 源地址置0 */

000080D6 8EDB mov ds,bx /* 恢复原段地址: 0x7000 */

/* 开始拷贝从 ds:[si] 0x7000:[0x0000])处拷贝到es:[di]0x0820:[0x0000]处 起始物力地址为0x70000,目的物理地址为0x8200

注意: es=0x0820为段地址, 转化为物理地址需要左移4bit*/

000080D8 FC cld 000080D9 F3A5 rep movsw

/* restore addressing regs and print a dot with correct DS

(MSG modifies SI, which is saved, and unused AX and BX) */

000080DB 1F pop ds

000080DC BE1B81 mov si,0x811b /*打印message*/ 000080DF E85700 call word 0x8139 000080E2 61 popaw

/* 检查是否还有剩余的硬盘扇区需要读写不,如果需要,返回Step2)继续读取 * step2) 的运行地址为0x800f */

000080E3 837D0800 cmp word [di+0x8],byte +0x0

000080E7 0F8524FF jnz word 0x800f

/* update position to load from 并跳转到bootloop

* 在step1) 初始化中我们继续判断 是否还需要读取剩余的扇区,如果没有那么进入下一步/

000080EB 83EF0C sub di,byte +0xc 000080EE E916FF jmp word 0x8007

7.3.1.4. Step4) Bootit: core.img

000080F1 BE1D81 mov si,0x811d /*打印message*/ 000080F4 E84200 call word 0x8139

/*跳转到 8200并开始执行, 8200地址为core.img的运行地址*/

000080F7 5A pop dx

000080F8 EA00820000 jmp word 0x0:0x8200

7.3.1.5. 总结

diskboot.img把LBA2到LBA67的core.img全部读入到内存0x8200位置, 这部分LBA2-LBA67内容分为两个内容:

1) 开始部分是start_raw.S的代码

2) 后一部分是压缩的GRUB2核心代码 后面进入GRUB2 startup部分

7.3.2. GRUB2 core Startup

在diskboot.img完成任务后,后面的工作又core.img中的Startup接手,startup分为两个部分:

Startup_raw Startup

为了节约时间,这部分代码我们不再进行反汇编讲解,而是直接从官方网站直接取得代码。 大伙可以登录官方网站 http://www.gnu.org/software/grub/ 并可以直接使用git下载代码

git clone git://git.savannah.gnu.org/grub.git

7.3.2.1. Startup_raw

这段代码位置:grub-core/boot/i386/pc/Startup_raw.S

依然为汇编语言,官方代码采用AT&T风格的汇编语言编写

/*

* 这段代码Startup_raw.S必须加载到0x0:0x8200. */

7.3.2.1.1. Step1) 初始化

/*首先需要 设置数据段、堆栈段和扩展段寄存器,以及栈指针。*/

cli

/* we're not safe here! */

/* set up %ds, %ss, and %es */ xorw %ax, %ax movw %ax, %ds movw %ax, %ss movw

%ax, %es

/* set up the real mode/BIOS stack */ movl $GRUB_MEMORY_MACHINE_REAL_STACK, ?p movl

?p, %esp

sti

/* we're safe again */

7.3.2.1.2. Step2) 进入保护模式

/*进行一些准备工作后开始进入保护模式

* real_to_prot 这个函数的代码在 grub-core/kern/i386/realmode .S中 。

/* save the boot drive */

*/

movb %dl, LOCAL(boot_drive)

/* reset disk system (%ah = 0) */ int $0x13

/* transition to protected mode */ calll real_to_prot

7.3.2.1.3. Step3) 解压核心代码

#ifdef ENABLE_LZMA

/* GRUB_MEMORY_MACHINE_DECOMPRESSION_ADDR 定义为0x100000. 在memory.h中*/

/* 因为core.img有 32K限制,对core.img进行压缩是非常有必要的 解压后的核心最开始放在0x00100000处,esi寄存器的值就是核心代码的地址 */

movl movl movl

$GRUB_MEMORY_MACHINE_DECOMPRESSION_ADDR, íi $decompressor_end, %esi $LOCAL(decompressor_end), %esi

#ifdef __APPLE__ #else #endif

pushl íi movl leal

LOCAL (uncompressed_size), ìx (íi, ìx), ?x

/* Don't remove this push: it's an argument. */ push ìx call

_LzmaDecodeA

pop ìx

/* _LzmaDecodeA clears DF, so no need to run cld */ popl

%esi

#endif

7.3.2.1.4. Step3) 跳转核心代码

在上面的指令中:

esi寄存器的值就是核心代码的地址:0x00100000 Edx:启动设备 movl movl movl movl

LOCAL(boot_dev), íx $prot_to_real, íi $real_to_prot, ìx $LOCAL(realidt), êx

jmp *%esi

Edi:从保护模式进入实模式函数的地址 Ecx:从实模式进入保护模式函数的地址 eax:实模式中断描述符表的地址

7.3.2.2. 核心代码Startup

这部分代码位于grub-core/kern/i386/pc/startup.S

/*

* 这段代码Startup.S必须加载到0x00100000. */

/* 调用 grub_main进入grub的主函数*/ jmp EXT_C(grub_main)

/* initialize the stack */

movl $GRUB_MEMORY_MACHINE_PROT_STACK, %esp

7.3.3. GRUB主函数即GRUB主要功能

到这来我们已经来到了grub的核心代码部分,这部分代码位于grub-core/kern/main.c

正式开始进入C语言工作,在main函数之后,GRUB2主要工作正式展开,其工作大致如下 - 是grub的模块化框架的初始

- 各种命令的注册 - 各种模块的加载

- 读取/boot/grub2/grub.cfg,显示启动菜单 - 根据菜单配置加载linux kernel

有关怎么配置/etc/grub.d脚本与/etc/default/grub下变量,怎么运行

grub2-mkconfig命令等,已经有很多资料可以查阅,在这里不作描述,有兴趣的读者可以自行研究。

7.3.4. 加载Linux Kernel

我们看看centos7中被加载的内核条目的配置(/boot/grub2/grub.cfg):

menuentry 'CentOS Linux (3.10.0-327.22.2.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class

load_video set gfxpayload=keep insmod gzio insmod part_msdos insmod xfs

set root='hd0,msdos1'

if [ x$feature_platform_search_hint = xy ]; then search else

search --no-floppy --fs-uuid --set=root 149ea7b1-0a88-47aa-ad0a-446b224dd84c fi linux16

/vmlinuz-3.10.0-327.22.2.el7.x86_64

root=/dev/mapper/centos-root

ro

crashkernel=auto

rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet LANG=zh_CN.UTF-8 initrd16 /initramfs-3.10.0-327.22.2.el7.x86_64.img }

--no-floppy

--fs-uuid

--set=root

--hint-bios=hd0,msdos1

--hint-efi=hd0,msdos1

--hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' 149ea7b1-0a88-47aa-ad0a-446b224dd84c

os

--unrestricted

$menuentry_id_option

'gnulinux-3.10.0-327.el7.x86_64-advanced-a09bce3c-411c-40f4-9487-dcfbe471f022' {

该菜单我们只关心最后两项

linux16 /vmlinuz-3.10.0-327.22.2.el7.x86_64

initrd16 /initramfs-3.10.0-327.22.2.el7.x86_64.img

他们的意思GRUB2会用linux16加载Linux内核vmlinuz-3.10.0-327.22.2.el7.x86_64 然后会用initrd16加载initramfs-3.10.0-327.22.2.el7.x86_64.img

注意,后者被称为:initial RAM disk: The initial RAM disk (initrd) is an initial root file system that is mounted prior to when the real root file system is available.

可以参考:http://www.ibm.com/developerworks/library/l-initrd/index.html

加载的这两个文件都在/boot目录下,可以用file命令查看他们类型

[root@controller boot]# file vmlinuz-3.10.0-327.22.2.el7.x86_64 vmlinuz-3.10.0-327.22.2.el7.x86_64:

Linux

kernel

x86

boot

executable

bzImage,

version

3.10.0-327.22.2.el7.x86_64 (builder@kbuilder.dev.centos.org) #1, RO-rootFS, swap_dev 0x4, Normal VGA [root@controller boot]# file initramfs-3.10.0-327.22.2.el7.x86_64.img

initramfs-3.10.0-327.22.2.el7.x86_64.img: ASCII cpio archive (SVR4 with no CRC)

可以知道内核的类型为bzImage.

可以肯定的是,这个时候GRUB已经有了文件系统(CentOS7的XFS)驱动,不会去调用BIOS INT13去加载这两个文件,而是直接在XFS文件系统/boot目录下加载。

在接下来的讲述之前,看看一个内存的结构

(下面是现代bzImage类型 kernel(version >= 2.02)结构)

~ ~ | Protected-mode kernel | 100000 +------------------------+ | I/O memory hole | 0A0000 +------------------------+ | Reserved for BIOS | Leave as much as possible unused ~ ~ | Command line | (Can also be below the X+10000 mark) X+10000 +------------------------+ | Stack/heap | For use by the kernel real-mode code. X+08000 +------------------------+ | Kernel setup | The kernel real-mode code. | Kernel boot sector | The kernel legacy boot sector. X +------------------------+ | Boot loader | <- Boot sector entry point 0000:7C00 001000 +------------------------+ | Reserved for MBR/BIOS | 000800 +------------------------+ | Typically used by MBR | 000600 +------------------------+ | BIOS use only | 000000 +------------------------+

7.3.4.1. Step1 linux16

/*

* 相关代码在grub-core/loader/i386/pc/linux.c

* 当处理菜单选项”linux16 /vmlinuz-3.10.0-327.22.2.el7.x86_64 ...”时,下面方法会被调用 *

* 在这里只讲述关键代码

* 要理解这部分代码只能去读《THE LINUX/x86 BOOT PROTOCOL》: * https://www.kernel.org/doc/Documentation/x86/boot.txt */

grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), { ......

/* 打开内核文件

The first step in loading a Linux kernel should be to load the real-mode code (boot sector and setup code) and then examine the following header at offset 0x01f1. The real-mode code can total up to 32K, although the boot loader may choose to load only the first two sectors (1K) and then examine the bootup sector size. */

int argc, char *argv[])

file = grub_file_open (argv[0]); ......

/*

* 把文件vmlinuz-3.10.0-327.22.2.el7.x86_64头部读入内存structure1h * 该结构在include/grub/i386/linux.h定义:linux_kernel_header * 里面包含一些内核基本信息,在后面加载内核的时候需要用到

* 该头部与https://www.kernel.org/doc/Documentation/x86/boot.txt一致 */

if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) { ...... goto fail;

} ......

/*

*计算实模式代码地址,该地址一般0x90000 */

grub_linux_real_target = grub_find_real_target (); ......

/*

*计算需要载入linux内核大小,包含两个部分,分别载入: * 是模式大小,在内核头部定义

* 保护模式大小,内核文件大小在减去实模式大小,在减去1个扇区0x200 512Byte */

real_size = setup_sects<< GRUB_DISK_SECTOR_BITS; grub_linux16_prot_size = grub_file_size (file) ......

/*

*读取实模式(Real_mode)代码到内存0x90000地址

* Unfortunately, under the following circumstances the 0x90000 memory segment has to be used:

- When loading a zImage kernel ((loadflags & 0x01) == 0). - When loading a 2.01 or earlier boot protocol kernel. -> For the 2.00 and 2.01 boot protocols, the real-mode code can be loaded at another address, but it is internally relocated to 0x90000. For the \ real-mode code must be loaded at 0x90000.

When loading at 0x90000, avoid using memory above 0x9a000. */

- real_size - GRUB_DISK_SECTOR_SIZE;

本文来源:https://www.bwwdw.com/article/vml6.html

Top