Yanyg - Software Engineer

grub2 1.95 源码分析之一 —— boot.S 分析及注释

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

/* -*-Asm-*- */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 1999,2000,2001,2002,2005,2006  Free Software Foundation, Inc.
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

// cppgp 注释版
// 转载请注明原作者
// 注释版本号 : 1.00
// 日期 : 2008-01-22
// 联系方式 :
// email yanyg02@163.com
// msn  yanyg02@hotmail.com
// qq  281607998

// boot.S 流程及功能简介 :
// boot.S 生成 512 字节的机器码存储在硬盘或者软盘的 MBR  ,  BIOS 加载
// 到内存 0x7C00  , 然后 BIOS 设置 CPU 跳转至 0x7C00 处开始执行 , 控制权到了
// boot.S 手中 . boot.S 根据 BIOS 设置的寄存器初始值和一些 INT 中断调用 , 判断
// 引导驱动器是 LBA硬盘/CHS硬盘/软盘 中的哪一种 , 并保存磁盘参数到磁盘参数块地
// 址(BPB) , 然后读取由 kernel_sector 处的 8 字节确定的扇区块 ( 即diskboot.img
//  bootdisk.S 生成的二进制码文件 )内存储的 512 字节的内容到内存地址 0x70000
//  , 再拷贝到内存 0x8000  , 然后跳转至 0x8000 开始执行 ,  bootdisk.S 
// 得控制权 ,  boot.S 就功成身退了. 但是 boot.S 设置的实模式下的堆栈及寄
// 存器值,以及引导驱动器的模式和参数 ,  bootdisk.S 中都有用到 . 具体请看代码
// 注释及 diskboot.S 的注释 . 如果在加载 diskboot.img 的过程中出错, 则输出错误
// 提示信息 , 进入死循环 , 等待手工重起或者关闭.
// boot.S 生成的机器码被加载在 0x7C00~0x7DFF 处, 具体机器码见文档最后部分


// 这个头文件里面只定义了一个版本号
#include <grub/boot.h>

// 这个头文件即../.././include/grub/i386/pc/boot.h , 定义引导需要的系统常量定义
#include <grub/machine/boot.h>

// 在代码中使用宏的地方 , 我都做了宏的说明 , 读者不需要跟踪到对应 .h 文件中看宏定义

/*
 *  defines for the code go here
 */

/* Absolute addresses
   This makes the assembler generate the address without support
   from the linker. (ELF can't relocate 16-bit addresses!)
*/

// ABS(x)计算x的绝对地址,用来生成不受连接器限制的地址(ELF不能生成可重入的地址).
#define ABS(x) (x-_start+0x7c00)

/* Print message string */
// 打印 x 指向的字符串到终端
#define MSG(x) movw $ABS(x), %si; call message

/* XXX: binutils-2.9.1.0.x doesn't produce a short opcode for this. */
#define MOV_MEM_TO_AL(x) .byte 0xa0;  .word x
// binutils-2.9.1.0.x不能生成short操作码,使用这个宏传递x给AL

 .file "boot.S"

 .text

/* Tell GAS to generate 16-bit instructions so that this code works
   in real mode.
*/
// 告知GAS生成16位的指令,使以下代码可以工作在实模式下
// 说明 : GAS是gcc的汇编器

 .code16

// 以下代码被BIOS启动例程加载至0X7C00处并且跳转到此处执行,过程描述如下:
// (为降低分析复杂度,我们假设从硬盘启动.)
// BIOS读取硬盘的MBR扇区共512字节,并放在地址0X0000:0X7C00处
// 跳转到0X0000:0X7C00处去执行
// 此时设置寄存器如下:
// CS:IP = 0X0000:0X7C00 !即代码段基址为0,偏移量为0X7C00
// ES:SI   !指向BIOS中硬盘分区表的地址.
// DL   !引导设备number,00x80~0xFF

.globl _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.
  */
 // 扇区开始处和BIOS参数块都是FAT/HPFS格式兼容的.

 // 跳转到after_BPB内存处
 jmp after_BPB
 nop /* do I care about this ??? */
 // nop 永远不会执行!

 /*
  * This space is for the BIOS parameter block!!!!  Don't change
  * the first jump, nor start the code anywhere but right after
  * this area.
  */
 // 以下空间是留给BIOS参数块的!!!
 // 不要更改跳转指令
 // 除了恰在BIOS参数块后的位置,也不要开始执行指令

 . = _start + 4
 // 保留空间 : 使本节代码占用的空间到_start+4处.如下分析:
 // jmp after_BPB  占用2字节
 // nop   占用1字节
 // 因此多保留一字节地址并以0填充.
 // 事实上,在生成的.img文件中,开始处四字节的机器码是:eb4b9000
 // Address  0x7C00  0x7C01  0x7C02  0x7C03
 // Value  eb  4b  90  00
 // Operator jmp  sfter_BPB nop  00

 /* scratch space */
 //保留的空间
 //占用 4 字节 , _start+0x00 ~ _start+0x03

