Yanyg - SAN Software Engineer

深入理解 GNU GRUB - 02 boot.S 2.4 boot.S详细注释

本文最初发布于CSDN:https://blog.csdn.net/cppgp/article/details/6361020

boot.S位于grub-1.98/boot/i386/pc/目录,采用AT&T汇编语法编写。

/* -*-Asm-*- */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 1999,2000,2001,2002,2005,2006,2007,2008,2009  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * cppgp 注释
 *
 * 转载请注明原作者
 *
 * 日期: 2011-04-22
 *
 * Email
 *    [email protected],
 *    [email protected]
 *
 * GRUB version:
 *    gnu grub-1.98
 */
/*
 * boot.S
 *
 * boot.S生成boot.img, 共512字节
 *
 * 安装程序根据实际情况, 改写kernel_sector所在LBA地址,
 * kernel_sector占用8字节, 首先是低4字节,然后是高4字节,
 * 它指明diskboot.img所在绝对扇区位置.
 *
 * 安装在硬盘上时,DPT部分(0x1BE~0x1FD)保留不变,
 *
 * 安装在软盘上时, DPT部分(0x1BE~0x1FD)是软盘复位和
 * 扇区探测代码. 扇区末尾两字节
 *
 * 扇区最后两字节 写入0xAA55 (小端表示, 0x1FE位置为0x55,
 * 0x1FF位置为0xAA).
 *
 * boot.img开机时加载到0x7C00~0x7DFF, 并以CS:IP=0x0000:0x7C00
 * 跳转执行, 它将加载diskboot.img到内存0x8000位置,并以
 * CS:IP=0x0000:0x8000跳转执行.
 *
 * boot.img保存磁盘参数和模式到BPB(BIOS parameter block)块.
 * diskboot.img和kernel.img都用到这些参数.
 *
 * boot.img设置堆栈和一些寄存器, diskboot.img和kernel.img都
 * 使用到这些设置. 包括:
 *  SP=0x2000 --> stack
 *  DL=boot-disk-driver
 *  SI=DAP --> disk address packet, -1(%si) is mode of disk read
 *  DS=SS=0 --> data segment and stack segment
 */
/*
  *
  * 关于宏LOCAL
  *
  * 所在文件:   grub-1.98/include/grub/symbol.h
  *
  * 描述
  *  #define LOCAL(sym) L_ ## sym
  *      ##在C语言中其粘贴作用
  *      LOCAL(sym)在sym符号前附加L_
  *      它以一种更易读的方式暗示这是一个局部(本地)符号
  *      例如, LOCAL(after_BPB)等价于L_after_BPB
  *
  */

#include <grub/symbol.h>
#include <grub/boot.h>
#include <grub/machine/boot.h>
/*
 *  defines for the code go here
 */
        /* Print message string */
/*
 * MSG宏设置源串指针并调用LOCAL(message)函数
 *    LOCAL(message)函数通过BIOS INT 10H, AH=0EH
 *    向终端输出提示信息
 * 关于BIOS INT 10H, AH=0EH参考2.1.6节
 */
#define MSG(x)  movw $x, %si; call LOCAL(message)
        /* 汇编文件名*/
        .file   "boot.S"
        /* 代码段声明*/
        .text
        /* Tell GAS to generate 16-bit instructions so that this code works
           in real mode. */
        /*
         * 告知编译器生成实模式下工作的16位指令
         */
        .code16
/*
 * .globl指令用来声明外部程序可以访问的标签
 *     程序入口点必须用.globl来声明
 *     _start是GNU链接器的默认入口点
 */
.globl _start, start;
_start:
start:
        /*
         * _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
         */
        /*
         * Beginning of the sector is compatible with the FAT/HPFS BIOS
         * parameter block.
         */
        /*
         *
         * 跳转到LOCAL(after_BPB)
         *  LOCAL(after_BPB)之前空间保留
         *  用来保存磁盘读模式, 以及
         *  LBA模式下的参数DAP结构体空间, 或者
         *  CHS模式下保存柱面/磁头/扇区参数值
         *
         *  BPB保留的空间足够多
         *  目前最多只用到17字节, 分别是
         *    1字节保存磁盘读模式(LBA模式为1,  CHS模式为0)
         *    16字节保存LBA下的DAP参数
         *
         */
        jmp     LOCAL(after_BPB)
        /*
         * nop占用1字节空间, 空指令
         */
        nop     /* do I care about this ??? */
        /*
         * This space is for the BIOS parameter block!!!!  Don't change
         * the first jump, nor start the code anywhere but right after
         * this area.
         */
        /*
         * GRUB_BOOT_MACHINE_BPB_START=0x03
         * BPB参数块开始地址: 0x7C04
         */
        . = _start + GRUB_BOOT_MACHINE_BPB_START
        . = _start + 4
        /* scratch space */
        /*
         * mode保存磁盘读模式
         */
