1、读盘
昨天写的IPL并没有装载程序,今天我们来写一个可以真正装载程序的IPL。
我们先来看软盘结构。
如图所示。一张软件有正反两面,对应读取用的磁头(0,1),而从外到内又分为80个环(0~79),称为柱面。每个柱面又分为18个扇区(1~18)。因为软盘的第一个扇区(正面的第0个柱面的第1个扇区)为启动区,所以我们读软盘的时候应该从第2个扇区开始读。
org 0x7c00
jmp init
DB 0x90
DB "HARIBOTE"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0,0,0x29
DD 0xffffffff
DB "HARIBOTEOS "
DB "FAT12 "
RESB 18
init:
mov bx,0
mov ax,0x0820
mov es,ax ;es:bx为缓冲地址,即读取的数据将存取在这里
mov dl,0 ;驱动器号0(即光驱号,现在一般只有一个光驱)
mov dh,0 ;磁头号0~1
mov ch,0 ;柱面号0~79
mov cl,2 ;扇区号1~18
mov al,1 ;要读的扇区数
mov si,0 ;读盘错误次数
read:
mov ah,0x02 ;读盘
int 0x13 ;中断,BIOS的0x13号函数
jnc finish ;jnc指令(jump if not carry),如果上一步没错误,则cf标志位为0,当cf = 0时,跳转到fin
inc si ;错误次数加1
cmp si,5
jae error ;jae指令(jump if above or equal)
;重置驱动器
mov ah,0
mov dl,0
int 0x13 ;重置驱动器
jmp read
finish:
hlt
jmp finish
error:
mov ax,0
mov ds,ax
mov si,msg
mov di,msg
mov ah,0x0e
mov bx,15
show:
mov al,[di]
inc di
cmp al,0
je finish
int 0x10
jmp show
msg:
db 0x0a,0x0a
db "5 times,failed!!"
db 0x0a
db 0
resb 0x7dfe - $
db 0x55,0xaa
读软盘时所需要设置的寄存器如下:
ah=0x02(读盘)
al=n(表示要读的扇区的个数)
ch=柱面号
cl=扇区号
dh=磁头号
dl=驱动器号(现在一般只有一个驱动器,故只要为0就OK了~)
es:bx=缓冲地址(即将讲到的数据存至内存的这个位置)
int 0x13的返回值:cf = 0 或cf = 1,有错误则cf标志位为1;无错误则为0。
至于我们设定的将数据装载到es=0x0820,bx=0,即内存0x8200处,是我们随意设定的,因为0x7c00~0x7dff为启动区,而在0x7e00后到0x9fbff都是没有特别用途的,我们可以随便使用。
因为软件这东西太不靠谱了,所以很容易出错,我们设定最多读5次,否则就提示错误。
好了,现在我们已经读了一个扇区了,接下来,我们将程序改写成能读入10个柱面。
cyls equ 10 ;#define cyls 10
org 0x7c00
jmp init
DB 0x90
DB "HARIBOTE"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0,0,0x29
DD 0xffffffff
DB "HARIBOTEOS "
DB "FAT12 "
RESB 18
init:
mov ax,0
mov ss,ax
mov sp,0x7c00
mov ds,ax
mov bx,0
mov ax,0x0820
mov es,ax ;es:bx为缓冲地址,即读取的数据将存取在这里
mov dl,0 ;驱动器号0(即光驱号,现在一般只有一个光驱)
mov dh,0 ;磁头号0~1
mov ch,0 ;柱面号0~79
mov cl,2 ;扇区号1~18
resetsi:
mov si,0 ;读盘错误次数
read:
mov al,1 ;要读的扇区数
mov bx,0
mov dl,0
mov ah,0x02 ;读盘
int 0x13 ;中断,BIOS的0x13号函数
jnc setnext ;没有出错则设置下一个要读的地方
add si,1 ;错误次数加1
cmp si,5
jae error ;jae指令(jump if above or equal)
;重置驱动器
mov ah,0
mov dl,0
int 0x13 ;重置驱动器
jmp read
setnext:
mov ax,es
add ax,0x0020
mov es,ax ;es加512字节
add cl,1
cmp cl,18
jbe resetsi ;jbe:jump if below or equal
mov cl,1
add dh,1
cmp dh,2
jb resetsi ;jb:jump if below
mov dh,0
add ch,1
cmp ch,cyls
jb resetsi
finish:
hlt
jmp finish
error:
mov ax,0
mov ds,ax
mov si,msg
mov di,msg
mov ah,0x0e
mov bx,15
show:
mov al,[di]
inc di
cmp al,0
je finish
int 0x10
jmp show
msg:
db 0x0a,0x0a
db "5 times,failed!!"
db 0x0a
db 0
resb 0x7dfe - $
db 0x55,0xaa
现在,由程序中我们读软盘的顺序是:柱面->正反面->扇区。
2、从启动区执行操作系统
启动区现在我们已经写好了,那么操作系统呢?我们可以写个最简单的.nas文件
finish:
hlt
jmp finish
将其保存成os.nas文件,然后将它编译后成os.sys文件,最后将其保存到映像里。一般向一个空软盘保存文件时,
(1)文件名会写在0x002600以后的地方;
(2)文件的内容会写在0x004200以后的地方。
知道了这个,我们就可以在启动区执行这个操作系统了。假设我们把磁盘上的内容装载到内存0x8000,那么磁盘0x4200处的内容就应该位于内存0x8000 + 0x4200 = 0xc200处,所以我们在os.nas里加上org 0xc200,在ipl.nas最后加上jmp 0xc200,那么,这个最简单的操作系统就会执行了。
3、32位模式前期准备
所谓32位模式,指的是CPU的模式。CPU有16位和32位两种模式,在32位模式下不能启用BIOS,这是因为BIOS是用16位机器语言写的,如果我们有什么事情想要用BIOS来做,那就将其全部放在开头先做。我们将会用到int 0x13,表示使用VGA图形模式的320 * 200 * 8位彩色模式。即会有200行,320列的像素,每位像素可以在256种颜色中选 一种。另外,如果要在画面上显示东西,那么就要往显卡内存里写数据。在VGA模式下,显卡内存为0xa0000 ~ 0xaffff的64kb,每个地址都对应着画面上的像素。
4、导入C语言
导入C语言其实就是将.c文件编译成.obj文件并链接后,将其于我们写好的汇编文件合并。具体的我们就不关注了。
5、C语言调用汇编函数
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ;将输出格式设定为WCOFF模式
[INSTRSET "i486p"] ;告诉编译器此程序给486用
[BITS 32] ;制作32位模式用的机器语言
[FILE "naskfunc.nas"] ;源文件名信息,必须在定义函数名前写上
GLOBAL _io_hlt,_write_mem8 ;需要链接的函数名,都要用global指令声明,函数名必须以下划线开关
[SECTION .text] ;在写函数前必须写上这句后再写函数
_io_hlt: ; void io_hlt(void);
HLT
RET
_write_mem8: ; void write_mem8(int addr,int data)
mov ecx,[esp + 4]
mov al,[esp + 8]
mov [ecx],al
ret
其中,ret与C语言中的return类似。如果要传参数的话,那么,第一个参数存放的地址是esp + 4,第二个参数存放的地址是esp + 8,第三个是esp + 12,以此类推。
那么,在.c文件中如何调用呢?
void io_hlt(void);
void write_mem8(int addr,int data);
void HariMain(void)
{
int i;
for(i = 0xa0000;i <= 0xaffff;i++)
write_mem8(i,i & 0x0f);
while(1)
io_hlt();
}
先声明函数,然后再调用。