// 磁盘信息参数块
// CHS 寻址方式用到 11 字节 _start+0x04 ~ _start+0x0E
// mode = 0 ; 然后依次是通过 BIOS INT 0x13 , AH = 0x08 调用得来的"扇区/磁头/柱面"参数
// LBA 寻址方式用到 16 字节 _start+0x04 ~ _start+0x14
// mode = 1 ; 然后会通过此后数据块的值和调用要求填充磁盘参数块
// 此时 , 诸如 sectors/heads/cylinders 等标签是无用的 , 具体参数格式见 lba_mode 一节对代码的注释

mode:
 .byte 0
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... */

 . = _start + GRUB_BOOT_MACHINE_BPB_END
 //保留空间

 /*
  * End of BIOS parameter block.
  */
 //BIOS参数块结束地址

// 现在,空间已经分配到了_start + GRUB_BOOT_MACHINE_BPB_END
// 其中
// 数据空间是 : _start+0x04 ~ _start+0x12
// 保留空间是 : _start+0x13 ~ _start+0x3D

// grub自身信息保存地址:
//  14 字节 : _start + 0x3E ~ _start + 0x4B
// boot_version !版本
// kernel_address !内核加载地址
// kernel_segment !内核加载段地址
// kernel_sector !扇区绝对地址,默认为0磁头0柱面1扇区
// boot_drive !从哪个磁盘加载,0xff表示使用boot驱动器加载
boot_version:
 .byte GRUB_BOOT_VERSION_MAJOR, GRUB_BOOT_VERSION_MINOR
 //  4   0
kernel_address:
 .word GRUB_BOOT_MACHINE_KERNEL_ADDR
 //  0x8000
kernel_segment:
 .word GRUB_BOOT_MACHINE_KERNEL_SEG
 //  0x800
kernel_sector:
 .long 1, 0
boot_drive:
 .byte 0xff /* the disk to load kernel from */
   /* 0xff means use the boot drive */

after_BPB:

/* general setup */

 // 首先屏蔽掉一切可屏蔽的中断!
 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).
         */

boot_drive_check:
 // 跳转至下一个1处 , 此处即往下第5行处
        jmp     1f
 // 以下三行有什么作用 ? 我不明白 ? 估计是残留下来的东西 , 上面英文注释说是为了修正布满 bug  BIOS
        testb   $0x80, %dl
        jnz     1f
        movb    $0x80, %dl
1:
 // 使 CS : IP = 0x0000 : 0x7C00
 /*
  * ljmp to the next instruction because some bogus BIOSes
  * jump to 07C0:0000 instead of 0000:7C00.
  */
 /*
  * ljmp跳转到$0, $ABS(real_start)指定的绝对位置执行指令
  * 因为一些糟糕的BIOS使用07C0:0000代替0000:7C00来实现跳转
  */

 // 跳转到 real_start 节的绝对地址并执行那里的代码.
 // cs : ip = 0x0000 : $ABS(real_start)
 ljmp $0, $ABS(real_start)

real_start:
 /* set up %ds and %ss as offset from 0 */
 // ax/ds/ss 清零
 xorw %ax, %ax
 movw %ax, %ds
 movw %ax, %ss
 // ds = 0 ; ss = 0

 /* set up the REAL stack */
 // GRUB_BOOT_MACHINE_STACK_SEG 是宏定义 , 等于 0x2000
 // 设置实模式下的堆栈 , 设置表示栈顶指针的变址寄存器 sp  0x2000
 // 现在 , ss : sp = 0x0000 : 0x2000
 movw $GRUB_BOOT_MACHINE_STACK_SEG, %sp

 // 现在 数据段堆栈段设置正确 , 允许中断
 // 中断标志位IF置1
 sti  /* we're safe again */

 /*
  *  Check if we have a forced disk reference here
  */

 //  boot_drive 处的值存储到 al
 // address(boot_drive) = _start+0x4B = 0x7C00+0x4B = 0x7C4B
 // 此处值默认为 0xff , 可在 setup 过程填充
 // 如果等于 0xff , 表示使用默认驱动 , 即从 BIOS 传递过来的驱动
 // 如果不等于 , 则将值存入 DL , 表示使用由该值确定的引导驱动
 MOV_MEM_TO_AL(ABS(boot_drive)) /* movb ABS(boot_drive), %al */
 cmpb $0xff, %al
 je 1f
 movb %al, %dl