mode:
        .byte   0
        /*
         * disk_address_packet, BIOS LBA读DAP标签
         * sectors/heads/cylinders 保存驱动器C/H/S参数
         *    通过INT 13H, AH=0x08H获取
         * sector_start/head_start/cylinder_start
         *    代码中没有用到, 可能是残留下来的标签吧
         */
disk_address_packet:
sectors:
        .long   0
heads:
        .long   0
cylinders:
        .word   0
sector_start:
        .byte   0
head_start:
        .byte   0
cylinder_start:
        .word   0
        /* more space... */
        /*
         * GRUB_BOOT_MACHINE_BPB_END=0x5A
         * 用来给BPB预留更多空间
         * BPB字节数=0x5A-0x04=0x56=86 Bytes
         */
        . = _start + GRUB_BOOT_MACHINE_BPB_END
        /*
         * End of BIOS parameter block.
         */
/*
 * GRUB_BOOT_MACHINE_KERNEL_ADDR=0x8000
 *  GRUB第二步指令装载后位于这个位置
 */
kernel_address:
        .word   GRUB_BOOT_MACHINE_KERNEL_ADDR
        /*
         * GRUB_BOOT_MACHINE_KERNEL_SECTOR=0x5C
         *   确保到达此处时占用字节数等于0x5C
         *   如果超过编译器会发出抱怨
         *   如果不够0x5C则预留至0x5C
         */
        . = _start + GRUB_BOOT_MACHINE_KERNEL_SECTOR
/*
 * 8字节用来表示LBA绝对扇区地址
 *  boot.S加载位于此处的扇区到0x8000位置
 *  注意两个4字节值的顺序
 *  先是低4字节,再是高4字节
 *  默认值是LBA第1扇区
 *  在安装过程,安装程序会改写成实际的安装扇区
 */
kernel_sector:
        .long   1, 0
        /*
         * GRUB_BOOT_MACHINE_BOOT_DRIVE=0x64
         *  上面的kernel_sector使用0x5C~0x63
         *  此处只是确保没有越界
         *  如果越界编译器会发出警告
         */
        . = _start + GRUB_BOOT_MACHINE_BOOT_DRIVE
/*
 * 磁盘驱动器号
 *  第一块软盘是0x00, 第二块软盘是0x01, 依此类推
 *  第一块硬盘是0x80, 第二块硬盘是0x81, 依此类推
 *  默认设置为0xff
 *  在安装过程会更改为实际的磁盘驱动器
 *  代码中如果探测到这里依然是0xff, 则会当做0x80处理
 */
boot_drive:
        .byte 0xff      /* the disk to load kernel from */
                        /* 0xff means use the boot drive */
/*
 * 0x7C00处的跳转到达这里
 */
LOCAL(after_BPB):
/* general setup */
        /*
         * cli 禁止中断
         */
        cli             /* we're not safe here! */
        /*
         * 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).
         */
         /*
          * 确保地址安排GRUB_BOOT_MACHINE_DRIVE_CHECK=0x66
          * cli指令占用1字节, 现在正好到达0x66
          */
        . = _start + GRUB_BOOT_MACHINE_DRIVE_CHECK
/*
 * BIOS在开机后设置DL为引导磁盘驱动器号
 * 有些BIOS会传递错误的驱动器号
 *   硬盘驱动器号是从0x80开始的
 *   正确的驱动器号和0x80执行testb不会把ZF置0
 *   testb对两个操作数执行逻辑与操作,
 *   并设置正确的SF/ZF/PF等状态位
 *   testb $0x80, %dl等于0表示dl<0x80
 *   如果GRUB安装在硬盘上,这很明显是一个错误的驱动器号
 *   因此会重置为0x80
 *
 */
