项目情景
开机后,CPU自动进入到FFFF
:0
单元处执行,此处有一条jmp
指令。CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。
初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。
硬件系统检测和初始化完成后,调用int 19h
进行操作系统的引导。
如果设置从软盘启动操作系统,则int 19h
将主要完成以下任务:
- 控制0号软驱(A),读取软盘0道0面1扇区至
0
:7C00
; - 将
CS
:IP
指向0
:7C00
;
软盘的0面0道1扇区装有操作系统引导程序。int 19h
将其装到0
:7C00
处后,设置CPU从此处开始执行引导程序,操作系统被激活,控制计算机。
详细过程如下图所示:
这个图是笔者随便画的。
如果0号软驱中没有软盘,或者软盘发生读写错误,则int 19h
进行下列操作:
- 读取硬盘C的0道0面1扇区的内容到
0
:7C00
; - 将
CS
:IP
指向0
:7C00
;
这次课程设计的任务是编写一个可以自行启动计算机,不需要在现有操作系统环境中运行的程序。
该程序的功能如下:
列出功能选项,让用户通过键盘进行选择,界面如下:
1
2
3
41) reset pc ;重新启动计算机
2) start system ;引导现有操作系统
3) clock ;进入时钟程序
4) set clock ;设置时间用户输入
1
后重新启动计算机(提示:考虑0FFFF
:0
单元);- 用户输入
2
后引导现有操作系统(提示:考虑C盘0道0面1扇区); - 用户输入
3
后执行动态显示当前日期、时间的程序。
显示格式如下:年/月/日 时:分:秒
进入此项功能后,一直动态显示当前的时间,在屏幕上将出现时间按秒变化的效果(提示:循环读取CMOS);
按下F1
后改变显示颜色;按下ESC
返回主选单(提示:利用键盘中断)。 - 用户输入
4
后可更改当前的日期、时间,更改后返回到主选单(提示:输入字符串);
下面给出几点建议:
- 在DOS下编写安装程序,在安装程序中包含任务程序;
- 运行安装程序,将任务程序写到软盘上;
- 若要任务程序可以在开机后自动执行,要将它写到软盘的0道0面0扇区。如程序长度大于
512B
,则需要用多个扇区存放。这种情况下,处于软盘0道0面1扇区中的程序就必须负责将其他扇区中的内容读入内存;
这个程序较为复杂,它用到了我们学到的所有技术,需要进行仔细的分析和耐心的调试。这个程序对于我们的整个学习过程是具有总结性的,希望读者能够尽力完成。
分析
这个题目看起来非常复杂,然而实际上也是极其复杂的。
首先,一个扇区(512B
)肯定是无法容纳这么多功能的,我们需要提前将每个扇区中存放什么东西规划好:
- 扇区1: 存放主程序和主选单文本;
- 扇区2: 存放需要用到的子程序;
- 扇区3: 存放自己编写的中断例程;
其次,debug
在这里是用不了的。debug
根本承受不住软盘1.44MB
的容量,如果试图强行打开只会喜提报错。
Extended Error 8
义为”没有足够的内存来完成请求的操作”,接下来就只能靠颅内debug
了。
程序实现
主选单
1 | ; 引导程序主程序 (扇区 1) |
主页面的文字,姑且在这里署个名。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24display:
; 从软盘载入子程序
mov ax,0
mov es,ax
mov bx,7c00h+512 ; 扇区1 + 512B
mov ah,2 ; BIOS:读取扇区
mov al,1
mov ch,0
mov cl,2 ; 扇区2
mov dh,0
mov dl,0
int 13h
mov bx,7c00h+1024 ; 扇区2 + 1024B
mov ah,2
mov al,1
mov ch,0
mov cl,3
mov dh,0
mov dl,0
int 13h
利用BIOS中断int 13h
将软盘的其余两个分区读入内存;1
2
3
4
5
6
7
8call clear ; 清屏
; 展示菜单
mov ax,0
mov ds,ax
mov si,0a0h ; 屏幕位置 (换一行)
mov di,7c02h ; 文本所在地址
call printf
清屏后展示主选单文本,这里的clear
和printf
子程序与之前的课程设计1程序相差不大;
1 | call install_int ; 安装中断例程 |
安装中断例程并等待中断1
2org offset main + 510
dw 0aa55h ; 启动扇区标志
用org
空出剩余字节,实现分扇区;
注意:结尾需要加0aa55h
,不然BIOS才不会拿你当启动扇区呢。
主选单中call
了几个子程序:clear
和printf
,与之前写的相差不大: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; 子程序(扇区2)
sub_program:
db "clear"
; 205
clear:
; 名称:clear
; 功能:清屏
push ax
push bx
push cx
push es
mov ax,0b800h ; 显存段
mov es,ax
mov cx,2000 ; 80x25字符
mov bx,0
c0:
mov byte ptr es:[bx],20h ; 空格
; 一个小改动:删去属性字节,因为后面有设置颜色的功能
add bx,2
loop c0
pop es
pop cx
pop bx
pop ax
ret
db "printf"
; 229
printf:
; 名称:printf
; 功能:在特定位置打印字符串,0A -> \n,以0结尾
; 参数: ds:di -> 打印内容
; (si) -> 位置
; 返回: None
push cx
push si
push di
push ds
push es
push si
mov cx,0b800h
mov es,cx
; 主打印循环
p0:
mov ch,0
mov cl,ds:[di]
jcxz printf_cmpd
cmp cx,0ah
je lineFeed
mov es:[si],cl
; 还是不动属性字节
add si,2
p1:
add di,1
jmp short p0
lineFeed:
; 换行
pop si
add si,0a0h
push si
jmp short p1
printf_cmpd:
pop si
pop es
pop ds
pop di
pop si
pop cx
ret
完成这些之后应该就能显示这样一个超酷的主选单了:
键盘中断
中断安装
安装没什么好说的,在中断向量表中将int 9h
(键盘中断)对应的地址替换为下面自己写的int 9
的地址,并将原int 9h
对应的地址保存即可;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
32install:
push ax
push cx
push si
push di
push ds
push es
mov ax,cs
mov ds,ax
mov ax,0
mov es,ax
; 保存原int 9h
mov ax,es:[9*4]
mov es:[200h],ax
mov ax,es:[9*4+2]
mov es:[202h],ax
; 设置中断向量表
cli
mov word ptr es:[9*4],803fh
mov word ptr es:[9*4+2],0
sti
pop es
pop ds
pop di
pop si
pop cx
pop ax
ret
这里存了这么多寄存器是因为一开始用了movsb
,后来发现没必要;
中断中转站
1 | int9: |
两个段分别用来存储func3
输出的时间和func4
输入的时间;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22inte:
push ax
push bx
push es
check:
in al,60h ; 键盘扫描码
cmp al,2h ; 1: 重启计算机
je func1
cmp al,3h ; 2: 启动操作系统
je func2
cmp al,4h ; 3: 时钟
je func3
cmp al,1h ; ESC: 返回主选单
je func_esc
cmp al,84h ; 3: 时钟
; 不设的话断码会打断那个循环
je func3
cmp al,5h ; 4: 设置时间
je func4
cmp al,3bh ; F1: 改变颜色
je func_color
jmp short check
也可以用直接定址表,但我实在是懒得对着十六进制编辑器去查地址了;
1 重启计算机
这应该是最简单的一个功能了。1
2
3
4
5
6
7func1:
; 按键:1
; 功能:重启计算机
mov bx,0
mov es,bx
jmp dword ptr es:[8041h]
其中es
:[8041h]
即前面存的0ffffh
:0
;
2 启动操作系统
1 | func2: |
注意:这里如果真的读0道0面1扇区是会卡死的,用十六进制编辑器看看磁盘镜像就知道了;
开头甚至不是jmp
指令和OEM信息。
而是应该读取0道1面1扇区,那才是系统的引导扇区:
以jmp
开头,带OEM的才是引导扇区嘛。1
2
3
4
5
6
7; 重置中断向量表
cli
mov ax,es:[200h]
mov es:[9*4],ax
mov ax,es:[202h]
mov es:[9*4+2],ax
sti
将中断向量表恢复为原来的地址,不然进DOS之后按键盘没反应;1
2; 跳转并载入系统
jmp dword ptr es:[8045h]
这里的es
:[8045h]
即前面存的0
:7c00h
;
接下来不出意外按2
就可以进操作系统了:
3 时钟
这个和之前写的time比较像,但需要循环读取;
首先仿照time写一个read_cmos
子程序:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25read_cmos:
; 名称:read_cmos
; 功能:读取CMOS RAM的特定字节
; 参数:(al)->地址(CMOS)
; 返回:ds:bx(输入)
push cx
push ax
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
add ah,30h
add al,30h
mov ds:[bx],ah
mov ds:[bx+1],al
pop ax
pop cx
ret
接着需要写的是func3
的主程序,功能是将CMOS里的时间写入0
:8049h
那个输出时间的数据段,并打印在屏幕上1
2
3
4
5
6
7
8
9
10func3_main:
; 按键:3
; 功能:实时时钟
call clear
mov ax,0
mov ds,ax
mov al,20h
out 20h,al
sti
发送EOI,不然接受不了键盘中断;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
30time:
cli
; 防止在循环体未完整执行时触发中断
mov al,9
mov cx,3
mov bx,8049h
; 前面的“输入时间”数据段
; 后面和time.asm差不多
l0:
call read_cmos
add bx,3
dec al
loop l0
mov al,4
mov cx,3
l1:
call read_cmos
add bx,3
sub al,2
loop l1
mov di,8049h
mov si,0a0h
call printf
sti
; 接收中断的窗口期
nop
jmp short time
同时还有ESC
键的退出功能:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21func_esc:
; 按键:ESC
; 功能:回到主页
cli
mov di,7c02h
mov si,0a0h
call printf
; 显示主页
mov al,20h
out 20h,al
pop es
pop bx
pop ax
sti
; 发送EOI
hold:
nop
; 等待中断
jmp short hold
完成上述程序后,实时刷新的时钟应该就可用了:
4 设置时钟
因为扇区3的512字节非常紧张了,所以func4
的主程序charin
和write_cmos
写在扇区2中;1
2
3
4
5
6
7
8
9
10func4:
; 按键:4
; 功能:设置时钟
cli
mov al,20h
out 20h,al
sti
call charin ; 获取用户输入
jmp short func_esc
charin
子程序负责处理键盘的输入,非数字自动忽略;Backspace
删除,输入和删除需要判断是否超界;Enter
确认、写入CMOS并返回;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16; 输入时间(功能 4)
charin:
; 名称:charin
; 功能:通过int 16h接收键盘输入
push ax
push bx
push si
push di
; 重置中断向量
cli
mov bx,es:[200h]
mov es:[9*4],bx
mov bx,es:[202h]
mov es:[9*4+2],bx
sti
因为要用键盘输入,所以这里要恢复int 9h
中断向量;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; 输入时间的保存位置
mov bx,805bh
input:
call clear
mov di,805bh
mov si,0a0h
call printf
; 在屏幕上显示输入的时间
mov ah,0
int 16h
; 键盘输入
cmp al,30h
; "0"的ASCII
jb nonumber
cmp al,39h
; "9"的ASCII
ja nonumber
cmp bx,8066h
; 判断不超上界
ja input
mov es:[bx],al
inc bx
jmp short input
处理输入,忽略非数字输入,Backspace
和Enter
特殊处理;还需要判断输入不超上界;1
2
3
4
5
6
7
8nonumber:
cmp ah,0eh
; Backspace的扫描码
je backspace
cmp ah,1ch
; Enter的扫描码
je enter
jmp short input
非数字输入的处理;1
2
3
4
5
6backspace:
cmp bx,805bh
jna input
dec bx
mov byte ptr es:[bx],30h
jmp short inputBackspace
退格功能,需要判断退格不超下界;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17enter:
call write_cmos
mov bx,805bh
mov cx,12
sn:
mov byte ptr es:[bx],30h
inc bx
loop sn
cli
mov word ptr es:[9*4],803fh
mov word ptr es:[9*4+2],0
sti
pop di
pop si
pop bx
pop ax
retEnter
功能:提交输入,写入CMOS,恢复int 9h
中断,并返回主选单;
接下来需要仿照read_cmos
写一个write_cmos
程序。和read_cmos
不同,循环写在write_cmos
函数体内;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
66write_cmos:
; 名称:write_cmos
; 功能:向CMOS写入时间
push ax
push bx
push cx
push dx
push si
mov bx,805bh
; 输入的时间的保存位置
mov cx,3
mov dl,9
write1:
push cx
push dx
; 字符 -> BCD
mov dx,es:[bx]
sub dh,30h
sub dl,30h
mov cl,4
shl dl,cl
add ch,dl
add ch,dh
pop dx
mov al,dl
out 70h,al
mov al,ch
out 71h,al
dec dx ; 年/月/日 9/8/7
add bx,2
pop cx
loop write1
mov dl,4
mov cx,3
write2:
push cx
push dx
mov dx,es:[bx]
sub dh,30h
sub dl,30h
mov cl,4
shl dl,cl
add ch,dl
add ch,dh
pop dx
mov al,dl
out 70h,al
mov al,ch
out 71h,al
sub dx,2 ; 时:分:秒 4:2:0
add bx,2
pop cx
loop write2
pop si
pop dx
pop cx
pop bx
pop ax
ret
应该就可以随心所欲地调时间了:
设置颜色
这个也算是比较简单的。1
2
3
4
5
6
7
8
9func_color:
; 按键:F1
; 功能:改变前景/背景色
call color
call clear
mov di,7c02h
mov si,0a0h
call printf
切换颜色,并跳转回主菜单;1
2
3
4
5
6
7
8
9
10mov cx,500h
delay:
push cx
mov cx,0ffffh
delay_1:
nop
loop delay_1
pop cx
loop delay
jmp short func_esc
延时循环,避免短时间内触发多次F1
出现跳过颜色的情况;
1 | color: |
然后就可以调成喜欢的字体颜色了:
安装程序
最后还需要写一个在DOS上运行的安装程序: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
44write:
; 写入软盘 A:
; 1. 主引导程序 (扇区 1)
; 2. 子程序 (扇区 2)
; 3. 中断 (扇区 3)
; 主引导程序 -> 扇区 1
mov ax,cs
mov es,ax
mov bx,offset main
mov ah,3 ; BIOS:写入扇区
mov al,1 ; 1 扇区
mov ch,0 ; 道 0
mov cl,1 ; 扇区 1
mov dh,0 ; 面 0
mov dl,0 ; 软驱 A
int 13h
; 子程序 -> 扇区 2
mov bx,offset sub_program
mov ah,3
mov al,1
mov ch,0
mov cl,2 ; 扇区 2
mov dh,0
mov dl,0
int 13h
; 中断 -> 扇区 3
mov bx,offset install_int
mov ah,3
mov al,1
mov ch,0
mov cl,3 ; 扇区 3
mov dh,0
mov dl,0
int 13h
; 退出安装程序
mov ax,4c00h
int 21h
至此,就完成了这个程序开发的全部工作!
完整代码
本程序的完整代码如下,共564行(含注释);
下载请见我的仓库: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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563; os.asm
; A bootloader with:
; - Reset PC
; - Start System
; - Real-time clock
; - Clock setting
; - Color set
assume cs:code
code segment
write:
; Write to the floppy A:
; 1. Main bootloader (Sector 1)
; 2. Subprograms (Sector 2)
; 3. Interrupt (Sector 3)
; Main -> Sector 1
mov ax,cs
mov es,ax
mov bx,offset main
mov ah,3 ; BIOS:Write sectors
mov al,1 ; 1 Sector
mov ch,0 ; Cylinder 0
mov cl,1 ; Sector 1
mov dh,0 ; Head 0
mov dl,0 ; Drive A
int 13h
; Subprogram -> Sector 2
mov bx,offset sub_program
mov ah,3
mov al,1
mov ch,0
mov cl,2 ; Sector 2
mov dh,0
mov dl,0
int 13h
; Interrupt -> Sector 3
mov bx,offset install_int
mov ah,3
mov al,1
mov ch,0
mov cl,3 ; Sector 3
mov dh,0
mov dl,0
int 13h
; Terminate writing program
mov ax,4c00h
int 21h
; Main bootloader (Sector 1)
main:
; 0
jmp short display
; 2
; Text of main page
text db "BootLoader By Hiedano Ajuu",0ah,"1) reset pc",0ah,"2) start system",0ah,"3) clock",0ah,"4) set clock",0
display:
; Load subprogram from floppy
mov ax,0
mov es,ax
mov bx,7c00h+512 ; Sector 1 + 512B
mov ah,2 ; BIOS:Read sectors
mov al,1
mov ch,0
mov cl,2 ; Sector 2
mov dh,0
mov dl,0
int 13h
mov bx,7c00h+1024 ; Sector 1 + 1024B
mov ah,2
mov al,1
mov ch,0
mov cl,3
mov dh,0
mov dl,0
int 13h
call clear ; Clear screen
; Display Menu
mov ax,0
mov ds,ax
mov si,0a0h ; Screen position (1 line)
mov di,7c02h ; Text address
call printf
call install_int ; Install interrupt
; Wait for interrupt
hold_m:
nop
jmp short hold_m
org offset main + 510
dw 0aa55h ; Boot signature
; Subgrogram (Sector 2)
sub_program:
db "clear"
; 205
clear:
; Name:clear
; Function:Clear the sreen
push ax
push bx
push cx
push es
mov ax,0b800h ; Video memory segment
mov es,ax
mov cx,2000 ; 80x25 characters
mov bx,0
c0:
mov byte ptr es:[bx],20h ; Space
add bx,2
loop c0
pop es
pop cx
pop bx
pop ax
ret
color:
; Name:color
; Function:Color rotation
push ax
push bx
push cx
push es
mov ax,0b800h
mov es,ax
mov cx,2000
mov bx,1 ; Color attribute byte
e0:
; Increment color values
mov ah,es:[bx]
inc ah
mov es:[bx],ah
add bx,2
loop e0
pop es
pop cx
pop bx
pop ax
ret
db "printf"
; 229
printf:
; Name:printf
; Function:Print the strings at the specified position,0A -> \n,ending in 0
; Parameter: ds:di -> content to be printed
; (si) -> position
; Return: None
push cx
push si
push di
push ds
push es
push si
mov cx,0b800h
mov es,cx
; Main print loop
p0:
mov ch,0
mov cl,ds:[di]
jcxz printf_cmpd
cmp cx,0ah
je lineFeed
mov es:[si],cl
add si,2
p1:
add di,1
jmp short p0
lineFeed:
; Handle newline
pop si
add si,0a0h
push si
jmp short p1
printf_cmpd:
pop si
pop es
pop ds
pop di
pop si
pop cx
ret
; CMOS RAM functions
read_cmos:
; Name:read_cmos
; Function:Read designated byte of CMOS RAM
; Parameter:(al)->address(CMOS)
; Return:ds:bx(in)
push cx
push ax
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
add ah,30h
add al,30h
mov ds:[bx],ah
mov ds:[bx+1],al
pop ax
pop cx
ret
; Time input (Function 4)
charin:
; Name:charin
; Function:Receive keyboard input through int 16h
push ax
push bx
push si
push di
; Reset IVT
cli
mov bx,es:[200h]
mov es:[9*4],bx
mov bx,es:[202h]
mov es:[9*4+2],bx
sti
mov bx,805bh
input:
call clear
mov di,805bh
mov si,0a0h
call printf
mov ah,0
int 16h
; Keyboard input
cmp al,30h
jb nonumber
cmp al,39h
ja nonumber
cmp bx,8066h
ja input
mov es:[bx],al
inc bx
jmp short input
nonumber:
cmp ah,0eh
je backspace
cmp ah,1ch
je enter
jmp short input
backspace:
cmp bx,805bh
jna input
dec bx
mov byte ptr es:[bx],30h
jmp short input
enter:
call write_cmos
mov bx,805bh
mov cx,12
sn:
mov byte ptr es:[bx],30h
inc bx
loop sn
cli
mov word ptr es:[9*4],803fh
mov word ptr es:[9*4+2],0
sti
pop di
pop si
pop bx
pop ax
ret
write_cmos:
; Name:write_cmos
; Function:Write time to CMOS
push ax
push bx
push cx
push dx
push si
mov bx,805bh
mov cx,3
mov dl,9
write1:
push cx
push dx
; char -> BCD
mov dx,es:[bx]
sub dh,30h
sub dl,30h
mov cl,4
shl dl,cl
add ch,dl
add ch,dh
pop dx
mov al,dl
out 70h,al
mov al,ch
out 71h,al
dec dx ; Y/M/D 9/8/7
add bx,2
pop cx
loop write1
mov dl,4
mov cx,3
write2:
push cx
push dx
mov dx,es:[bx]
sub dh,30h
sub dl,30h
mov cl,4
shl dl,cl
add ch,dl
add ch,dh
pop dx
mov al,dl
out 70h,al
mov al,ch
out 71h,al
sub dx,2 ; H:M:S 4:2:0
add bx,2
pop cx
loop write2
pop si
pop dx
pop cx
pop bx
pop ax
ret
org offset sub_program +512
; Sector 3
install_int:
jmp short install
db "int"
install:
push ax
push cx
push si
push di
push ds
push es
mov ax,cs
mov ds,ax
mov ax,0
mov es,ax
; Save original int 9h
mov ax,es:[9*4]
mov es:[200h],ax
mov ax,es:[9*4+2]
mov es:[202h],ax
; Set IVT
cli
mov word ptr es:[9*4],803fh
mov word ptr es:[9*4+2],0
sti
pop es
pop ds
pop di
pop si
pop cx
pop ax
ret
db "int9"
int9:
jmp short inte
dw 0,0ffffh ; BIOS
dw 7c00h,0 ; Floppy
db "yy/mm/dd hh:mm:ss",0 ; Time(Out)
db "000000000000",0 ; Time(In)
inte:
push ax
push bx
push es
check:
in al,60h ; Keyboard scan code
cmp al,2h ; 1: Reset PC
je func1
cmp al,3h ; 2: Start system
je func2
cmp al,4h ; 3: Clock
je func3
cmp al,1h ; ESC
je func_esc
cmp al,84h ; 3: Clock
je func3
cmp al,5h ; 4: Set clock
je func4
cmp al,3bh ; F1: Change color
je func_color
jmp short check
func_esc:
; Key:ESC
; Function:Jump to main page
cli
mov di,7c02h
mov si,0a0h
call printf
mov al,20h
out 20h,al
pop es
pop bx
pop ax
sti
hold:
nop
jmp short hold
func4:
; Key:4
; Function:Set clock
cli
mov al,20h
out 20h,al
sti
call charin ; Get user's input
jmp short func_esc
func1:
; Key:1
; Function:Reset pc
mov bx,0
mov es,bx
jmp dword ptr es:[8041h]
func2:
; Key:2
; Function:Start system
mov ax,0
mov es,ax
mov bx,7c00h
; Read Disk C
mov ah,2
mov al,1
mov ch,0
mov cl,1
mov dh,1
mov dl,80h
int 13h
; Reset IVT
cli
mov ax,es:[200h]
mov es:[9*4],ax
mov ax,es:[202h]
mov es:[9*4+2],ax
sti
; Jmp to load OS
jmp dword ptr es:[8045h]
func3:
; Wrap to func3(jmp short limit)
jmp short func3_main
func_color:
; Key:F1
; Function: Change fg/bg color
call color
call clear
mov di,7c02h
mov si,0a0h
call printf
mov cx,500h
delay:
push cx
mov cx,0ffffh
delay_1:
nop
loop delay_1
pop cx
loop delay
jmp short func_esc
func3_main:
; Key:3
; Function:Real-time clock
call clear
mov ax,0
mov ds,ax
mov al,20h
out 20h,al
sti
time:
cli
mov al,9
mov cx,3
mov bx,8049h
l0:
call read_cmos
add bx,3
dec al
loop l0
mov al,4
mov cx,3
l1:
call read_cmos
add bx,3
sub al,2
loop l1
mov di,8049h
mov si,0a0h
call printf
sti
nop
jmp short time
int_end:
nop
code ends
end write
从嘲笑屎山到堆起屎山,这段代码写得虽然毫无美感,但好歹算是能实现功能。
运行结果
在QEMU虚拟机上编译、链接,生成os.exe
;
运行os.exe
,写入软盘;
在命令行中输入以下命令,启动QEMU虚拟机,并将A盘设置为启动盘。1
qemu-system-i386 -m 32M -hda c.img -fda a.img -boot a -smp 1,sockets=1,cores=1
运行结果如视频所示:
可以看到,实现了我们预期的所有功能。
项目完成