1:
 /* save drive reference first thing! */
 //驱动信息压栈 : dl 中保存驱动number , dh 中什么都不是
 pushw %dx

 /* print a notification message on the screen */
 // 输出字符串 notification_string 到屏幕
 // notification_string = "GRUB"
 // MSG宏经展开以后是这样的 :
 // movw $(notification_string-_start+0x7c00), %si; call message
 // 其中 $(notification_string-_start+0x7c00) 是字符串 notification_string 绝对地址
 // 现在 , 我们知道 ds = 0x0000 , si = address(notification_string)
 // 而在函数 message  , 会通过 BIOS 提供的 INT 0x10 调用逐字符显示到终端
 // 具体见 message 函数定义部分
 // 注意 , 此时不需要设置 DF
 MSG(notification_string)

 /* set %si to the disk address packet */
 // 设置变址寄存器si为 disk_address_packet 的绝对地址
 // 宏展开以后是 : movw $(disk_address_packet-_start+0x7c00), %si
 movw $ABS(disk_address_packet), %si

 /* do not probe LBA if the drive is a floppy */
 // 如果驱动器是软盘,则不判断是否是支持LBA的磁盘 , 而直接跳转到chs_mod
 // 根据前面资料 , 我们知道磁盘的 DL 值为 0X80~0XFF , DL 最高位必然为1
 // GRUB_BOOT_MACHINE_BIOS_HD_FLAG宏定义 = 0x80
 // 因此 , 如果是磁盘 , ZF = 0 ;
 // 否则 , 是软盘引导 , ZF = 1 , 将跳转至 chs_mod
 testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
 jz chs_mode

 /* check if LBA is supported */
 // 现在 , 已经确定是磁盘引导 , 判断是只支持 8G 寻址的老式的磁盘还是 LBA 磁盘
 // 通过 BIOS 调用 INT 0x13 来确定是否支持扩展 , 详细见具体代码部分
 // LBA 扩展功能分两个子集 , 如下 :
 //  第一个子集提供了访问大硬盘所必须的功能 , 包括
 // 1.检查扩展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0x80 ~ 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

 //下面开始具体检测 , 首先检测扩展是否存在
 // 此时寄存器的值和 BIOS 调用分别是 :
 // AH = 0x41
 // BX = 0x55AA
 // DL = driver( 0x80 ~ 0xFF )
 // INT  13H
 // 返回结果 : 如果支持 CF = 0 ; 否则 CF = 1
 // CF = 0 (支持LBA) 时的寄存器值 :
 // ah : 扩展功能的主版本号( major version of extensions )
 // al : 内部使用( internal use )
 // bx : AA55h ( magic number )
 // cx :
 //  Bits  Description
 //  0  extended disk access functions
 //  1  removable drive controller functions supported
 //  2  enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported.
 //    Extended drive parameter table is valid
 //  3~15  reserved (0)
 // CF = 1 (不支持LBA) 时的寄存器值 :
 // ah = 0x01 ( invalid function )
 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 可能已被 INT 13 , AH = 41H , BX = 55AAH 调用修改,重新改回!
 // 例如 AST BIOS 1.04 将存在这种修改
 popw %dx
 pushw %dx

 /* use CHS if fails */
 // 如果扩展检测调用失败 , 进入 chs_mod , 分别通过检测 CF/BX/CX 确认是否支持 LBA

 // 检测 CF  ,  1 表示失败进入 chs_mod
 jc chs_mode

 // 检测 BX  , 如果不等于 0xAA55 , 进入 chs_mod
 cmpw $0xaa55, %bx
 jne chs_mode

 // 如果不支持扩展第一子集(见上) , CX.bit0 = 0 , 进入 chs_mod
 andw $1, %cx
 jz chs_mode