boot_drive_check:
        /*
         * 安装程序可能会改写jmp
         *  例如,安装在硬盘上时,
         *    可能改写为两个nop
         *    这样就可以解决一些BIOS传递错误驱动器号的BUG
         * 在当前版本中
         * 专门开辟了一字节空间(boot_drive)保存驱动器号
         * 除非系统上接入了128块硬盘,
         * 且使用最后一块硬盘引导(此时驱动器号为0xFF)
         * 否则后面的代码会重置DL为boot_drive中保存的驱动器号
         * 系统上接入128块硬盘且使用最后一块引导的概率非常低
         * 再者,如果真使用0xFF引导, 即使检测到有BUG且改成了0x80,
         * 也解决不了问题--因为实际上引导盘不是0x80而是0xFF
         *
         * 因此,是否改写jmp有些无关紧要.
         * 为了完整性, 我对这个检测的代码逻辑也做了注释
         */
        jmp     1f      /* grub-setup may overwrite this jump */
        /*
         * 这几行代码是针对安装在硬盘上的GRUB的
         * 用来解决某些BIOS无法提供正确驱动器号的BUG
         * testb结果: 如果dl<0x80则操作结果为0, 因此jnz不执行跳转
         * movb重置DL为0x80
         *
         */
        testb   $0x80, %dl
        jnz     1f
        movb    $0x80, %dl
1:
        /*
         * ljmp to the next instruction because some bogus BIOSes
         * jump to 07C0:0000 instead of 0000:7C00.
         */
        /*
         * 某些存在BUG的BIOS,
         * 使用CS:IP=0x07C0:0x0000执行代码
         * 通过ljmp解决这个BUG
         * ljmp segment, offset
         * 其中segment是段寄存器, offset是偏移值
         */
        ljmp    $0, $real_start
real_start:
        /* set up %ds and %ss as offset from 0 */
        /*
         * 数据段/堆栈段寄存器置0
         * 前面通过ljmp, 已经确保数据段寄存器置0了
         */
        xorw    %ax, %ax
        movw    %ax, %ds
        movw    %ax, %ss
        /* set up the REAL stack */
        /*
         * GRUB_BOOT_MACHINE_STACK_SEG=0x2000
         * 设置实模式下的堆栈
         */
        movw    $GRUB_BOOT_MACHINE_STACK_SEG, %sp
        /* 打开中断 */
        sti             /* we're safe again */
        /*
         * 现在CS/DS/SS都是0x0000
         * 并且设置好了实模式堆栈SP=0x2000
         * 下面可以安全的使用pushx/popx等指令了
         */
        /*
         *  Check if we have a forced disk reference here
         */
        /*
         * 如果boot_drive依然为0xff, 则使用BIOS传递的驱动器号
         * 否则使用boot_drive中保存的驱动器号
         * 这样就允许GRUB的第一步和后续不在同一块磁盘也能工作
         *
         */
        movb   boot_drive, %al
        cmpb    $0xff, %al
        je      1f
        movb    %al, %dl
1:
        /* save drive reference first thing! */
        /*
         * 压栈DX,
         * DL中保存有驱动器号
         * BIOS调用都有可能会更改DL寄存器
         */
        pushw   %dx
        /* print a notification message on the screen */
        /*
         * 向终端输出提示信息notification_string
         * notification_string="GRUB"
         * 如果注意,可以看到在启动GRUB引导的Linux时,
         * 屏幕上有GRUB字样一闪而过.
         */
        MSG(notification_string)
        /* set %si to the disk address packet */
        /*
         * 设置SI寄存器
         * 下面的BIOS调用及结果保存都会用到
         * movw将disk_address_packet地址保存到SI
         */
        movw    $disk_address_packet, %si
        /* do not probe LBA if the drive is a floppy */
        /*
         * 探测如果驱动器小于0x80, 不再执行LBA探测,
         *  因为小于0x80为软盘,软盘只支持CHS.
         */
        testb   $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
        jz      LOCAL(chs_mode)
        /* check if LBA is supported */
        /*
         * BIOS INT 13H, AH=41H
         * 检测磁盘扩展读支持情况
         * 详细查看2.1.1节
         */
        movb    $0x41, %ah
        movw    $0x55aa, %bx
        int     $0x13
        /*
         *  %dl may have been clobbered by INT 13, AH=41H.
         *  This happens, for example, with AST BIOS 1.04.
         */
         /*
          * 驱动器DL重置, 因为BIOS调用可能已经更改了它
          */
        popw    %dx
        pushw   %dx
        /* use CHS if fails */
        /*
         * 检测结果, 如果支持LBA,则有:
         *  1. CF清零; 2. BX==AA55H; 3. CX=1
         *
         * 如果BIOS调用失败按照CHS处理
         */
        jc      LOCAL(chs_mode)
        cmpw    $0xaa55, %bx
        jne     LOCAL(chs_mode)
        andw    $1, %cx
        jz      LOCAL(chs_mode)
