天天看点

深入理解 GNU GRUB - 02 boot.S 2.1 相关BIOS例程

转载注明出处(cppgp: http://blog.csdn.net/cppgp )

2. boot.S: GRUB引导第一步

boot.S位于目录boot/i386/pc/。这部分指令被加载到0x7C00~0x7DFF。主要工作包括:配置寄存器;设置堆栈;检测引导盘;检测引导盘读取模式;读取另一扇区指令。这个过程用到几个BIOS例程,并且对主引导记录(MBR, Master Boot Record)结构有很大的依赖。因此boot.S我们分作四步进行详细描述:

1)    相关BIOS例程

2)    主引导记录MBR结构

3)    boot.S代码结构

4)    boot.S详细注释

在本节最后,我们实现一个简单的boot.S,这个boot.S仅仅通过BIOS例程,向终端输出文本。

2.1 相关BIOS例程

boot.S用到五个磁盘相关的BIOS例程。分别是:探测磁盘扩展读支持、LBA方式读、CHS参数获取、CHS方式读、磁盘复位。磁盘读取出现LBA/CHS两种模式(其实LBA又分为28-bit、48-bit、64-bit三种),是因为在磁盘的发展历史上,由于对磁盘容量的错误估量,导致容量屏障的出现,而不得不进行接口更改、扩展。历史上出现的容量屏障主要有四次:

1)    504MiB限制。也称作物理CHS寻址限制。早期的IDE/ATA磁盘接口规定柱面(C=Cylinder)、磁头(H=Head)、扇区(S=Sector)的位宽分别为C/H/S=16/4/8;而BIOS INT 13 H/02H读调用接口规定C/H/S=10/8/6。两者结合取位宽较少者,磁盘寻址参数C/H/S=10/4/6。扇区的起始编号是1而不是0,每扇区数据512字节,因此可寻址容量为210*24*(26-1)*512=528482304bytes=504MiB。

2)    7.88GiB限制。也称作逻辑CHS寻址限制。从1) 可以看出,BIOS INT 13 H/02H接口磁头H有4位是空闲的,因此逻辑上可以扩展磁头H为8位(而实际上连接到IDE/ATA时,磁头H有4位映射到柱面C,或者映射到柱面C和扇区S)。在BIOS接口上看,现在C/H/S寻址范围扩展到210*28*(26-1)*512=8455716864bytes=7.88GiB。

3)    128GiB限制。也称作LBA-28bits限制。在 2) 中,BISO接口的CHS寻址已经到了极限,无法再扩展了,而IDE/ATA是28 位(IDE/ATA内部C/H/S=16:4:8共28位)的,理论寻址容量为228*512=128GB,因此出现了LBA (Logical Block Addressing) 寻址。LBA是一个一维地址,从0~2N-1,其中N是地址宽度,在这里是28,而LBA到CHS的转换由BIOS和磁盘完成。28位LBA寻址容量为228*512=128GiB。

4)    2TiB限制。为了提供更大范围的寻址,Western Digital和Phoenix Technologies制定了EDD (BIOS Enhanced Disk Drive Services) 标准。它使用64位LBA寻址,同时也支持48位和28位寻址。48位LBA寻址容量为128PiB,而64位LBA寻址容量更是高达8ZiB,无论48位LBA或者64位LBA,在当前或可以预见的将来应该是足够的。但是历史悠久的MBR中保存有磁盘分区表DPT,而分区表中分区绝对起始扇区和分区总扇区数都是32位的,因此对于传统分区的磁盘,最大寻址范围由这两个32位值决定,大小为232*512=2TiB。为了解决该问题,引进了GPT (GUID Partition Table) 和EFI (Extensible Firmware Interface) 技术,本文不对其做详细描述,有兴趣的读者可以Google相关主题。

在磁盘容量的计算上,软件按照1024进行单位换算,而厂商按照1000进行单位换算,因此上述的504MiB限制、7.85GiB限制、128GiB限制又称为522MB限制、8.46GB限制、137GB限制。

除了上述限制,历史上还出现过一些软件导致的容量屏障,例如一些BIOS BUG或文件系统体系导致的容量屏障,本文 不再对其做进一步描述,有兴趣可以Google相关细节。BIOS例程及容量屏障描述参考如下网络资源:

http://en.wikipedia.org/wiki/INT_13H

http://en.wikipedia.org/wiki/INT_10H

http://www.pcguide.com/ref/hdd/bios/index.htm