lba_mode:
 // 本节代码的功能 : 加载 bootdisk.S 生成的机器码到绝对地址 0x7000  , 并跳转执行 copy_buffer

 // 进入到这里时 , 已探测到系统支持 LBA 扩展的第一子集
 // 下面首先为扩展读的 BIOS 调用设置参数块值
 // 关于扩展读调用的详细情况和结果值见调用部分注释
 // 扩展读需要的磁盘参数块说明如下 :
 // 磁盘参数块的规定格式如下 :
 // Format of disk address packet:
 // Offset Size Bits Description
 // 00h BYTE 8 size of packet (10h or 18h)
 // 01h BYTE 8 reserved (0)
 // 02h WORD 16 number of blocks to transfer (max 007Fh for Phoenix EDD)
 // 04h DWORD 32 -> transfer buffer
 // 08h QWORD 64 starting absolute block number
 // 现在 , 我们的 ds = 0x0000 , si = $ABS(disk_address_packet)
 // 我们取扩展读磁盘参数块的地址标号是 ADDR_LBA_BPB(0x00) ~ ADDR_LBA_BPB(0x0F)  16 字节
 // 对于各个值的设置见下面代码

 xorw %ax, %ax
 movw %ax, 4(%si)
 // ADDR_LBA_BPB(0x04) = 0x00
 // ADDR_LBA_BPB(0x05) = 0x00

 // 设置 mode ,  diskboot.S 里面会用到
 // mode = 1 , LBA 扩展读
 // mode = 0 , CHS 寻址读
 incw %ax
 /* set the mode to non-zero */
 // -1(%si)即为mode所在处地址
 // 此时 AX = 0x0001 , AL = 0x01
 movb %al, -1(%si)

 /* the blocks */
 movw %ax, 2(%si)
 // ADDR_LBA_BPB(0x02) = 0x01
 // ADDR_LBA_BPB(0x03) = 0x00

 /* the size and the reserved byte */
 movw $0x0010, (%si)
 // ADDR_LBA_BPB(0x00) = 0x10
 // ADDR_LBA_BPB(0x01) = 0x00

 /* the absolute address */
 // 将(kernel_sector~kernel_sector+3)单元(绝对地址)的值给 ebx , 默认为 1
 //  grub-mkimage 命令中被修改 ,存储 diskboot.S 生成代码的存储扇区
 movl ABS(kernel_sector), %ebx
 movl %ebx, 8(%si)
 // ADDR_LBA_BPB(0x08~0x0B) = 0x0000 0001

 // 将(kernel_sector+4~kernel_sector+7)单元(绝对地址)的值给 ebx , 默认为 0
 movl ABS(kernel_sector + 4), %ebx
 movl %ebx, 12(%si)
 // ADDR_LBA_BPB(0x0C~0x0F) = 0x0000 0000

 /* the segment of buffer address */
 // GRUB_BOOT_MACHINE_BUFFER_SEG 宏定义 = 0x7000
 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si)
 // ADDR_LBA_BPB(0x06~0x07) = 0x7000

 // 对应如下 :
 // Offset  Size/Bits  Value  Description
 // 00h  BYTE/8   0x10  size of packet (10h or 18h)
 // 01h  BYTE/8   0x00  reserved (0)
 // 02h  WORD/16   0x0001  number of blocks to transfer (max 007Fh for Phoenix EDD)
 // 04h  DWORD/32  0x70000000 -> transfer buffer
 // 08h  QWORD/64  0x01  starting absolute block number

/*
 * 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
 */
 movb $0x42, %ah

 // 寄存器和磁盘参数块设置完毕
 // 寄存器值 : AH = 0x42 ; DL = driver number ; DS : SI = 0x0000 : ABS(disk_address_packet)
 // disk_address_packet 各个值见上面注释
 // 这次 INT 0x13 调用将完成 :
 // 通过扩展读 , 从磁盘读取由 kernel_sector 处8字节长度确定的某一扇区的内容到内存地址 0x70000(0x7000:0x0000) 
 // 成功 : CF = 0 , AH = 0
 // 失败 : CF = 1 , AH = 错误码
 // 调用结束后 磁盘地址块的 block 计数项 (ADDR_LBA_BPB(0x02)) 存放成功成功传送的数据块数.

 int $0x13

 // LBA扩展读失败 , 进入 chs_mod
 /* LBA read is not supported, so fallback to CHS.  */
 jc chs_mode

 // 存储 GRUB_BOOT_MACHINE_BUFFER_SEG  bx , 跳转至 copy_buffer
 // GRUB_BOOT_MACHINE_BUFFER_SEG = 0x7000
 // 数据寄存器 BX 设置为 0x7000
 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
 jmp copy_buffer