lba_mode:
/*
 * 初步探测支持LBA扩展读
 * 如果LBA读失败依然进入CHS处理
 */
        /*
         * 如下代码设置LBA读的DAP结构及寄存器
         * 并调用LBA读BIOS例程
         * 如果读失败进入CHS处理
         * 成功则数据被读到了0x7000:0x0000(内存0x700000)位置
         * 跳转到LOCAL(copy_buffer)拷贝至0x8000并跳转执行
         */
         /*
          * BIOS INT 13H, AH=42H
          * LBA读
          * 详细查看2.1.2节
          */
        xorw    %ax, %ax
        movw    %ax, 4(%si)
        // 支持LBA读,保存LBA读到mode地址,LBA读标记为0
        /*
         * 保存硬盘读模式为LBA
         * mode保存1表示支持LBA
         */
        incw    %ax
        /* set the mode to non-zero */
        movb    %al, -1(%si)
        // 设置BIOS调用参数
        /*
         * 以下是填充LBA读的DAP结构
         * 2.1.2节对此有详细描述
         */
        /* the blocks */
        /*
         * 读取扇区数,只读1扇区
         */
        movw    %ax, 2(%si)
        /* the size and the reserved byte */
        /*
         * DAP结构体大小,当前为16=0x10
         * 把需要读取扇区数的高字节置0
         */
        movw    $0x0010, (%si)
        /* the absolute address */
        /*
         * 8字节的LBA绝对扇区地址
         */
        movl    kernel_sector, %ebx
        movl    %ebx, 8(%si)
        movl    kernel_sector + 4, %ebx
        movl    %ebx, 12(%si)
        /* the segment of buffer address */
        /*
         * GRUB_BOOT_MACHINE_BUFFER_SEG=0x7000
         * 缓冲区段地址
         */
        movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si)
/*
 * BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
 *      Call with       %ah = 0x42
 *                      %dl = drive number
 *                      %ds:%si = segment:offset of disk address packet
 *      Return:
 *                      %al = 0x0 on success; err code on failure
 */
        /*
         * 调用INT 13H, AH=42H执行LBA读
         *   缓冲区segment:offset=0x7000:0x0000
         */
        movb    $0x42, %ah
        int     $0x13
        /*
         * LBA读失败跳转到LOCAL(chs_mode)尝试CHS读
         */
        /* LBA read is not supported, so fallback to CHS.  */
        jc      LOCAL(chs_mode)
        /*
         * GRUB_BOOT_MACHINE_BUFFER_SEG=0x7000
         * 设置BX为缓冲区段地址0x7000
         *  跳转执行LOCAL(copy_buffer)
         *  它将搬运0x7000:0x0000处一扇区的数据到0x8000处
         */
        movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
        jmp     LOCAL(copy_buffer)
/*
 * CHS模式读处理
 *  1. BIOS INT 13H, AH=08H获取磁盘CHS参数
 *  2. BIOS INT 13H, AH=02H实现CHS读
 *  3. 可能尝试软盘驱动器复位和CHS读
 */
LOCAL(chs_mode):
        /*
         *  Determine the hard disk geometry from the BIOS!
         *  We do this first, so that LS-120 IDE floppies work correctly.
         */
        /*
         * 获取磁盘CHS参数
         * 有关该调用细节查看2.1.3节
         * 如果成功直接跳转到LOCAL(final_init)
         */
        movb    $8, %ah
        int     $0x13
        jnc     LOCAL(final_init)
        /*
         *  The call failed, so maybe use the floppy probe instead.
         */
        /*
         * GRUB_BOOT_MACHINE_BIOS_HD_FLAG=0x80
         *  检测如果驱动器DL小于0x80,
         *  则尝试软盘复位和读
         *  如果大于等于0x80, 表明硬盘错误,跳转进入LOCAL(hd_probe_error)处理
         */
        testb   $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
        jz      LOCAL(floppy_probe)
        /* Nope, we definitely have a hard disk, and we're screwed. */
        jmp     LOCAL(hd_probe_error)
