x86实模式引导程序开发与ISO9660文件系统解析 1. boot.asm代码概述boot.asm是MonkOS操作系统的第一阶段引导加载程序采用x86汇编语言编写运行在16位实模式下。这段512字节的代码被BIOS加载到内存0x7C00处执行主要完成以下核心任务初始化CPU寄存器和内存布局从ISO9660格式的光盘介质中定位第二阶段加载程序(LOADER.SYS)将第二阶段加载程序读取到内存指定位置移交控制权给第二阶段加载程序注意该引导程序专为El Torito可启动CD-ROM设计使用2048字节/扇区的ISO9660格式不同于传统的512字节扇区硬盘引导。2. 内存布局与初始化2.1 BIOS加载时的内存状态当BIOS将控制权交给boot.asm时x86 CPU处于16位实模式内存布局如下00000000-000003FF 1KB 实模式中断向量表(IVT) 00000400-000004FF 256B BIOS数据区 00000500-00007BFF ~30KB 空闲内存 00007C00-00007DFF 512B 引导程序(boot.asm) 00007E00-0009FBFF ~608KB 空闲内存 0009FC00-0009FFFF 1KB 扩展BIOS数据区(EBDA) 000A0000-000BFFFF 128KB 视频内存 000C0000-000FFFFF 256KB ROM区域2.2 寄存器初始化流程boot: jmp Mem.Loader1.Segment : .init ; 远跳转更新CS寄存器 .init: cli ; 禁用中断 mov ax, cs mov ds, ax ; 数据段代码段 mov fs, ax mov gs, ax xor ax, ax mov ss, ax ; 栈段0 mov sp, Mem.Stack.Top ; 栈顶0x7C00 mov es, ax ; 附加段0 sti ; 启用中断 mov byte [es:Globals.DriveNumber], dl ; 保存启动驱动器号关键点说明cli/sti保护关键初始化过程不被中断打断所有段寄存器初始化为0x0000或0x07C0栈指针SP设置为0x7C00(向下增长)DL寄存器保存的启动驱动器号存入全局变量3. ISO9660文件系统解析3.1 定位主卷描述符ISO9660文件系统结构从第16扇区(0x10)开始查找主卷描述符.findRootDirectory: mov bx, 0x10 ; 起始扇区LBA0x10 mov cx, 1 ; 读取1个扇区 mov di, Mem.Sector.Buffer ; 目标缓冲区0x0800 .readVolume: call ReadSectors ; 读取扇区 jc .error ; 出错处理 mov al, [es:Mem.Sector.Buffer] ; 读取描述符类型 cmp al, 0x01 ; 类型1主卷描述符 je .found cmp al, 0xff ; 类型0xFF结束标记 je .error inc bx ; 检查下一个扇区 jmp .readVolume3.2 解析根目录找到主卷描述符后从固定偏移量获取根目录位置.found: mov bx, [es:Mem.Sector.Buffer \ ISO.PrimaryVolumeDescriptor.RootDirEntry \ ISO.DirectoryEntry.LocationLBA] mov [es:Globals.RootDirectorySector], bx ; 保存根目录扇区关键数据结构偏移量RootDirEntry位于主卷描述符的156字节处LocationLBA在目录项中偏移2字节占4字节(Little-Endian)4. 加载第二阶段引导程序4.1 查找LOADER.SYS文件遍历根目录项匹配文件名LOADER.SYS;1.processDirEntry: xor ax, ax mov al, [es:di ISO.DirectoryEntry.RecordLength] cmp al, 0 ; 检查目录项长度 je .error test byte [es:di ISO.DirectoryEntry.FileFlags], 0x02 jnz .nextDirEntry ; 跳过目录项 cmp byte [es:di ISO.DirectoryEntry.NameLength], \ Loader.Filename.Length jne .nextDirEntry ; 文件名长度不匹配 push di mov cx, Loader.Filename.Length mov si, Loader.Filename add di, ISO.DirectoryEntry.Name cld rep cmpsb ; 逐字节比较文件名 pop di je .loaderFound4.2 加载文件到内存找到文件后校验大小并加载到0x8000.loaderFound: mov bx, [es:di ISO.DirectoryEntry.LocationLBA] ; 文件起始扇区 .calcSize: mov cx, [es:di ISO.DirectoryEntry.Size 2] ; 检查文件大小高16位 cmp cx, 0 jne .error ; 超过64KB mov cx, [es:di ISO.DirectoryEntry.Size] ; 获取文件大小 cmp cx, 0x8000 ; 最大32KB ja .error add cx, Mem.Sector.Buffer.Size - 1 ; 向上取整 shr cx, 11 ; 转换为扇区数(除以2048) .load: mov ax, Mem.Loader2.Segment ; 0x0800 mov es, ax xor di, di ; 目标偏移0 call ReadSectors ; 读取文件内容5. 关键功能实现5.1 扇区读取函数ReadSectors: mov word [BIOS.DAP.Buffer BIOS.DAP.ReadSectors], cx mov word [BIOS.DAP.Buffer BIOS.DAP.TargetBufferOffset], di mov word [BIOS.DAP.Buffer BIOS.DAP.TargetBufferSegment], es mov word [BIOS.DAP.Buffer BIOS.DAP.FirstSector], bx lea si, [BIOS.DAP.Buffer] ; DS:SI指向DAP mov ax, 0x4200 ; AH0x42(扩展读), AL0 int 0x13 ; BIOS磁盘服务 ret磁盘地址包(DAP)结构0 BYTE 包大小(16) 1 BYTE 保留 2 WORD 读取扇区数 4 DWORD 目标缓冲区(段:偏移) 8 QWORD 起始LBA5.2 字符串输出函数DisplayString: pusha mov ah, 0x0e ; BIOS teletype输出 xor bx, bx ; 页号0 cld .loop: lodsb ; 读取字符 cmp al, 0 ; 检查结束符 je .done int 0x10 ; 输出字符 jmp .loop .done: popa ret6. 常见问题与调试技巧6.1 典型错误场景找不到主卷描述符检查ISO映像是否使用-b参数正确指定引导扇区确认genisoimage命令包含-no-emul-boot选项文件加载失败确认LOADER.SYS位于ISO根目录检查文件属性是否为普通文件(非目录)内存越界确保LOADER.SYS不超过32KB验证第二阶段加载程序正确链接到0x80006.2 调试方法使用Bochs调试 在bochsrc配置中添加magic_break: enabled1在代码中插入xchg bx, bx作为断点查看寄存器状态; 打印AX寄存器值(十六进制) pusha mov cx, 4 .print_digit: rol ax, 4 mov bx, ax and bx, 0x0f mov al, [hexchars bx] mov ah, 0x0e int 0x10 loop .print_digit popa ret hexchars db 0123456789ABCDEF内存检查 使用es:di指向目标内存通过int 0x10逐字节输出检查7. 开发环境搭建建议汇编工具链nasm -f bin -o boot.sys boot.asmISO生成命令genisoimage -R -J -c boot/bootcat -b boot/boot.sys \ -no-emul-boot -boot-load-size 4 -o boot.iso \ ./isoQEMU测试命令qemu-system-x86_64 -cdrom boot.iso -monitor stdio调试技巧在QEMU中使用-d int参数记录中断调用通过-s -S参数启用GDB远程调试8. 代码优化方向错误处理增强添加更多错误状态提示信息实现重试机制应对读取失败性能优化增加多扇区读取减少中断次数使用BIOS预取功能加速访问功能扩展支持FAT32文件系统添加启动菜单选择功能实现简单的CRC校验这个引导程序展示了x86实模式下最基础的磁盘操作和文件系统访问技术虽然代码量不大但涵盖了操作系统启动的关键流程。通过扩展错误处理、支持更多文件系统、添加图形界面等功能可以将其发展为更完善的引导加载程序。