chs_mode:
 // 进入此处 , 已经判定是不支持磁盘 LBA 扩展了 , 但是从磁盘引导还是软盘引导尚未确定
 // 因此 , CHS磁盘引导 或者 软盘引导 均从此处处理
 /*
  *  Determine the hard disk geometry from the BIOS!
  *  We do this first, so that LS-120 IDE floppies work correctly.
  */
 // 通过 BIOS INT 0x13 , AH = 0x08 调用测定磁盘的物理参数
 // 该的详细解释如下 :
 /*
  * 作用 :
  * 取得磁盘参数
  * 寄存器值 :
  * AH = 0x08
  * DL = drive (bit 7 set for hard disk)
  * ES:DI = 0x0000 : 0x0000 to guard against BIOS bugs
  * 成功 :
  * CF = 0
  * AH = 00h
  * AL = 00h on at least some BIOSes
  * BL = drive type (AT/PS2 floppies only)
  * CH = low eight bits of maximum cylinder number
  * CL = maximum sector number (bits 5-0) , high two bits of maximum cylinder number (bits 7-6)
  * DH = maximum head number
  * DL = number of drives
  * ES:DI -> drive parameter table (floppies only)
  * 失败 :
  * CF = 1
  * AH = status (07h)
  */

 movb $8, %ah
 int $0x13
 jnc final_init
 // 如果调用成功 , CF = 0 , 跳转到 final_init 处!

 // 现在已经确定不支持 CHS , 测试是否是软盘引导
 // 如果是软盘引导 , 进入 floppy_probe
 // 否则出错 , 进入 hd_probe_error
 /*
  *  The call failed, so maybe use the floppy probe instead.
  */
 // 如果 DL 最高位为 0 ,则为软盘
 testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
 // 如果是软盘 , 跳转到 floppy_probe
 jz floppy_probe

 // 现在 , 知道我们确实是一个磁盘(硬盘) , 但是实在不知道该怎么读 ,  hd_probe_error 玩玩吧
 /* Nope, we definitely have a hard disk, and we're screwed. */
 jmp hd_probe_error

final_init:
 // 设置 mode = 0 , mode  diskboot.S 中用到 , 用来判断引导磁盘是 LBA 读还是 CHS 
 /* set the mode to zero */
 // DH 存放磁头数
 // 这句完成了2个功能 : dh 存放到 eax  al ; eax 的其它位置 0
 movzbl %dh, %eax
 movb %ah, -1(%si)
 // 设置 mode = 0

 // 保存 磁头/柱面/扇区数
 /* save number of heads */
 // BIOS 中磁头编号是按照 0 ~ n-1 , 此处加 1 并保存到磁盘参数块
 incw %ax
 movl %eax, 4(%si)

 // cl  0~5 bits 存放扇区数 , 6~7 bits 存放柱面数的高 2 
 movzbw %cl, %dx
 shlw $2, %dx
 // 柱面数的低 8 
 movb %ch, %al
 // dh 现在是磁头数的高两位!
 movb %dh, %ah
 // 现在 ah 存放柱面数的高 2 
 // al 存放柱面数的低 8 
 // ax 存放整个柱面参数

 /* save number of cylinders */
 // BIOS 中柱面编号是 0 ~ n-1 , 此处加 1 并保存到磁盘参数块
 incw %ax
 movw %ax, 8(%si)

 // dl  0~2bits 是0  ,5~7bits 是扇区数
 movzbw %dl, %ax
 shrb $2, %al
 // 右移 2  , 现在 al  0~5 bits 是扇区数 , 6~7 bits  0

 /* save number of sectors */
 // BIOS 中扇区编号是 1 ~ n , 直接保存到磁盘参数块!
 movl %eax, (%si)