LOCAL(final_init):
        /* set the mode to zero */
        /*
         * 保存磁盘CHS参数
         *  S索引从1开始,直接保存它即可
         *  柱面C使用寄存器CH表示低8位
         *               使用CL高两位表示高2位
         *  磁头H使用DH
         *  扇区S使用CL低6位
         *  注意,获取到的C/H/S都是最大索引
         *  C/H索引都是从0开始,因此它们的值应该加1保存
         *  有关细节见2.1.3节
         */
        /*
         * movzbl保存dh到al,同时eax其他三字节置0
         * movb将保存0到mode, 表示支持CHS读
         */
        movzbl  %dh, %eax
        movb    %ah, -1(%si)
        /* save number of heads */
        /*
         * 保存磁头参数H到BPB的heads
         * 磁头索引从0开始,递增表示磁盘磁头数
         * SI在前面已经设置成DAP地址了
         * 而CHS和DAP是复用的
         */
        incw    %ax
        movl    %eax, 4(%si)
        /*
         * 移位操作
         * 10位的柱面C保存到AX中
         */
        movzbw  %cl, %dx
        shlw    $2, %dx
        movb    %ch, %al
        movb    %dh, %ah
        /* save number of cylinders */
        /*
         * 柱面索引从0开始,递增表示磁盘柱面数
         */
        incw    %ax
        movw    %ax, 8(%si)
        /*
         * 上面保存柱面C时使用了DX
         * DX曾左移2位,因此:
         * DH低2位保存柱面8~9位
         * DL高6位保存扇区数
         * movzbw让AH为0,AL为DL
         * AL右移两位,正好表示扇区
         * 扇区索引从1开始,因此不需要递增
         */
        movzbw  %dl, %ax
        shrb    $2, %al
        /* save number of sectors */
        /*
         * 保存磁盘扇区数
         */
        movl    %eax, (%si)
        /*
         * 现在磁盘的C/H/S数已保存
         *      下面的读使用它们来判断需要读取的扇区
         *      是否越界(CHS 7.88Gib屏障,参考2.1节)
         *
         *  待读取的扇区采用LBA表示,占用8字节
         *      需要转换成C/H/S表示的方法,
         *      且确保在C/H/S寻址范围之内
         *  转换算法:
         *
         */
