启动区详解:ipl10.nas
一、已解决疑点(答案在注释中)
1.为什么ipl要装入位置是0x7c00?
2.为什么磁盘装入位置是0x8200,而ipl结束后跳转位置是0x8000+0x4200?
3.为什么ss:sp和org(装入位置)都是7c00?push会覆盖代码吗?
二、未解决疑点
1.程序没有使用push类指令,为何仍然必须对ss和sp初始化?(测试发现删去对ss和sp的初始化后程序无法启动)
三、代码及注释
;ipl :initial program loader,启动程序加载器。存放在启动区(软盘第一个扇区) ;作用:把操作系统从软盘加载到内存,之后jmp到操作系统程序所在地址,ipl其实就类似于MBR主引导记录 ; haribote-ipl ; TAB=4 CYLS EQU 10 ; “CYLS EQU 10”意思是“CYLS = 10”。EQU是“equal”的缩写。 ; CYLS代表cylinders ,柱面 ORG 0x7c00 ; 指明程序的装载地址 ; 0x00007c00-0x00007dff :规定为启动区内容的装载地址 ; 以下这段是标准FAT12格式软盘专用的代码 ;汇编知识补充: ;DB,DW,DD : 在当前位置写入字节,字,双字 ;RESB : 从当前位置开始,保留n个字节,保留的字节全为0 ;DB,DW,DD,RESB 不是需要“执行”的指令,也没有对应机器码,在汇编为机器码时直接在当前位置写入信息 ;详见图1:ipl.bin的二进制文件,开头EB 4E为JMP entry对应的机器码,后续90为DB 0X90写入,后面的信息也是DB,DD或DW直接写入的 JMP entry DB 0x90 DB "HARIBOTE" ; 启动区的名称可以是任意的字符串(8字节) DW 512 ; 每个扇区(sector)的大小(必须为512字节) DB 1 ; 簇(cluster)的大小(必须为1个扇区) DW 1 ; FAT的起始位置(一般从第一个扇区开始) DB 2 ; FAT的个数(必须为2) DW 224 ; 根目录的大小(一般设成224项) DW 2880 ; 该磁盘的大小(必须是2880扇区) DB 0xf0 ; 磁盘的种类(必须是0xf0) DW 9 ; FAT的长度(必须是9扇区) DW 18 ; 1个磁道(track)有几个扇区(必须是18) DW 2 ; 磁头数(必须是2) DD 0 ; 不使用分区,必须是0 DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明,固定 DD 0xffffffff ; (可能是)卷标号码 DB "HARIBOTEOS " ; 磁盘的名称(11字节) DB "FAT12 " ; 磁盘格式名称(8字节) RESB 18 ; 先空出18字节 ; 程序主体 entry: MOV AX,0 ; 初始化寄存器 MOV SS,AX ; 没有从立即数到段寄存器的电路实现,只有通过其他寄存器来中转 ; SS是段基址寄存器,即存放sp和bp的默认基址 MOV SP,0x7c00 ; 由于SS是0,所以SP指向的是[SS:SP]即0*16 + 7c00 = 7c00, ; 这个地址和ORG指定的7c00一致,这时使用push覆盖的是JMP entry和DB写入的一些信息,这些都是一次性使用的,被覆盖后(也许)不会有什么影响
; 实际上ipl也没有使用过push和call类的指令,其实SS:SP只要在7c00-7fff(ipl)之内即可(亲测)
MOV DS,AX ; 读磁盘 MOV AX,0x0820 MOV ES,AX ; ES 为附加段寄存器,常与BX连用 [ES:BX] MOV CH,0 ; 柱面0 C0 MOV DH,0 ; 磁头0 H0 MOV CL,2 ; 扇区2 S2 ; C0-H0-S1是ipl所处的启动区,自动装载到0x8000,因此我们需要把之后的扇区读入内存 ; 软盘地址格式:柱面号-磁头号-扇区号 ; 柱面和磁头都是从0开始,而扇区是从1开始 readloop: MOV SI,0 ; SI寄存器记录出错次数 retry: ;INT软件中断使用寄存器传参 ;对于INT13 AH表示模式,AL表示扇区数,AH=DL=0表示复位软盘状态 MOV AH,0x02 ; AH=0x02 : 代表读入磁盘 MOV AL,1 ; 1个扇区 MOV BX,0 ; [ES:BX]为C0-H0-S2的装入地址,即 0820*16 + 0 = 8200 ,这个地址是本书作者选用的安全区域 ; 8000-81ff本应该保存第一个扇区,但第一个扇区已经作为启动区装载到了7c00,因此不用装入,8000-81ff这0x200B暂时保留,不装入任何内容, ; C0-H0-S2从8200开始装入,等价于C0-H0-S1从8000开始装入 MOV DL,0x00 ; A驱动器 INT 0x13 ; 调用磁盘BIOS JNC next ; 没出错的话跳转到next ADD SI,1 ; 若出错,则计数并复位软盘并重试 CMP SI,5 JAE error ; 累计错误5次SI >= 5 跳转到error MOV AH,0x00 MOV DL,0x00 ; A驱动器 INT 0x13 ; 重置驱动器 JMP retry next: MOV AX,ES ; 把ES地址后移0x0020,相当于[ES:BX]后移0x200 = 512 ,即512B(一个扇区大小),之后[ES:BX]就是下一个扇区的装入地址 ADD AX,0x0020 MOV ES,AX ; 段寄存器无法直接与立即数运算,所以用AX中转 ADD CL,1 CMP CL,18 JBE readloop ; 如果CL <= 18 跳转readloop装入下一个扇区 MOV CL,1 ADD DH,1 ; DH表示磁头号 CMP DH,2 JB readloop ; 如果DH < 2,则跳转到readloop MOV DH,0 ADD CH,1 ; CH表示柱面号 CMP CH,CYLS JB readloop ; 如果CH < CYLS,则跳转到readloop ;截止此处,已经把软盘最初的10(柱面) × 2(磁头) × 18(扇区) × 512B = 184320Byte = 180KB内容完整无误地装载到内存里了 ; 读取结束,运行haribote.sys! MOV [0x0ff0],CH ; 记录IPL实际读取了多少柱面 JMP 0xc200 ; C200 = 8000 + 4200 ; 向一个空软盘保存文件时,文件的内容会写在0x004200以后的地方,这个偏移量是相对于C0-H0-S1的 ; C0-H0-S2从8200开始装入,等价于C0-H0-S1从8000开始装入,因此是用8000来加4200 ;以下为错误处理 error: MOV SI,msg putloop: MOV AL,[SI] ;将msg的地址传给AL ADD SI,1 CMP AL,0 JE fin MOV AH,0x0e ; 显示一个文字,AH向INT10传入模式,AL传入字符地址 MOV BX,15 ; 指定字符颜色 INT 0x10 ; 调用显卡BIOS JMP putloop fin: HLT ; 让CPU停止,等待指令 JMP fin ; 无限循环 msg: DB 0x0a, 0x0a ; 0x0a为换行 DB "load error" DB 0x0a ; 换行 DB 0 RESB 0x7dfe-$ ; 到0x7dfe为止用0x00填充的指令 DB 0x55, 0xaa ;规定:启动区最后两个字节为55aa
四、附录:
图1:ipl10.bin的二进制文件内容:

表1:内存地图
+———————————————— 0x0
| Interrupts vectors(中断向量表)
+———————————————— 0x400
| BIOS data area(BIOS的数据区域)
+———————————————— 0x5??
| OS load area(操作系统加载区域)
+———————————————— 0x7C00
| Boot sector(引导区域)
+———————————————— 0x7E00
| Boot data/stack(引导数据/堆栈)
+———————————————— 0x7FFF
| (not used)
+———————————————— (…)
表2:内存地图(ipl执行后)
+———————————————— 0x0
| Interrupts vectors(中断向量表)
+———————————————— 0x400
| BIOS data area(BIOS的数据区域)
+———————————————— 0x5??
| OS load area(操作系统加载区域)
+———————————————— 0x7C00
| ipl
+———————————————— 0x7E00
| Boot data/stack(引导数据/堆栈)
+———————————————— 0x7FFF
| (not used)
+———————————————— 0x81FF
| C0-H0-S2和之后的扇区的内容
+———————————————— (...)

浙公网安备 33010602011771号