深入理解 GNU GRUB - 02 boot.S 2.1 相关BIOS例程
目录
1 boot.S: GRUB引导第一步
boot.S位于目录boot/i386/pc/。这部分指令被加载到0x7C00~0x7DFF。主要工作包括:配置寄存器;设置堆栈;检测引导盘;检测引导盘读取模式;读取另一扇区指令。这个过程用到几个BIOS例程,并且对主引导记录(MBR, Master Boot Record)结构有很大的依赖。因此boot.S我们分作四步进行详细描述:
- 相关BIOS例程
- 主引导记录MBR结构
- boot.S代码结构
- boot.S详细注释
在本节最后,我们实现一个简单的boot.S,这个boot.S仅仅通过BIOS例程,向终端输出文本。
1.1 相关BIOS例程
boot.S用到五个磁盘相关的BIOS例程。分别是:探测磁盘扩展读支持、LBA方式读、CHS参数获取、CHS方式读、磁盘复位。磁盘读取出现LBA/CHS两种模式(其实LBA又分为28-bit、48-bit、64-bit三种),是因为在磁盘的发展历史上,由于对磁盘容量的错误估量,导致容量屏障的出现,而不得不进行接口更改、扩展。历史上出现的容量屏障主要有四次:
- 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。
- 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。
- 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。
- 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
1.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寄存器值,因此需要重置。
1.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采用小端序。
1.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来判断参数获取调用是否成功。
1.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判断是否成功。
1.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模式的读
1.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调用无返回值。