2.1.1磁盘扩展探测: INT 13H, AH=41H

检测磁盘扩展读(LBA/CHS)支持情况。详细描述如下。

参数:

寄存器        描述

AH=0x41    扩展检测函数序号

DL        驱动器编号(第一块硬盘为0x80,第二块为0x81,依次类推)

BX        0x55AA

结果:

寄存器        描述

CF        支持清零,不支持置1

AH        错误码或者主版本号

BX        0x55AA

CX        接口支持掩码

        1 – 使用打包结构体存取设备

        2 – 驱动器加锁和弹出

        4 – 支持增强型磁盘驱动器(EDD)

使用AT&T语法实现这个调用的例子如下(默认使用0x80作为驱动器):

boot_driver:

.byte 0x80

movb boot_driver, %dl

movb $0x41, %ah

movw $0x55aa, %bx

int $0x13

movb boot_driver, %dl

jc chs_mode

cmpw $0xaa55, %bx

jne chs_mode

andw $1, %cx

lba_mode:

// Do LBA operation

Jmp end

chs_mode:

// Do CHS operation

end:

// Do Normal Works

调用结束后,如果CF置1、或者BX不等于0xAA55、或者CX不等于1,都表示不支持LBA,因此进入CHS处理。注意,BIOS调用可能更改掉DL寄存器值,因此需要重置。

2.1.2 LBA模式读: INT 13H, AH=42H

LBA模式的读采用打包的数据结构作为参数。

参数:

寄存器        描述

AH=42H    扩展读函数序号

DL        驱动器编号(第一块硬盘为0x80,第二块为0x81,依次类推)

DS:SI        segment:offset指针,指向磁盘地址包DAP (Disk Address Packet)

DAP结构体的格式描述如下:

偏移量        大小        描述

00H        1 Byte        DAP大小=16=0x10

01H        1 Byte        未用,必须置0

02H~03H    2 Bytes        需要读的扇区数

(有些BIOS限制不能超过127扇区)

04H~07H    4 Bytes        segment:offset指针,指向内存缓冲区,读取到的

扇区内容放置在该缓冲区

08H~0FH    8 Bytes        需要读的连续扇区的起始扇区编号

(第一个扇区的编号是0)

结果:

寄存器        描述

CF        失败置1,成功清零

AH        返回码

使用AT&T语法实现这个调用的例子如下(假定我们使用0x80作为驱动器,从第2个扇区开始连续读取4个扇区):

boot_driver:

.byte 0x80

sector_start:

.long 2, 0

sector_num:

.word 4

dap:

/* reserved 16 bytes to hold dap, or disk address packet */

. = dap + 0x10

read_buffer:

/* reserved 512*4bytes to save data */

. = read_buffer + 512*4

movb boot_driver, %dl

movw $dap, %si

xorw %ax, %ax

movw %ax, %ds

movw %ax, 4(%si)

movb $0x0010, (%si)

movw sector_num, %ax

movw %ax, 2(%si)

movw $read_buffer, 6(%si)

movl sector_start, %eax

movl %eax, 8(%si)

movl sector_start+4, %eax

movl %eax, 12(%si)

movb $0x42, %ah

int $0x13

jc lba_fail

// Do LBA-read success operations

Jmp end

lba_fail:

// Do LBA-read fail operations

end:

// Do Normal Works

调用结束后,通过检测CF来判断是否成功。需要注意字节序的问题,80x86采用小端序。

2.1.3 CHS参数获取: INT 13H, AH=08H

获取驱动器CHS参数。如下。

参数:

寄存器        描述

AH=0x08    驱动器CHS参数读取函数序号

DL        驱动器编号(第一块硬盘为0x80,第二块为0x81,依次类推)

BX        0x55AA

结果:

寄存器        描述

CF        成功清零,失败置1

AH        返回码

DL        驱动器号

DH        逻辑磁头最大索引值(number_of-1因为索引从0开始)

CX        逻辑柱面索引最大值和逻辑扇区数

        逻辑柱面最大索引=number_of-,因为索引从0开始

        逻辑扇区数=number_of,因为索引从1开始

柱面占10位,CH表示柱面低8位,CL高2位表示柱面9~10位

扇区占6位,CL低6位表示扇区

使用AT&T语法实现这个调用的例子很简单:

boot_driver:

.byte 0x80

movb boot_driver, %dl

movb $0x08, %ah

int $0x13

jc chs_para_fail

// Do CHS-para success operations

jmp end

chs_para_fail:

// Do CHS-para fail operation

