今天作者写了一个真正载入内存的IPL,用来讲磁盘上的数据载入内存当中
载入的程序节选是
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 ; 没出错则跳转到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
next:
MOV AX,ES ; 把内存地址后移0x200(512/16十六进制转换)
ADD AX,0x0020
MOV ES,AX ; ADD ES,0x020因为没有ADD ES,只能通过AX进行
ADD CL,1 ; 往CL里面加1
CMP CL,18 ; 比较CL与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
; 读取完毕,跳转到haribote.sys执行!
MOV [0x0ff0],CH ;
JMP 0xc200
这段程序就就通过读取磁盘上的文件,载入到0x8200往后的地址上面,最后跳转掉0xC200去执行。
上面虽然写的是0x820,这个数据是存在段寄存器上面的,在进行解析的时候是要默认*16,也就是0x8200。在书中有提到要将程序载入到内存0x8000哪里去,但现在的程序确实0x8200,开始满懵逼的,原来仔细看程序它是从第二个扇区开始读取数据,所以地址要写0x8200。如果上面是从第一个扇区读取数据的话,也就是语句MOV CL,2 ; 扇区2改成
MOV CL,1 ; 扇区1,那么MOV AX,0x0820就要改成0x800。
上面的INT 0x13就是调用BIOS的中段函数,他的格式如下:
BIOS中断INT 0x13中,
ah=0x02,即为读磁盘扇区到内存。
al=需要读出的扇区数;
ch=磁道号的低八位;
cl=开始扇区(位0—5),磁道号高二位(位6—7)
dh=磁头号
dl=驱动器号(若是硬盘则要置位7)
es:dx—>指向数据缓冲区
若出错则CF示志置位
上面的程序只是将磁盘载入内存,载入内存之后,执行JMP 0xc200跳转到磁盘里面的程序进行运行,执行asmhead.nas里面的程序
BOTPAK EQU 0x00280000 ; 加载bootpack
DSKCAC EQU 0x00100000 ; 磁盘缓存的位置
DSKCAC0 EQU 0x00008000 ; 磁盘缓存的位置(实模式)
; BOOT_INFO相关
CYLS EQU 0x0ff0 ; 引导扇区设置
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色的信息
SCRNX EQU 0x0ff4 ; 分辨率X
SCRNY EQU 0x0ff6 ; 分辨率Y
VRAM EQU 0x0ff8 ; 图像缓冲区的起始地址
ORG 0xc200 ; 这个的程序要被装载的内存地址
; 画面モードを設定
MOV AL,0x13 ; VGA显卡,320x200x8bit
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 屏幕的模式(参考C语言的引用)
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 通过BIOS获取指示灯状态
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
; 防止PIC接受所有中断
; AT兼容机的规范、PIC初始化
; 然后之前在CLI不做任何事就挂起
; PIC在同意后初始化
MOV AL,0xff
OUT 0x21,AL
NOP ; 不断执行OUT指令
OUT 0xa1,AL
CLI ; 进一步中断CPU
; 让CPU支持1M以上内存、设置A20GATE
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
; 保护模式转换
[INSTRSET "i486p"] ; 说明使用486指令
LGDT [GDTR0] ; 设置临时GDT
MOV EAX,CR0
AND EAX,0x7fffffff ; 使用bit31(禁用分页)
OR EAX,0x00000001 ; bit0到1转换(保护模式过渡)
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 写32bit的段
MOV DS,AX
MOV ES,AX
MOV FS,AX
MOV GS,AX
MOV SS,AX
; bootpack传递
MOV ESI,bootpack ; 源
MOV EDI,BOTPAK ; 目标
MOV ECX,512*1024/4
CALL memcpy
; 传输磁盘数据
; 从引导区开始
MOV ESI,0x7c00 ; 源
MOV EDI,DSKCAC ; 目标
MOV ECX,512/4
CALL memcpy
; 剩余的全部
MOV ESI,DSKCAC0+512 ; 源
MOV EDI,DSKCAC+512 ; 目标
MOV ECX,0
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4 ; 除以4得到字节数
SUB ECX,512/4 ; IPL偏移量
CALL memcpy
; 由于还需要asmhead才能完成
; 完成其余的bootpack任务
; bootpack启动
MOV EBX,BOTPAK
MOV ECX,[EBX+16]
ADD ECX,3 ; ECX += 3;
SHR ECX,2 ; ECX /= 4;
JZ skip ; 传输完成
MOV ESI,[EBX+20] ; 源
ADD ESI,EBX
MOV EDI,[EBX+12] ; 目标
CALL memcpy
skip:
MOV ESP,[EBX+12] ; 堆栈的初始化
JMP DWORD 2*8:0x0000001b
waitkbdout:
IN AL,0x64
AND AL,0x02
JNZ waitkbdout ; AND结果不为0跳转到waitkbdout
RET
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy ; 运算结果不为0跳转到memcpy
RET
; memcpy地址前缀大小
ALIGNB 16
GDT0:
RESB 8 ; 初始值
DW 0xffff,0x0000,0x9200,0x00cf ; 写32bit位段寄存器
DW 0xffff,0x0000,0x9a28,0x0047 ; 可执行的文件的32bit寄存器(bootpack用)
DW 0
GDTR0:
DW 8*3-1
DD GDT0
ALIGNB 16
bootpack:
INT 0x10
AH = 00H --设置显示的格式
调用参数:
AL = 00H 40 × 25 黑白文本,16级灰度
AL = 01H 40 × 25 16色文本
AL = 02H 80 × 25 黑白文本,16级灰度
AL = 03H 80 × 25 16色文本
AL = 04H 320 × 200 4色图形
AL = 05H 320 × 200 黑白图形,4色灰度
AL = 06H 640 × 200 黑白图形
AL = 07H 80 × 25 黑白文本
AL = 08H 160 × 200 16色图形(MCGA)
AL = 09H 320 × 200 16色图形(MCGA)
AL = 0AH 640 × 200 4色图形(MCGA)
AL = 0DH 320 × 200 16色图形(EGA/VGA)
AL = 0EH 640 × 200 16色图形(EGA/VGA)
AL = 0FH 640 × 350 单色图形(EGA/VGA)
AL = 0DH 320 × 200 16色图形(EGA/VGA)
AL = 0DH 320 × 200 16色图形(EGA/VGA)
AL = 0E 640 × 200 16色图形(EGA/VGA)H
AL = 0F 640 × 350 单色图形(EGA/VGA)H
AL = 11H 640 × 480 单色图形(VGA)
AL = 12H 640 × 480 16色图形(VGA)
AL = 13H 320 × 200 256色图形(VGA)
屏幕的显存地址是0x000a0000
对上面程序的小笔记
1)PIC:可编程中断控制器,是微处器与外设之间的中断处理的桥梁,用于处理由外设发出的中断请求。一般用的是8259芯片
初始化的时候为了防止芯片处理中断函数,所以要先屏蔽掉中断。
2)GDT:
关于GDT和IDT这里写的很好
http://www.techbulo.com/708.html
3)设置A20GATE
又是实模式下,芯片寻址只有20根,也就是1M的寻址能力,如果要想超过1M那就需要时能第21根地址先,也就是A20
MOV AL,0xdf ; enable A20
OUT 0x60,AL
这两个语句就是用来使能A20的
4)要进入保护模式,只要设置CR0寄存器的最高位为0,最低位为1,则可以进入保护模式。
5)作者将引导程序赋值到了0X10000的地址上面去了
; 从引导区开始
MOV ESI,0x7c00 ; 源
MOV EDI,DSKCAC ; 目标
MOV ECX,512/4
CALL memcpy
; 剩余的全部
MOV ESI,DSKCAC0+512 ; 源
MOV EDI,DSKCAC+512 ; 目标
MOV ECX,0
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4 ; 除以4得到字节数
SUB ECX,512/4 ; IPL偏移量
CALL memcpy
6)对于bootpac.c的复制
MOV ESI,bootpack ; 源
MOV EDI,BOTPAK ; 目标
MOV ECX,512*1024/4
CALL memcpy
这里作者将这个.c文件生成的程序复制到了0x280000哪里去了。
这个bootpack是在末尾哪里定义的符号,它就是在asmhead.nas最后面定义的。而在makefile里面我们看到
bootpack.hrb : bootpack.bim Makefile
$(BIM2HRB) bootpack.bim bootpack.hrb 0
haribote.sys : asmhead.bin bootpack.hrb Makefile
copy /B asmhead.bin+bootpack.hrb haribote.sys
bootpack.hrb就是挨着asmhead.bin进行连接的,所以在asmhead.nas中定义的符号bootpack就是指向bootpack.hrb
7)程序最后跳转到 JMP DWORD 28:0x0000001b去执行程序了。
为什么跳转到这个地址,刚开始我也不解,看了好久才有点明白,上面的28其实就是选择GDT的第二个引索。
GDT0:
RESB 8 ; 这个是0个GDT引索
DW 0xffff,0x0000,0x9200,0x00cf ; 这个是第1个
DW 0xffff,0x0000,0x9a28,0x0047 ; 这个是2个
DW 0
GDTR0:
DW 8*3-1
DD GDT0
ALIGNB 16
现在来看看GDT的64位段信息,并将第二段信息带入
0~15 --限制长度的低16位 --0xffff
16~31 --基地址低16位 --0000
32~39 -基地址中间8位 --28(因为是小端存储,所以28在前面)
40~47 --权限 --9a
48~55 --限制长度高8位 --cf
56~63 --基地址最高8位 --00
根据上面所得,基地址是0x280000,限制长度是0xcfffff,权限是9a,查资料是可执行的意思
至于为什么要跳转到0x1b的话,得看二进制文件可得
这样系统就进入bootpac.c程序里面去跑了。