setup_sectors:
 // 使用 CHS 模式下 BIOS 提供的读调用 , 加载由 kernel_sector 确定的一个扇区到内存 0x70000 

 /* load logical sector start (top half) */
 // kernel_sector + 4 存储内存偏移量
 //  CHS 读中必须为 0 , 否则出错
 movl ABS(kernel_sector + 4), %eax
 orl %eax, %eax
 // 如果 eax != 0x0000 0000 0000 0000 , 则无法引导 , 跳转 geometry_error 处理 !
 jnz geometry_error

 /* load logical sector start (bottom half) */
 // kernel_sector 处值默认为 1 ,  grub-mkimage 命令中设置为 diskboot.img(diskboot.S生成) 存储的扇区号
 movl ABS(kernel_sector), %eax


 // 以下通过除法运算的商和余数 , 计算引导扇区所在的 磁头/柱面/扇区
 // eax 现在存储的是引导扇区编号
 /* zero %edx */
 xorl %edx, %edx

 /* divide by number of sectors */
 // (%si) 处存放扇区数
 divl (%si)
 // (%si)中存放sectors , (%si)/%eax
 // eax  sectors/eax 的商 , kernel_sector 取默认值时等于 sectors
 // edx 中现在是余数 , kernel_sector 为默认时为 edx  0

 /* save sector start */
 // 存储起始扇区 , 因为扇区编号是 1~n ,需要 cl+=1(见后面操作)
 movb %dl, %cl

 xorw %dx, %dx /* zero %edx */
 divl 4(%si)  /* divide by number of heads */
 // 4(%si) 是磁头数 , 磁头数除以扇区数 , eax 中存放柱面数 , edx 存放余数 , 表示起始磁头

 /* do we need too many cylinders? */
 // 8(%si)是柱面数  , 如果 ax 中数值大于柱面数 , 表示超出范围 , 跳转至 geometry_error 处理!
 cmpw 8(%si), %ax
 jge geometry_error

 /* normalize sector start (1-based) */
 // start 扇区 += 1 , 因为扇区编号是 1~n
 incb %cl

 /* low bits of cylinder start */
 // al 是柱面的低 8 
 movb %al, %ch

 /* high bits of cylinder start */
 // ah 是柱面的高 2  , 操作之后 , cl  2 位存储柱面的高 2 
 xorb %al, %al
 shrw $2, %ax
 orb %al, %cl

 // 保存起始磁头到 al
 /* save head start */
 movb %dl, %al

 // 重新设置DL为引导驱动器编号
 /* restore %dl */
 popw %dx

 /* head start */
 // al 中现在是起始磁头到 dh
 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
 */

 /* 寄存器 :
  * AH = 0x02
  * AL = number of sectors to read (must be nonzero)
  * CH = low eight bits of cylinder number
  * CL = sector number 1-63 (bits 0-5),high two bits of cylinder (bits 6-7, hard disk only)
  * DH = head number
  * DL = drive number (bit 7 set for hard disk)
  * ES:BX -> data buffer
  * 成功 :
  * CF = 0
  * AH = status
  * AL = number of sectors transferred (only valid if CF set for some BIOSes)
  * BX
  * 失败 :
  * CF = 1
  * AH = 11h (corrected ECC error), AL = burst length
  */

 // 设置磁盘读取缓冲区
 // 操作完毕之后 , bx = 0x0000 , es = 0x7000
 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
 movw %bx, %es /* load %es segment with disk buffer */

 xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
 movw $0x0201, %ax /* function 2 */

 // CHS 模式读的 BIOS INT 0x13 调用 , 寄存器值已经设置如下 :
 // 寄存器名    意义
 // AH   0x02  CHS模式读需要
 // AL   0x01  需要读的扇区数
 // CH   已设置  柱面数的低 8 
 // CL   已设置  0~5bits 扇区号 , bits 6-7bits 柱面的高 2 
 // DH   已设置  磁头数
 // DL   已恢复  驱动号
 // ES:BX   0x7000:0x0000 数据缓冲区
 // 其中 , CH/CL/DH 保存起始 磁头/柱面/扇区 , 是由 kernel_sector 中设置的值经运算得来 ,
 // 其中 , CH/CL/DH 保存起始 磁头/柱面/扇区 , 是由 kernel_sector 中设置的值经运算得来 , 具体见上面运算过程

 int $0x13

 // 如果 CHS 读失败 , 进入 read_error
 jc read_error

 // 设置 bx  0x7000 ,  copy_buffer 用到
 movw %es, %bx

copy_buffer:
 // 以下代码把通过 BIOS 调用读进来的一扇区的数据移动到内存地址 0x8000 

 movw ABS(kernel_segment), %es
 // es = 0x800

 /*
  * We need to save %cx and %si because the startup code in
  * kernel uses them without initializing them.
  */
 // 这里需要保存 cx  si 的值
 // 因为内核 startup 里面的代码未经初始化就使用了它( 其实是 diskboot.S 代码 )
 pusha
 pushw %ds


 // 设置循环次数和源/目的串段寄存器和偏移指针
 // 设置完毕之后 , 寄存器值 :
 // CX = 0x100 ; ES:SI=0x800:0x0000
 // LBA读 : DS:SI = 0x7000:0x0000
 // CHS读 : DS:SI = ?
 movw $0x100, %cx
 // cx = 0x100 = 256

 movw %bx, %ds
 // ds = 0x7000

 xorw %si, %si
 // si = 0x00

 xorw %di, %di
 // di = 0

 cld
 //DF = 0 (方向标志)

 // 操作串由 DS:SI  ES:DI 指定
 //  DS:SI 指向的字传送到 ES:DI 指向的内存单元
 // DF = 0 , 因此 SI += 2 ; DI += 2
 rep
 movsw
 //  ds:si 处的连续的 512 字节数据搬移到 es:di 地址处
 // 即将刚读进来的 512 字节的数据搬移到内存地址地址 0x8000 

 // 寄存器还原
 popw %ds
 popa

 // 现在 boot.S 的所有任务全部完成 , 再执行一条跳转指令 , 把控制权交给 diskboot.S 后就功成身退了
 /* boot kernel */
 // kernel_address = 0x8000
 jmp *(kernel_address)
 // 0x8000 处存放了 diskboot.S 生成的机器码
 // 至于 0x8000 处的 512 字节代码完成什么功能 ,  diskboot.S 注释

