前面我们实现了引导区,下面我们继续完善磁盘磁道的读写。

Day02 - 读磁盘

我们先把上一篇文章的 helloos.nas 改写一下,让他更像汇编语言的编写方式,并重命名为 ipl.nas。ipl = initial progrom loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
; hello-os
; TAB=4

ORG 0x7c00 ; 指明程序的装载地址

; 以下这段是标准FAT12格式软盘专用的代码

JMP entry
DB 0x90
DB "HELLOIPL" ; 启动区的名称可以是任意的字符串(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 "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称(8字节)
RESB 18 ; 先空出18字节

; 程序核心

entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX

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
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无线循环

msg:
DB 0x0a, 0x0a ; 换行2次
DB "hello, world"
DB 0x0a ; 换行
DB 0

RESB 0x7dfe-$ ; 填写0x00,直到0x7dfe

DB 0x55, 0xaa

; 以下是启动区以外部分的输出

DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432

这段代码没啥好多说了,结构都在上一篇文章里了,就是用0x10中断来显示字符罢了。

AL = 0x03 16色字符模式 80 * 25
AL = 0x12 VGA图形模式 640 480 4
AL = 0x13 VGA图形模式 320 200 8
AL = 0x6a 扩展VGA图形模式 800 600 4

读磁盘

然后我改写 entry 段,加入磁盘读写的部分。新 entry 段代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,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:
HLT
JMP fin
error:
MOV SI, msg

下图是磁盘的结构:

1

柱面(cylinder)就是上图中「深黑色」的一圈一圈圆环状的区域,从外向内分别为柱面0、柱面1……柱面79,一共80个柱面。

磁头是一种针状的磁性设备,它可以从正反两面接触磁盘。软盘磁盘正反两面都能记录数据,因此我们有正反两个磁头,分别是磁头0号和磁头1号。

扇区(sector)就是把一个圆环(柱面),均匀分成几等份,每一份称为一个扇区。一个圆环有18个扇区,分别称为扇区1、扇区2……扇区18。

所以一张磁盘的容量为:80×2×18×512 = 1 474 560B = 1440KB。

我们制作的IPL启动区位于C0-H0-S1(柱面0,磁头0,扇区1),上面的代码就是读取第二个扇区到内存0x0820处。

出错重试

为了提升我们的操作系统的稳定性,在读磁盘出问题的时候,我们要重新读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,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
JMP fin
error:
MOV SI, msg

这里需要我们掌握的就是磁盘重置的代码(AH = 0x02是读磁盘,AH = 0x00是磁盘复位)

1
2
3
MOV		AH,0x00
MOV DL,0x00
INT 0x13

加 大 力 度

读取错误机制我们加上了以后我们就可以加大读取的力度,保证后面系统的使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,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 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
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES,0x20指令,所以这里稍微绕个弯
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
fin:
HLT
JMP fin
error:
MOV SI, msg

EQU指令:是”equal”的缩写,相当于C语言中的#define命令,用来声明常数。“CYLS EQU 10”意思是”CYLS = 10”。

现在我们程序已经可以把软盘最初的10×2×18×512=184320byte=180KB内容装载到内存里了,即这个程序可以用从软盘读取的数据填满内存0x08200~0x34fff的地方。

Day03 - 着手开发操作系统

前面两天的工作是完善启动引导程序,现在我们要开始做的就是通过这个引导程序让指令跳到我们的操作系统里。并且我们将使用Makefile来统一编译我们的程序。

完整的 ipl.nas 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
; haribote-ipl
; TAB=4

CYLS EQU 10
ORG 0x7c00 ; 指明程序的装载地址

; 以下这段是标准FAT12格式软盘专用的代码

JMP entry
DB 0x90
DB "HELLOIPL" ; 启动区的名称可以是任意的字符串(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 "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称(8字节)
RESB 18 ; 先空出18字节

; 程序核心

entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,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 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
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES,0x20指令,所以这里稍微绕个弯
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

; 跳转到asmhead.sys

JMP 0xc200

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
fin:
HLT
JMP fin
msg:
DB 0x0a, 0x0a
DB "load error"
DB 0x0a
DB 0

RESB 0x7dfe-$

DB 0x55, 0xaa

我们可以看到其中增加里一句 JMP 0xc200 这就是我们引导程序结束后跳转到操作系统的代码。

我们把这个文件命名为 asmhead.nas,这个命名的原因是核心代码打算用C语言写,但是前面图形界面的启动还是得靠汇编。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
; haribote-os
; TAB=4

; 有关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显卡、320x200x8位彩色
MOV AH, 0x00
INT 0x10
MOV BYTE [VMODE], 8 ; 记录画面模式
MOV WORD [SCRNX], 320
MOV WORD [SCRNY], 200
MOV DWORD [VRAM], 0x000a0000

; 用BIOS取得键盘上各种LED指示灯状态

MOV AH, 0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS], AL

fin:
HLT
JMP fin

为了编译这个还特地写了 Makefile 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
TOOLPATH = ../z_tools/
MAKE = $(TOOLPATH)make.exe -r
NASK = $(TOOLPATH)nask.exe
EDIMG = $(TOOLPATH)edimg.exe
IMGTOL = $(TOOLPATH)imgtol.com
COPY = copy
DEL = del

default :
$(MAKE) img

ipl.bin : ipl.nas Makefile
$(NASK) ipl.nas ipl.bin ipl.lst

asmhead.sys : asmhead.nas Makefile
$(NASK) asmhead.nas asmhead.sys asmhead.lst

haribote.img : ipl.bin asmhead.sys Makefile
$(EDIMG) imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl.bin len:512 from:0 to:0 \
copy from:asmhead.sys to:@: \
imgout:haribote.img

img :
$(MAKE) haribote.img

install :
$(MAKE) img
$(IMGTOL) w a: haribote.img

clean :
-$(DEL) ipl.bin
-$(DEL) ipl.lst
-$(DEL) haribote.sys
-$(DEL) haribote.lst

src_only :
$(MAKE) clean

文件架构如下:

1
2
3
4
5
6
7
8
9
10
11
12
| 
--day03
| |
| -- asmhead.nas
| |
| -- ipl.nas
| |
| -- Makefile
| |
| -- Make.bat
|
-- z_tools

这边程序运行起来就是一片黑,连 Hello World 都没有,所以就不上运行结果了。

Day04 - 导入用C编写的后半部分

为了调用c语言程序,作者在 asmhead.nas 里面添加了100行左右的代码,这个代码暂时不要求理解。

最新的 asmhead.nas 代码如下(请忽略注释的乱码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
; haribote-os boot asm
; TAB=4

BOTPAK EQU 0x00280000 ;
DSKCAC EQU 0x00100000 ;
DSKCAC0 EQU 0x00008000 ;

; BOOT_INFO
CYLS EQU 0x0ff0 ;
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ;
SCRNX EQU 0x0ff4 ;
SCRNY EQU 0x0ff6 ;
VRAM EQU 0x0ff8 ;

ORG 0xc200 ;

;

MOV AL,0x13 ;
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ;
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000

;

MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL



MOV AL,0xff
OUT 0x21,AL
NOP ;
OUT 0xa1,AL

CLI ;

;

CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout

;

[INSTRSET "i486p"] ;

LGDT [GDTR0] ;
MOV EAX,CR0
AND EAX,0x7fffffff ;
OR EAX,0x00000001 ;
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ;
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 ;
SUB ECX,512/4 ;
CALL memcpy



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 ;
RET

memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy ;
RET

ALIGNB 16
GDT0:
RESB 8 ;
DW 0xffff,0x0000,0x9200,0x00cf ;
DW 0xffff,0x0000,0x9a28,0x0047 ;

DW 0
GDTR0:
DW 8*3-1
DD GDT0

ALIGNB 16
bootpack:

下面就是我们的C语言部分:bootpack.c

1
2
3
4
5
6
7
8
void io_hlt(void);

void HariMain(void)
{
fin:
io_hlt();
goto fin;
}

这个就实现了一个简单的HLT功能,其中HLT我们以后还会多次用到,所以把他从C语言中剥离出来单独写成 naskfunc.nas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; naskfunc
; TAB=4

[FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式用的机械语言

; 制作目标文件的信息

[FILE "naskfunc.nas"] ; 源文件名信息

GLOBAL _io_hlt ; 程序中包含的函数名

; 以下是实际的函数

[SECTION .text] ; 目标文件中写了这些之后再写程序

_io_hlt: ; void io_hlt(void)
HLT
RET

改写 Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
TOOLPATH = ../z_tools/
INCPATH = ../z_tools/haribote/

MAKE = $(TOOLPATH)make.exe -r
NASK = $(TOOLPATH)nask.exe
CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM = $(TOOLPATH)obj2bim.exe
BIM2HRB = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG = $(TOOLPATH)edimg.exe
IMGTOL = $(TOOLPATH)imgtol.com
COPY = copy
DEL = del


default :
$(MAKE) img


ipl10.bin : ipl10.nas Makefile
$(NASK) ipl10.nas ipl10.bin ipl10.lst

asmhead.bin : asmhead.nas Makefile
$(NASK) asmhead.nas asmhead.bin asmhead.lst

bootpack.gas : bootpack.c Makefile
$(CC1) -o bootpack.gas bootpack.c

bootpack.nas : bootpack.gas Makefile
$(GAS2NASK) bootpack.gas bootpack.nas

bootpack.obj : bootpack.nas Makefile
$(NASK) bootpack.nas bootpack.obj bootpack.lst

naskfunc.obj : naskfunc.nas Makefile
$(NASK) naskfunc.nas naskfunc.obj naskfunc.lst

bootpack.bim : bootpack.obj naskfunc.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
bootpack.obj naskfunc.obj
# 3MB+64KB=3136KB

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

haribote.img : ipl10.bin haribote.sys Makefile
$(EDIMG) imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl10.bin len:512 from:0 to:0 \
copy from:haribote.sys to:@: \
imgout:haribote.img


img :
$(MAKE) haribote.img

run :
$(MAKE) img
$(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
$(MAKE) -C ../z_tools/qemu

install :
$(MAKE) img
$(IMGTOL) w a: haribote.img

clean :
-$(DEL) *.bin
-$(DEL) *.lst
-$(DEL) *.gas
-$(DEL) *.obj
-$(DEL) bootpack.nas
-$(DEL) bootpack.map
-$(DEL) bootpack.bim
-$(DEL) bootpack.hrb
-$(DEL) haribote.sys

src_only :
$(MAKE) clean

好了,到这部分开始我们的操作系统就进入C语言时代了。

Day05 - 界面

在这个部分我们要完成界面的基础绘制。

想要在画面上画东西,只要往VRAM里写点东西就可以了。VRAM指的是显卡内存(video RAM),就是可以用来显示画面的内存。这一块内存可以像内存一样存储数据,它的各个地址都对应着画面上的像素。我们可以利用这个机制在画面上绘制各种各样的图案。

VRAM分布在内存分布图好几个不同的地方,不同画面模式的像素不一样,切换不同的画面模式,会使用不同VRAM,即不同画面模式可以使用的内存也不一样,我们预先把要使用的VRAM地址保存在BOOT_INFO里(VRAM EQU 0x0ff8)。

首先我们要在 naskfunc.nas 中添加函数 _write_mem8 (在全局变量那里不要忘记声明,不然是无法调用的)

1
2
3
4
5
_write_mem8:
MOV ECX, [ESP+4] ;[ESP + 4]中存 放的是地址,将其读入ECX
MOV AL, [ESP+8] ; [ESP + 8]中存 放的是数据,将其读入AL
MOV [ECX], AL
RET

这个函数可以实现界面的绘制

故我们修改一下 bootpack.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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, 15); // 这个15表示白色
}

for(;;){
io_hit();
}
}

整个程序结构如下:

3

在命令行里输入

1
.\make

我们就可以在虚拟机中得到以下结果

2

好诶!不再是一团黑了。

如果我们把绘制部分改成:

1
write_mem8(i, i & 0x0f);

我们就可以在每一列上得到不同颜色的条纹了

4

最后我们搞点大动作,这里代码全是C语言就不过多解释了。

附上最后的 bootpack.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
void io_hlt(void);
void io_cli(void);
void io_out8(int port, int data);
int io_load_eflags(void);
void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);