end:

// Do Normal Works

调用结束后,通过检测CF来判断参数获取调用是否成功。

2.1.4 CHS模式读: INT 13H, AH=02H

CHS模式读扇区。需要注意缓冲区没有超出该段寻址范围。

参数:

寄存器        描述

AH=0x02    CHS模式读扇区函数序号

AL        需读取扇区数

CX        柱面和扇区,柱面占10位,扇区占6位

CH表示柱面低8位,CL高2位表示柱面9~10位

CL低6位表示扇区(从1~63)

DH        磁头

DL        驱动器编号

第一软盘为0x00,第二软盘为0x01,依次类推

第一硬盘为0x80,第二硬盘为0x81,依次类推

ES:BX        segment:offset缓冲区地址指针

结果:

寄存器        描述

CF        成功清零,失败置1

AH        返回码

AL        实际读取的扇区数

CHS读存在7.88GiB容量限制,现在几乎没有硬盘使用这种模式读,并且也很少有人使用软盘(你还有软驱吗?),因此CHS读扇区只是作为残留机制存在,很少用到。

使用AT&T语法实现这个调用的例子如下,因为要用到移位等,汇编代码看起来稍微有点麻烦。这里我们依然假定使用0x80作为驱动器,以C/H/S=0/0/1开始连续读取4个扇区。另外,需要用到2.1.3描述的CHS参数获取的结果。

boot_driver:

.byte 0x80

cylinder_start:

.word 0

head_start:

.byte 0

sector_start:

.byte 1

sector_num:

.word 4

chs_sectors:

.byte 0

chs_heads:

.word 0

chs_cylinders:

.word 0

read_buffer:

/* reserved 512*4bytes to save data */

. = read_buffer + 512*4

movb boot_driver, %dl

movb $0x08, %ah

int $0x13

jc chs_para_fail

/* saved the CHS parameters */

/*

* dh : numbers_of_heads-1, 8-bits

* cx : numbers_of_cylinders-1, number_of_sectors

*/

movzbl %dh, %eax

incw %ax

movw %ax, chs_head

movzbw %dl, %dx

shlw $2, %dx

movb %ch, %al

movb %dh, %ah

incw %ax

movw %ax, chs_cylinders

movzbw %dl, %ax

shrw $2, %ax

movw %ax, chs_sectors

/* CHS read */

movb sectors_start, %al

movb %al, %cl

movw cylinder_start, %ax

movb %al, %ch

xorb %al, %al

shrw $2, %ax

orb %al, %cl

movb head_start, %al

movb %al, %dh

movb boot_drive, %dl

xorw %ax, %ax

movw %ax, %es

movw $read_buffer, %bx

movb sector_num, %al

movb $2, %ah

int $0x13

jc chs_read_fail

// Do CHS-read success operations

jmp end

chs_para_fail:

// Do CHS-para fail operations

jmp end

chs_read_fail:

// Do CHS-read fail operations

jmp end

end:

// Do Normal Works

CHS读结束后,通过CF判断是否成功。

2.1.5 复位磁盘驱动器: INT 13H, AH=00H

复位磁盘驱动器。如下。

参数:

寄存器        描述

AH=0x00    驱动器复位函数序号

DL        驱动器编号(第一块硬盘为0x80,第二块为0x81,依次类推)

结果:

寄存器        描述

CF        成功清零,失败置1

AH        状态

使用AT&T语法实现这个调用的例子非常简单:

boot_driver:

.byte 0x80

xorb %ah, %ah

movb boot_driver, %dl

int $0x13

jc reset_fail

// Do Disk Reset success operations

jmp end

reset_fail:

// Do Disk Reset fail operations

end:

// Do Normal Works

通过CF判断复位是否成功。软盘的读取和CHS模式的读

2.1.6 字符输出: INT 10H, AH=0EH

BIOS使用INT 10H图形服务,可以用来设置图形模式、输出字符或字符串、以及基本图形(图形模式下读取/写入像素)。

AH=0EH提供用来向终端输出单一字符。如下。

参数:

寄存器        描述

AH=0x0E    电传输出函数序号

AL=Character    需要输出的字符

BL=Color    输出字符颜色,只对图形终端有效

结果:

无返回

使用AT&T语法实现这个调用的例子如下:

char_value:

.byte ‘A’

char_color:

.byte 0

movb char_value, %al

movb char_color, %bl

movb $0x0E, %ah

int $0x10

INT 10H, AH=0EH调用无返回值。