/* END OF MAIN LOOP */

/*
 * BIOS Geometry translation error (past the end of the disk geometry!).
 */
geometry_error:
 // 打印错误信息串 geometry_error_string 然后跳转到 general_error
 // 宏展开后是 : movw $(geometry_error_string-_start+0x7c00), %si; call message
 // geometry_error_string = "Geom"
 MSG(geometry_error_string)
 jmp general_error

/*
 * Disk probe failure.
 */
hd_probe_error:
 // 打印错误信息串 hd_probe_error_string 然后跳转到 general_error
 // 宏展开后是 : movw $(hd_probe_error_string-_start+0x7c00), %si; call message
 // hd_probe_error_string = "Hard Disk"
 MSG(hd_probe_error_string)
 jmp general_error

/*
 * Read error on the disk.
 */
read_error:
 // 打印错误信息串 read_error_string
 // 宏展开后是 : movw $(read_error_string-_start+0x7c00), %si; call message
 // read_error_string = "Read"
 MSG(read_error_string)

general_error:
 // 打印错误信息串 general_error_string
 // 调用 BIOS 提供的调用 INT 0x18 告诉 BIOS 引导失败
 // 进入死循环 , 等待手动重起 ( 下面第 4  处代码 )
 // 宏展开后是 : movw $(general_error_string-_start+0x7c00), %si; call message
 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 */
        int $0x18
stop: jmp stop

notification_string: .string "GRUB "
geometry_error_string: .string "Geom"
hd_probe_error_string: .string "Hard Disk"
read_error_string: .string "Read"
general_error_string: .string " Error"

/*
 * message: write the string pointed to by %si
 *
 *   WARNING: trashes %si, %ax, and %bx
 */

 /*
  * Use BIOS "int 10H Function 0Eh" to write character in teletype mode
  * %ah = 0xe %al = character
  * %bh = page %bl = foreground color (graphics modes)
  */
// message 通过 BIOS 提供的 INT 0x10 , AH = 0x0E ,  ds:si 字符串逐个输出到终端
1:
 movw $0x0001, %bx
 movb $0xe, %ah
 int $0x10  /* display a byte */
message:
 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.
  */

 // Windows NT 在此设置了幻数 , 此处保留已保证兼容性
 // GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC = 0x1B8
 . = _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?
  */

// 如果是硬盘 , 以下是分区表地址域
// 否则如果是软盘 , 则存储
part_start:
 // GRUB_BOOT_MACHINE_PART_START = 0x1BE
 . = _start + GRUB_BOOT_MACHINE_PART_START

probe_values:
 .byte 36, 18, 15, 9, 0

// 软盘引导探测
floppy_probe:
/*
 *  Perform floppy probe.
 */

 // 宏展开之后 : movw $(probe_values-1 -_start+0x7c00), %si
 movw $ABS(probe_values-1), %si

probe_loop:
 /* reset floppy controller INT 13h AH=0 */
 /* 磁盘/软盘复位调用
  * INT 0x13 ; AH = 0x00 ; DL = drive number
  * 成功 : CF = 0 , AH = 0x00
  * 失败 : CF = 1 , AH = 错误状态
  * Note:
  * Forces controller to recalibrate drive heads (seek to track 0).
  * For PS/2 35SX, 35LS, 40SX and L40SX, as well as many other systems,
  * both the master drive and the slave drive respond to the Reset function
  * that is issued to either drive
  */


 xorw %ax, %ax
 int $0x13

 incw %si
 movb (%si), %cl

 /* if number of sectors is 0, display error and die */
 // 如果扇区数为 0 , 出错 , 否则跳转至 1f
 cmpb $0, %cl
 jne 1f

