先开个头,以后的棋一步一步下。
先说几句题外话:下面是《30天自制操作系统》引导扇区的代码,略作修改,并且加了大量注释,让我们RTFSC!
CYLS EQU 10 ; 定义变量,将要读取的柱面数量 ORG 0x7c00 ; 指明程序载入到内存的地址,7c00是魔法数字,人为规定的,没什么道理好讲 ; 以下这段是标准的FAT12格式软盘用的代码 JMP entry ; 跳到下面执行入口entry DB 0x90 ; 写一个字节,DW是写2个字节,DD是写4个字节 DB "HARIBOTE" ; 启动区的名称,必须8字节 DW 512 ; 每个扇区的大小,必须512字节 DB 1 ; 簇的大小,必须1个扇区 DW 1 ; FAT的起始位置,通常从第一个扇区开始 DB 2 ; FAT的个数,必须为2 DW 224 ; 根目录大小,通常为224 DW 2880 ; 该磁盘的大小,必须为2880扇区 DB 0xf0 ; 磁盘的种类,必须是0Xf0 DW 9 ; FAT的长度,必须是9扇区 DW 18 ; 一个磁道有几个扇区,必须是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 ; 将0赋值给AX寄存器,目的其实是初始化后面的SS和DS寄存器 ; 为什么要初始化DS寄存器?因为使用内存时默认都要使用DS段寄存器作为基址,比如MOV AX,[0xFF](表示将内存地址0xFF存储的数据赋值给AX)实际是MOV AX,[DS:0xFF],DS默认省略了。 ; [DS:0xFF]表示DS*16+0xFF所指地址存储的数据 ; 段寄存器不能通过MOV DS, 0这种形式(没有这种机器码),为了初始化段寄存器,不得不通过AX间接来初始化,以后赋值也必须通过寄存器间接赋值 MOV SS,AX ; 初始化SS寄存器 MOV SP,0x7c00 ; 栈指针初始化为与内存起始位置一致,为什么?我也不知道。 MOV DS,AX ; 初始化DS寄存器 ; 下面这段的功能是读磁盘,读磁盘需要调用BIOS的中断来实现,中断相当于一个系列的函数,都是围绕某个功能,比如下面这个13中断,都是与磁盘相关,具体是度还是写,就要看传什么参数。 ; BIOS的中断的调用方式跟函数相似,传参数,调用,取返回值。中断的参数和返回值一般都放在特定寄存器中,所以调用中断之前,先要向特定寄存器传递值。 ; 下面要用到INT13中断,这个中断的说明如下: ; 直接磁盘服务(Direct Disk Service——INT 13H) ; 功能00H ; 功能描述:磁盘系统复位 ; 入口参数:AH=00H ; DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘 ; 出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明 ; 功能02H ; 功能描述:读扇区 ; 入口参数:AH=02H ; AL=扇区数 ; CH=柱面 ; CL=扇区 ; DH=磁头 ; DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘 ; ES:BX=缓冲区(内存)的地址 ; 出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码, ; 初始化BIOS的参数 MOV AX,0x0800 MOV ES,AX ; 初始化ES段为0800,而作为基址ES要*16,所以实际上0x0800*16=0x8000,为什么要放到内存0x8000的位置?因为这段是空的。。 MOV CH,0 ; 柱面从0开始读 MOV DH,0 ; 磁头从0开始读 MOV CL,1 ; 扇区从1开始读。。。因为第一个就是1而不是0 readloop: MOV SI,0 ; 初始化SI,SI用作记录失败次数 retry: MOV AH,0x02 ; AH=0x02 : AH设置为0x02表示读取磁盘 MOV AL,1 ; 要读取的扇区数 MOV BX,0 ; ES:BX表示读到内存的地址 0x0800*16 + 0 = 0x8000 MOV DL,0x00 ; 驱动器号,0表示第一个软盘,是的,软盘。。 INT 0x13 ; 调用BIOS 13号中断,磁盘相关功能 JNC next ; 未出错则跳转到next,出错的话则会使EFLAGS寄存器的CF位置1 ADD SI,1 ; SI加1,表示出错次数加1 CMP SI,5 ; SI与5比较 JAE error ; SI >= 5 则跳转到error MOV AH,0x00 ; AH=0x00 表示磁盘系统复位 MOV DL,0x00 ; 复位第一个软盘,我们也就一块软盘 INT 0x13 ; 调用BIOS 13号中断,磁盘相关功能 JMP retry next: MOV AX,ES ADD AX,0x0020 MOV ES,AX ; 上面三个指令的目的就是 ADD ES,0x020,但是没有这种指令,所以只能通过AX间接操作 ; ES+0x020 那么ES:BX=ES*16+BX=0x020*16+BX=0X200+BX=512+BX 相当于放到内存的位置往后移了一个扇区的大小 ADD CL,1 ; 扇区位置加1 CMP CL,18 ; 与18比较,意思就是读到第18个扇区,为什么是18个扇区?请看文后图 JBE readloop ; CL <= 18 跳到readloop MOV CL,1 ; 重读第一个扇区 ADD DH,1 ; 磁头加1,表示读完第一个磁头的18个扇区读第二个磁头的18个扇区 CMP DH,2 JB readloop ; MOV DH,0 ; 重读第一个磁头 ADD CH,1 ; 柱面加1 CMP CH,CYLS ; 按照读完扇面换磁头,读完磁头再换柱面的顺序循环读取 JB readloop ; ; MOV [0x0ff0],CH ; 将读取的柱面数保存到内存地址0x0ff0处 JMP 0xc200 ; 跳转到内存的0xc200,0xc200是从哪来的?0xc200是0x8000+0x4200,0x8000是软盘读取到内存的起始地址,那么0x4200就是指软盘的0x4200位置 ; 因为软盘是FAT12格式,通过找资料,知道软盘中保存的第一个文件的起始地址(也就是数据区)的位置 ; 是隐藏扇区+保留扇区+FAT表数*FAT表所占扇区+根目录所占扇区=0+1+2*9+14=33,每个扇区是512字节,33*512=16896,换算成16进制正好是0x4200。 ; 所以这里的跳转就是跳到软盘里保存的第一个文件的位置。 ; 下面这段是用来在屏幕上显示错误信息的 ; 显示服务(Video Service——INT 10H) ; 功能0EH ; 功能描述:在Teletype模式下显示字符 ; 入口参数:AH=0EH ; AL=字符 ; BH=页码 ; BL=前景色(图形模式) ; 出口参数:无 error: MOV SI,msg ; 将错误信息地址赋值给SI寄存器 putloop: MOV AL,[SI] ; 根据地址取出一个字节数据 ADD SI,1 ; SI加1 CMP AL,0 ; 如果数据为0则结束 JE fin MOV AH,0x0e ; MOV BX,15 ; 设置颜色 INT 0x10 ; 上面三行为调用BIOS第10号中断,AH为0x0e时为显示字符功能,BH为页码,BL为前景色 JMP putloop fin: HLT ; CPU待机 JMP fin ; 无限循环 msg: DB 0x0a, 0x0a ; 换两行 DB "load error" DB 0x0a ; 换行 DB 0 RESB 0x7dfe-$ ; 0x7dfe 是0x7c00+510 DB 0x55, 0xaa ; 最后两位55aa表示这个扇区是启动区