setup_sectors:
        /* load logical sector start (top half) */
        /*
         * LBA表示的高4字节地址加载到EAX
         * 如果EAX不为0, 则肯定超过CHS寻址范围
         * 因此直接报告错误
         */
        movl    kernel_sector + 4, %eax
        /*
         * orl没什么作用
         * EAX与自身执行或运算,EAX结果不会有变化
         * 但是会设置标识寄存器EFLAGS的某些位
         * 例如SF/ZF等
         * 如果EAX不等于0,则CHS越界,跳转到LOCAL(geometry_error)
         */
        orl     %eax, %eax
        jnz     LOCAL(geometry_error)
        /* load logical sector start (bottom half) */
        /*
         * LBA表示的低4字节地址
         */
        movl    kernel_sector, %eax
        /* zero %edx */
        /*
         * EDX清零,因为32位的div操作的被除数是EDX:EAX
         */
        xorl    %edx, %edx
        /* divide by number of sectors */
        /*
         * 操作数
         *   被除数: EDX:EAX=0:EAX
         *   除数: (%si) 磁盘扇区数S
         * 结果
         *   商: EAX
         *   余数: EDX
         */
        divl    (%si)
        /* save sector start */
        /*
         * 余数表示起始扇区
         * 保存到CL
         * 因为扇区起始索引为1,需要递增
         * 递增操作见稍后代码
         */
        movb    %dl, %cl
        /*
         * EAX是上次div的结果,
         * 再除以磁头数H,
         * 就可得到起始柱面Cylinder
         */
        /*
         * 清零DX
         * 在上面的divl操作中,
         *   除数是扇区数(6位)
         *   因此余数不会超过一字节
         *   清零DX则EDX全部为0
         */
        xorw    %dx, %dx        /* zero %edx */
        /*
         * 操作数
         *   被除数: EDX:EAX=0:EAX
         *   除数: 4(%si) 磁盘磁头数H
         * 结果
         *   商: EAX
         *   余数: EDX
         */
        divl    4(%si)          /* divide by number of heads */
        /*
         * 现在,
         *  AX中保存有起始柱面C
         *  DX中保存有起始磁头S
         */
        /* do we need too many cylinders? */
        /*
         * 判断起始柱面有否越界
         * 如果越界跳转到LOCAL(geometry_error)
         */
        cmpw    8(%si), %ax
        jge     LOCAL(geometry_error)
        /*
         * 注意,
         *  众所周知,余数不可能超过除数
         *  上面的除法中除数是磁盘磁头数H
         *  因此起始磁头不会越界,不需要检测是否越界
         */
        /* normalize sector start (1-based) */
        /*
         * 扇区索引从1开始,
         * 因此起始扇区需要加1
         */
        incb    %cl
        /*
         * 现在
         *  AX=起始柱面C
         *  DX=起始磁头H
         *  CL=起始扇区S
         *
         * 下面通过移位运算,完成
         *    CHS读的寄存器设置
         * 详细查看2.1.4节
         *
         */
        /* low bits of cylinder start */
        /*
         * 保存起始柱面C的0~7位到CH
         */
        movb    %al, %ch
        /* high bits of cylinder start */
        /*
         * 保存起始柱面C的8~9位到CL高2位
         * CL中低6位已经包含合法的起始扇区
         * orb或操作起始柱面8~9位和起始扇区0~5位
         */
        xorb    %al, %al
        shrw    $2, %ax
        orb     %al, %cl
        /* save head start */
        /*
         * divl操作后,
         *  Dl存有起始磁头,保存到AL中
         */
        movb    %dl, %al
        /* restore %dl */
        /*
         * DL值已被更改,还原磁盘驱动器值
         */
        popw    %dx
        /* head start */
        /*
         * 保存起始磁头到AH中
         */
        movb    %al, %dh
/*
 * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
 *      Call with       %ah = 0x2
 *                      %al = number of sectors
 *                      %ch = cylinder
 *                      %cl = sector (bits 6-7 are high bits of "cylinder")
 *                      %dh = head
 *                      %dl = drive (0x80 for hard disk, 0x0 for floppy disk)
 *                      %es:%bx = segment:offset of buffer
 *      Return:
 *                      %al = 0x0 on success; err code on failure
 */
        /*
         * GRUB_BOOT_MACHINE_BUFFER_SEG=0x7000
         *  缓冲区段地址
         */
        movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
        movw    %bx, %es        /* load %es segment with disk buffer */
        /*
         * 调用INT 13H, AH=02H执行CHS读
         *   缓冲区segment:offset=0x7000:0x0000
         */
        xorw    %bx, %bx        /* %bx = 0, put it at 0 in the segment */
        movw    $0x0201, %ax    /* function 2 */
        int     $0x13
        /*
         * 检测CHS读是否成功
         *  CHS读成功CF清零,失败则置1
         */
        jc      LOCAL(read_error)
        /*
         * 设置BX为缓冲区段地址
         */
        movw    %es, %bx