/*
 * Floppy disk probe failure.
 */
 // 宏替换后 : movw $(fd_probe_error_string-_start+0x7c00), %si; call message
 // fd_probe_error_string = "Flobby"
 MSG(fd_probe_error_string)
 jmp general_error

fd_probe_error_string: .string "Floppy"

1:
 // 软盘读取 , BIOS 调用和 CHS 读取的 BIOS 调用完全一致 , 详细见 chs_mod 处的注释
 // 寄存器值 ah = 0x02 , al = 0x01 , ch = dh = 0 , cl = start sector , dl = driver number
 /* perform read */
 // GRUB_BOOT_MACHINE_BUFFER_SEG = 0x7000
 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
 movw $0x201, %ax
 movb $0, %ch
 movb $0, %dh
 int $0x13

 /* if error, jump to "probe_loop" */
 // 如果读失败返回 probe_loop 重读
 jc probe_loop

 /* %cl is already the correct value! */
 // 设置 扇区/柱面/磁头
 // cl  start sector 已设置正确
 // dh 设置为 79 , 表示柱面最大值为 79(80柱:0~79)
 // dh 设置为 1 , 表示磁头数最大值为 1(2头:0~1)
 // 然后跳转至 final_init
 // 查看 final_init , 知道保存时会把柱面和磁头分别加 1 , 扇区不变
 // 因此 , 在软盘加载时 , 将设置 Cylinder : Head : Sector = 80 : 2 : start_sector
 movb $1, %dh
 movb $79, %ch

 jmp final_init

 // GRUB_BOOT_MACHINE_PART_END = 0x1FE
 . = _start + GRUB_BOOT_MACHINE_PART_END
 // 空间保留 , 分区信息占用内存地址是 _start + 0x1BE ~ _start + 0x1FD

/* the last 2 bytes in the sector 0 contain the signature */
 // 引导幻数 , 2 字节 , 等于 0xAA55
 // GRUB_BOOT_MACHINE_SIGNATURE = 0xAA55
 .word GRUB_BOOT_MACHINE_SIGNATURE


//本段代码在编译链接成的机器码如下:( 0x7C00 ~ 0x7DFF 共512字节 )
/*
 * ADDR ---------------CODE----------------
 * 7c00 eb4b9000 00000000 00000000 00000000
 * 7c10 00000000 00000000 00000000 00000000
 * 7c20 00000000 00000000 00000000 00000000
 * 7c30 00000000 00000000 00000000 00000400
 * 7c40 00800008 01000000 00000000 fffaeb07
 * 7c50 f6c28075 02b280ea 5c7c0000 31c08ed8
 * 7c60 8ed0bc00 20fba04c 7c3cff74 0288c252
 * 7c70 be717de8 2301be05 7cf6c280 7448b441
 * 7c80 bbaa55cd 135a5272 3d81fb55 aa753783
 * 7c90 e1017432 31c08944 04408844 ff894402
 * 7ca0 c7041000 668b1e44 7c66895c 08668b1e
 * 7cb0 487c6689 5c0cc744 060070b4 42cd1372
 * 7cc0 05bb0070 eb73b408 cd13730a f6c2800f
 * 7cd0 84f000e9 8300660f b6c68864 ff406689
 * 7ce0 44040fb6 d1c1e202 88e888f4 40894408
 * 7cf0 0fb6c2c0 e8026689 0466a148 7c6609c0
 * 7d00 754f66a1 447c6631 d266f734 88d131d2
 * 7d10 66f77404 3b44087d 38fec188 c530c0c1
 * 7d20 e80208c1 88d05a88 c6bb0070 8ec331db
 * 7d30 b80102cd 13722a8c c38e0642 7c601eb9
 * 7d40 00018edb 31f631ff fcf3a51f 61ff2640
 * 7d50 7cbe777d e84200eb 0ebe7c7d e83a00eb
 * 7d60 06be867d e83200be 8b7de82c 00cd18eb
 * 7d70 fe475255 42200047 656f6d00 48617264
 * 7d80 20446973 6b005265 61640020 4572726f
 * 7d90 7200bb01 00b40ecd 10ac3c00 75f4c300
 * 7da0 00000000 00000000 00000000 00000000
 * 7db0 00000000 00000000 00000000 00002412
 * 7dc0 0f0900be bd7d31c0 cd13468a 0c80f900
 * 7dd0 750fbeda 7de8c1ff eb8d466c 6f707079
 * 7de0 00bb0070 b80102b5 00b600cd 1372d7b6
 * 7df0 01b54fe9 e0fe0000 00000000 000055aa
 */