#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15

void HariMain(void)
{
char *vram;
int xsize, ysize;

init_palette();
vram = (char *) 0xa0000;
xsize = 320;
ysize = 200;

boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28);
boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1);

boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24);
boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5);
boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3);
boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);

boxfill8(vram, xsize, COL8_848484, xsize-47, ysize-24, xsize-4, ysize-24);
boxfill8(vram, xsize, COL8_848484, xsize-47, ysize-23, xsize-47, ysize-4);
boxfill8(vram, xsize, COL8_FFFFFF, xsize-47, ysize-3, xsize-4, ysize-3);
boxfill8(vram, xsize, COL8_FFFFFF, xsize-3, ysize-24, xsize-3, ysize-3);

for (;;) {
io_hlt();
}
}

void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {
0x00, 0x00, 0x00, /* 0:黑 */
0xff, 0x00, 0x00, /* 1:亮红 */
0x00, 0xff, 0x00, /* 2:亮绿 */
0xff, 0xff, 0x00, /* 3:亮黄 */
0x00, 0x00, 0xff, /* 4:亮蓝 */
0xff, 0x00, 0xff, /* 5:亮紫 */
0x00, 0xff, 0xff, /* 6:浅亮蓝 */
0xff, 0xff, 0xff, /* 7:白 */
0xc6, 0xc6, 0xc6, /* 8:亮灰 */
0x84, 0x00, 0x00, /* 9:暗红 */
0x00, 0x84, 0x00, /* 10:暗绿 */
0x84, 0x84, 0x00, /* 11:暗黄 */
0x00, 0x00, 0x84, /* 12:暗青 */
0x84, 0x00, 0x84, /* 13:暗紫 */
0x00, 0x84, 0x84, /* 14:浅暗紫 */
0x84, 0x84, 0x84 /* 15:暗灰 */
};
set_palette(0, 15, table_rgb);
return;
}

void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 记录中断许可标志 */
io_cli(); /* 将中断许可标志置0,表示禁止中断 */
io_out8(0x03c8, start);
for (i = start; i <= end; i++) {
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); /* 复原中断许可标志 */
return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c;
}
return;
}

效果如下

5