LOCAL(copy_buffer):
        /*
         * We need to save %cx and %si because the startup code in
         * kernel uses them without initializing them.
         */
        /*
         * 无论CHS或LBA,一扇区的内容都被放置在
         *  segment:offset=%bx:0处
         * 这里将数据搬运到0x0000:0x8000并跳转执行
         *
         */
        /*
         * 寄存器压栈, 因为
         *   数据搬运操作将使用这些寄存器
         *   而GRUB第二步将假定这些寄存器包含正确值
         *   并直接使用
         */
        pusha
        pushw   %ds
        /*
         * movsw指令:
         *  搬运两字节数据
         *  使用segment:offset=DS:SI作为源操作数
         *  使用segment:offset=ES:DI作为目的操作数
         *
         * rep指令:
         *  使用CX寄存器作为循环标记,
         *  循环执行紧跟其后的指令CX次
         */
        /*
         * GRUB_BOOT_MACHINE_KERNEL_ADDR=0x8000
         * 设置循环次数为100次
         * 设置DS:SI=0x7000:0x0000即CHS/LBA读的缓冲区
         * 设置ES:DI=0x0000:0x8000, 数据将搬运到这里
         */
        movw    $0x100, %cx
        movw    %bx, %ds
        xorw    %si, %si
        movw    $GRUB_BOOT_MACHINE_KERNEL_ADDR, %di
        movw    %si, %es
        /*
         * DF清零
         *  movsw自动增加源和目的操作数值
         *  清零后每次执行movsw, SI和DI都加2
         */
        cld
        /*
         * 循环搬运
         *  把0x7000:0x0000处的512字节搬运到0x0000:0x8000处
         */
        rep
        movsw
        /*
         * 还原寄存器
         */
        popw    %ds
        popa
        /* boot kernel */
        /*
         * 跳转到0x0000:0x8000执行
         * 这里存放的是刚才得到的指令数据
         * 一次成功的GRUB引导都会执行到这里
         * 此后将进入第二步执行
         * 这里设置的堆栈和保存的BPB数据
         *    在后来的第二步(diskboot.S)及第三步(startup.S)中还会用到
         */
        jmp     *(kernel_address)
/* END OF MAIN LOOP */
/*
 * BIOS Geometry translation error (past the end of the disk geometry!).
 */
 /*
  * 以下是几个错误处理函数
  * 向终端输出错误提示信息并等待手动关机或重启
  */

/*
 * 向终端输出错误提示"Geom"并跳转到LOCAL(general_error)
 * 磁盘物理结构错误(一般是C/H/S参数越界导致的错误)
 */
LOCAL(geometry_error):
        MSG(geometry_error_string)
        jmp     LOCAL(general_error)
/*
 * Disk probe failure.
 */
/*
 * 向终端输出错误提示"Hard Disk"并跳转到LOCAL(general_error)
 * 不支持LBA或者LBA读失败的硬盘,在获取CHS参数时错误
 */
LOCAL(hd_probe_error):
        MSG(hd_probe_error_string)
        jmp     LOCAL(general_error)
/*
 * Read error on the disk.
 */
/*
 * 向终端输出错误提示"Hard Disk"并跳转到LOCAL(general_error)
 * CHS读失败错误
 */
LOCAL(read_error):
        MSG(read_error_string)
/*
 * 向终端输出错误提示"Error/r/n"
 */
LOCAL(general_error):
        MSG(general_error_string)
/* go here when you need to stop the machine hard after an error condition */
        /* tell the BIOS a boot failure, which may result in no effect */
        /*
         * 向BIOS报告引导失败
         */
        int     $0x18
/*
 * BIOS对引导失败没有响应到达这里
 * 此时用户需要手动关机/重启
 */
LOCAL(stop):
        jmp     LOCAL(stop)
/*
 * 错误提示字符串
 */
notification_string:    .asciz "GRUB "
geometry_error_string:  .asciz "Geom"
hd_probe_error_string:  .asciz "Hard Disk"
read_error_string:      .asciz "Read"
general_error_string:   .asciz " Error/r/n"
/*
 * message: write the string pointed to by %si
 *
 *   WARNING: trashes %si, %ax, and %bx
 */
/*
 * 错误提示函数
 * 使用BIOS INT 10H, AH=0EH调用向终端输出错误提示字符串
 * 关于该中断细节查看2.1.6节
 */
        /*
         * Use BIOS "int 10H Function 0Eh" to write character in teletype mode
         *      %ah = 0xe       %al = character
         *      %bh = page      %bl = foreground color (graphics modes)
         */
1:
        movw    $0x0001, %bx
        movb    $0xe, %ah
        int     $0x10           /* display a byte */
LOCAL(message):
        /*
         * lodsb加载DS:SI指向的一字节数据到AL
         * 加载后自动更新SI值(指向下一字符)
         * 如果字符为'/0'返回,
         * 否则调用BIOS中断输出该字符
         */
        lodsb
        cmpb    $0, %al
        jne     1b      /* if not end of string, jmp to display */
        ret
        /*
         *  Windows NT breaks compatibility by embedding a magic
         *  number here.
         */
        /*
         * WindowsNT在这里插入一个幻数
         * 在Linux下, 把这个位置的6个字节清零
         * 不会导致任何问题
         * 这个测试详见2.3节末尾部分
         */
        . = _start + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC
