首先对ipl.nas进行修改:
; haribote-ipl
; TAB=4
ORG 0x7c00 ; 这个程序被读入哪里
; 以下是标准FAT12格式软盘的描述
JMP entry
DB 0x90
DB "HARIBOTE" ; 可以自由地写引导扇区的名字 (8字节)
DW 512 ; 1扇区的大小 (必须是512字节)
DB 1 ; 簇的大小 (必须是一个扇区)
DW 1 ; FAT从哪里开始 (一般从第1扇区开始)
DB 2 ; FAT的个数 (必须是2)
DW 224 ; 根目录的大小(一般为224个项)
DW 2880 ; 这个磁盘驱动器的大小 (必须是2880扇区)
DB 0xf0 ; 磁盘类型
DW 9 ; FAT的长度(必须是9个扇区)
DW 18 ; 1个磁道有几个扇区 (必须是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
MOV SP,0x7c00
MOV DS,AX
; 阅读光盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV AH,0x02 ; AH=0x02: 读盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JC error
; 虽然读完了,但是因为没什么要做的所以睡觉
fin:
HIL ; 让CPU处于休眠,等待指令
JMP fin ; 无限循环
error:
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
msg:
DB 0x0a,0x0a ; 两个换行
DB "load error"
DB 0x0a ; 一个换行
DB 0
RESB 0x7dfe-$ ; 填写0x00,直到0x7dfe
DB 0x55,0xaa
所谓的JC是指,当进位标志是1的时候就跳转
磁盘读、写、扇区校验、寻道:
AH=0x02; —— 读盘
AH=0x03; —— 写盘
AH=0x04; —— 校验
AH=0x0c; —— 寻道
AL=处理对象的扇区数(只能处理连续的扇区)
CH=柱面号&0xff;
CL=扇区号(0-5位)|(柱面号&0x300)>>2
DH=磁头号
DL=驱动器号
ES:BX=缓冲地址(校验及寻道时不使用)
返回值:
FLACS.CF0: 没有错误,AH0
FLACS.CF==1: 有错误,错误号码存入AH内(与重置功能一样)
以下这几个寄存器:
CH对应柱面号;
CL对应扇区号;
DH对应磁头号
DL对应驱动器号
在上面的程序中,柱面号是0,磁头号是0,扇区号是2,磁盘号是2
本书提供的软盘示意图:
虽说,软盘现在已被淘汰。但在这里还是记一下:
1张软盘有80个柱面,2个磁头,18个扇区,且一个扇区有512个字节
缓冲区地址:这是一个内存地址,表明我们要把从软盘上读出的数据装载到内存中的哪个位置。
但是问题在于,用一个寄存器来表示内存地址显然不够,BX作为16位寄存器,最大才64K。在设计BIOS的时代CPU甚至没有32位寄存器,人们只好设计了起辅助作用的段寄存器,用于协助指定内存地址,在使用的时候,以ES:BX这种方式来表示地址
[ES:BX] 代表了ES*16+BX的内存地址
不管我们要指定内存的什么地址,都必须同时指定段寄存器,一般如果省略的话就会把“DS:“作为默认的段寄存器。就比如说,以前我们用的"MOV CX,[1234]” ,其实是"MOV CX,[DS:1234]"的意思。而"MOV AL,[SI]"也就是"MOV AL,[DS:SI]"的意思。在汇编语言中,如果每日会都这么写就太麻烦了,所以可以省略默认的段寄存器DS,正因为如此DS必须预先指定为0,否则会引起混乱。
再对ipl.nas进行改动:
; haribote-ipl
; TAB=4
ORG 0x7c00 ; 这个程序被读入哪里(指明程序的装载地址)
; 以下是标准FAT12格式软盘的描述
JMP entry
DB 0x90
DB "HARIBOTE" ; 引导扇区的名字可以是任意字符串(8字节)
DW 512 ; 每个扇区的大小(必须为512字节)
DB 1 ; 簇的大小(必须是一个扇区)
DW 1 ; FAT的起始位置(一般从第一个扇区开始)
DB 2 ; FAT的个数(必须是2)
DW 224 ; 根目录区域的大小(一般为224项)
DW 2880 ; 该磁盘的大小(必须是2880扇区)
DB 0xf0 ; 磁盘种类(必须为0xf0)
DW 9 ; FAT区域的长度(必须为9个扇区)
DW 18 ; 1个磁道有几个扇区(必须是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
MOV SP,0x7c00
MOV DS,AX
; 读取软盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 磁盘BIOS调用
JNC fin ; 如果没出错的话就跳转到fin
ADD SI,1 ; SI加1
CMP SI,5 ; SI与5比较
JAE error ; SI >= 5的话进入error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
; 虽然读完了,但是因为没什么要做的所以睡觉
fin:
HLT ; 让CPU休眠,等待指令
JMP fin ; 无限循环
error:
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
msg:
DB 0x0a, 0x0a ; 换行2次
DB "load error"
DB 0x0a ; 换行1次
DB 0
RESB 0x7dfe-$ ; 填写0x00,直到0x07dfe
DB 0x55, 0xaa
指令JNC:进位标志是0就跳转
指令JAE:大于或等于时跳转
指令JBE:小于等于则跳转
这三个语句加在一起的作用是复位软盘状态:
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
然后,我们再次修改ipl.nas:
; haribote-ipl
; TAB=4
ORG 0x7c00
; 以下是标准FAT12格式软盘的描述
JMP entry
DB 0x90
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 ; 寄存器初始化
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 读取软盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV DL,2 ; 扇区2
readloop:
MOV SI,0 ; 计算失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 磁盘读取
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC next ; 如果没有错误的话进入next
ADD SI,1 ; SI加1
CMP SI,5 ; 比较SI和5
JAE error ; SI>=5的话进入error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES,0x020的命令,所以这里绕了个弯
ADD CL,1 ; CL加1
CMP CL,18 ; 比较SI和18
JBE readloop ; 如果CL<= 18的话去readloop
; 虽然读完了,但是因为没什么要做的所以睡觉
fin:
HLT ; 使CPU处于休眠,等待信号
JMP fin ; 无限循环
error:
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个字符
MOV BX,15 ; 彩色编码
INT 0x10 ; 磁盘BIOS调用
JMP putloop
msg:
DB 0x0a, 0x0a ; 两个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 用0x00填充,直到0x7dfe
DB 0x55,0xaa
再一次修改ipl.nas:
; haribote-ipl
; TAB=4
CYLS EQU 10 ; 读入到哪里
ORG 0x7c00 ; 指明程序的装载地址
; 以下是标准FAT12格式软盘的描述
JMP entry
DB 0x90
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 ; 寄存器初始化
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 读取软盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
readloop:
MOV SI,0 ; 计算失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 磁盘读取
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC next ; 如果没有错误的话进入next
ADD SI,1 ; SI加1
CMP SI,5 ; 比较SI和5
JAE error ; SI>=5的话进入error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES,0x020的命令,所以这里绕了个弯
ADD CL,1 ; CL加1
CMP CL,18 ; 比较SI和18
JBE readloop ; 如果CL<= 18的话去readloop
MOV CL,1
ADD DH,1
CMP DH,2
JB readloop ; DH<2的话进入readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readloop ; 如果CH小于CYLS,则跳转到readloop
; 虽然读完了,但是因为没什么要做的所以睡觉
fin:
HLT ; 使CPU处于休眠,等待信号
JMP fin ; 无限循环
error:
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个字符
MOV BX,15 ; 彩色编码
INT 0x10 ; 磁盘BIOS调用
JMP putloop
msg:
DB 0x0a, 0x0a ; 两个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 用0x00填充,直到0x7dfe
DB 0x55,0xaa
指令JB:如果小于的话就跳转
cylinder可以译为“柱面”。“CYLS EQU 10” 的意思是CYLS=10,相当于C语言的#define命令,用来声明常数
1月12日更新:
作者通过haribote.img这个文件,在书中说明:
一般向一个空软盘保存文件时:
(1)文件名会写在0x002600以后的地方
(2)文件名会写在0x004200以后的地方
之后修改了ipl.nas,haribote.nas这两个文件。
关于如何设置显卡模式的问题,书上是这么说的:
AH=0x00
AL=模式:
0x03 : 16色字符模式,8025
0x12 : VGA图形模式,6404804位彩色模式,独特的4面存储模式
0x6a : 扩展VGA图形模式,800600*4位彩色模式,独特的4面存储模式
返回值:无
作者提到的有一点我给记下来:
CPU的自我保护功能(识别出可疑的机器语言并进行屏蔽以免破坏系统)在16位下不能使用,在32位的条件下能用。
但是BIOS是用16位的机器语言书写的。一旦使用32位模式就不能调用BIOS功能了,解决的思路就是把用BIOS函数的工作全部放在开头先做,因为一旦进入32位模式就不能调用BIOS函数了。
VRAM指的是显卡内存,也就是用来显示画面的内存,它的各个地址都对应着画面上的像素,可以利用这一机制在画面上绘出五彩缤纷的图案。顺带说,VRAM分布在内存分布图上好几个不同的地方
根据书上的说法,总结一下bootpack.c变成机器语言的过程:
为了让电脑处于HALT状态,编写了naskfunc.nas:
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 制作目标文件的模式,之所以采用这一模式,是因为用汇编写的函数还要和bootpack.obj链接因此也需要编译成目标文件
[BITS 32] ; 制作32位模式用的机器语言
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt ; 程序中包含的函数名(在CPU的指令中,HLT指令也属于I/O指令)
; 需要链接的函数名,都需要用GLOBAL声明
; 以下是实际的函数
[SECTION .text] ; 目标文件中写了这些之后再写程序
_io_hlt: ; void io_hlt(void)
HLT
RET ; 这个相当于C语言中的return,意思是"函数的处理到此结束,返回吧”
里面的io_hlt函数在C语言中是这么使用的:
//告诉C编译器,有一个函数在别的文件里
void io_hlt(void);
//在上面直接用了分号,意思是: 函数在别的文件中,你自己找一下吧
void HariMain(void)
{
fin:
io_hlt(); //执行naskfunc.nas里的_io_hlt
goto fin; //我想在这里加入HLT,但是c语言不能使用HLT!
}