深入理解 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-*- */ #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