nt_magic:
        .long 0
        .word 0
        /*
         *  This is where an MBR would go if on a hard disk.  The code
         *  here isn't even referenced unless we're on a floppy.  Kinda
         *  sneaky, huh?
         */
        /*
         * GRUB_BOOT_MACHINE_PART_START=0x1BE
         * 从这里开始是硬盘MBR的DPT区
         * 如果是安装在软盘,则这里存放
         * 软盘驱动器复位和CHS读取的指令
         */
        . = _start + GRUB_BOOT_MACHINE_PART_START
part_start:
/*
 * 解决软盘获取CHS参数错误的问题
 *
 * 一系列可能的软盘扇区参数. 即:
 *  软盘可能的最大扇区数
 *  当软盘的CHS参数获取调用失败以后,
 *  设置柱面和磁头均为0,然后
 *  遍历尝试读取可能的最大扇区
 *  成功以后, 设置CHS参数为C/H/S=0/0/CL
 *  并进入LOCAL(final_init)并执行正常的CHS流程
 *
 * 例如, 假设第一次复位/读测试成功,则
 *    软盘C/H/S=0/0/36. 可寻址范围36*512=18KiB
 */
probe_values:
        .byte   36, 18, 15, 9, 0
LOCAL(floppy_probe):
/*
 *  Perform floppy probe.
 */
        /*
         * LOCAL(probe_loop)循环中总是先递增SI
         * 然后取值,因此设置SI初始值为$probe_values-1
         * 这样可以防止错过probe_values中的第一个数据
         */
        movw    $probe_values - 1, %si
LOCAL(probe_loop):
        /* reset floppy controller INT 13h AH=0 */
        /*
         * 复位软盘驱动器: BIOS INT 13H, AH=00H
        */
        xorw    %ax, %ax
        int     $0x13
        incw    %si
        movb    (%si), %cl
        /* if number of sectors is 0, display error and die */
        /*
         * 起始扇区是否为0
         * 如果为0则提示错误结束
         *
         * LOCAL(probe_loop)将依次遍历probe_values中设定的值,
         * 直到出现0则结束遍历, 并输出错误提示
         */
        cmpb    $0, %cl
        jne     1f
/*
 * Floppy disk probe failure.
 */
/*
 * 终端输出"Floppy"并跳转到LOCAL(general_error)
 */
        MSG(fd_probe_error_string)
        jmp     LOCAL(general_error)
/* "Floppy" */
fd_probe_error_string:  .asciz "Floppy"
1:
        /* perform read */
        /*
         * BIOS INT 13H, AH=02H读(和硬盘CHS读使用同一BIOS中断)
         *  设置CH为0, CL为起始扇区,柱面C为0
         *  设置CL中为probe_values中的其中一个
         *  设置DH为0, 磁头H为0
         *  设置AL为1, 只读取一扇区
         *  设置AH为2, BIOS INT 13H CHS读函数序号
         *  设置缓冲区segment:offset=ES:BX=0x0000:0x7000
         * INT 13H调用将读取软盘0柱面0磁头CL扇区
         *    如果读成功,跳转到LOCAL(final_init)
         *    将再次执行CHS读
         *    因此这里的缓冲区数据并不会被处理
         */
        movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
        movw    $0x201, %ax
        movb    $0, %ch
        movb    $0, %dh
        int     $0x13
        /* if error, jump to "LOCAL(probe_loop)" */
        /*
         * 失败跳转回LOCAL(probe_loop)继续尝试
         * 成功跳转到LOCAL(final_init)
         * 下次讲尝试读取probe_values设定的另一扇区
         * 如果probe_values所有值都读失败,提示错误信息
         */
        jc      LOCAL(probe_loop)
        /* %cl is already the correct value! */
        movb    $1, %dh
        movb    $79, %ch
        /*
         * 幸运的,判断出0柱面0磁头CL扇区是可以工作的
         * 因此跳转回LOCAL(final_init)
         * 在那儿将保存软盘的CHS参数为C/H/S=0/0/CL
         */
        jmp     LOCAL(final_init)
        . = _start + GRUB_BOOT_MACHINE_PART_END
/* the last 2 bytes in the sector 0 contain the signature */
/*
 * MBR幻数,小端(little endian) 下总是等于0xAA55
 */
        .word   GRUB_BOOT_MACHINE_SIGNATURE