0%

《汇编语言》(第四版) 课程设计 2

《汇编语言(第四版)》 . 王爽著 . 清华大学出版社 . 2019

课程设计 2


我们自顶向下地梳理一下程序编写的思路,顺便对程序进行解释

首先,我们要搞清楚我们写的程序放在哪里去运行
按照所给的材料理解,我们要把我们编写的这个可以自行启动计算机而不需要在现有操作系统环境中运行的程序放到软盘中去,这样当我们从软盘启动操作系统时,int 19h 将会控制 0 号软驱,读取软盘中我们编写的程序到内存 0:7c00 处,并将 CS:IP 指向 0:7c00,这样计算机就开始执行我们的控制程序

考虑到我们的程序长度会大于 512 个字节,即大于一个扇区的大小,那么它就需要用多个扇区来存放,于是我们不将它放在软盘 0 道 0 面 1 扇区,而将其从 2 号扇区开始存放,在 1 号扇区中,我们放一个 introduce 程序(为了方便,以后把这个要放入 1 号扇区中的程序称作 introduce 程序,把从 2 号扇区开始存放的我们主要要编写的程序称作 boot 程序),introduce 程序负责将软盘中的 boot 程序读入到内存中去,因为 introduce 程序会被 int 19h 读到内存 0:7c00,而一个扇区的大小为 200h,那么 boot 程序可以被读入到内存 0:7e00

于是我们在主程序中调用两个子程序cpy_introduce_todiskacpy_boot_todiska

cpy_introduce_todiska 子程序负责将 introduce 程序写入一号扇区(调用 int 13h 的 3 号功能),而 cpy_boot_todiska 子程序负责将 boot 程序从 2 号扇区开始写入软盘,一共写几个扇区是通过计算 boot 程序的大小 / 一个扇区的大小得到,这里我们调试程序时知道了一共要写 2 个扇区(mov al,2 (al)=读取的扇区数,mov dl,0 驱动器号设置为软驱)

调用完这两个子程序后,主程序就可以返回了,我们会在虚拟机上的 xp 系统中运行主程序,使得 introduce 程序与 boot 程序都写入软盘,然后我们关闭 xp 系统,在虚拟机中设置启动顺序为:软驱->光驱->硬盘,然后重新启动

重新启动后,计算机将运行我们的 introduce 程序,调用cpy_boot_fromdiska 子程序将在主程序中已经写入 2,3 号扇区的 boot 程序读到内存区 0:7e00,然后跳转至 0:7e00(使用 retf 指令)执行 boot 程序

注意,在 introduce 程序的结尾,需要填充字节

进入 boot 程序后,首先调用 init_reg 子程序进行段寄存器的初始化((es)=0b800h,(ds)=0),再调用 clear_screen 子程序,将屏幕全初始化为空字符,属性为黑底白字,然后调用 show_option 子程序,将选项界面显示在屏幕上,注意,show_option 需要调用 show_string 子程序才能完成字符串显示功能,这个子程序将经常被别的子程序调用

显示了选项界面后,程序跳转到 choose_option 标号处,在这里一直循环等待键盘缓冲区输入的选项,这里调用 int 16h 的 0 号功能,al 中即为从键盘缓冲区读出的字符的 ASCII 码,因为数字 0 ~ 9 的 ASCII 码减去 30h 即为数字本身,所以我们将 al 减去 30h 并与 1,2,3,4 逐个比较,若相等则进行跳转

为了体现我们做了选择,屏幕中可以进行反馈,将选中的选项在屏幕中显示为黑底绿字,我们用 change_option_color 子程序来完成这项功能,注意,在每次设置相应选项为黑底绿字前,先恢复所有选项为黑底白字

选项 1:ischooseone
1)reset pc 重新启动计算机(考虑 ffff:0 单元)
调用 reset 子程序,其功能为跳转至内存 ffff:0 处执行,根据材料可知这样能够重新启动,再次进入主选单

选项 2:ischoosetwo
2)start system 引导现有的操作系统(考虑硬盘 C 的 0 道 0 面 1 扇区)
调用 start_syetem 子程序,该子程序模拟 int 19h 的功能,先读取硬盘 C 的 0 道 0 面 1 扇区的内容到 0:7c00,再将 CS:IP 指向 0:7c00,这样我们就引导了现有的操作系统,即计算机开始运行 windows xp 系统

选项 3:ischoosethree
3)clock 进入时钟程序
动态显示当前时间即屏幕上时间按秒变化(循环读取 CMOS)
且按 F1 改变其显示颜色,按 Esc 回到主选单
调用 show_clock 子程序,在子程序中循环读取 CMOS 以获取时间,并且用 save_old_int9 保存好原有的 int 9h,用 set_new_int9 设置安装新的 int 9h,通过键盘中断的方式,检测 F1 和 Esc 的通码(3bh 和 01h),若检测到按下 F1,调用 change_time_color 以改变屏幕上显示时间的颜色,若检测到按下 Esc,则返回到主选单,注意 isesc 的写法,应该模拟 int9_ret 并跳转到 show_time_ret 处,这样才能正常返回到主选单,show_time_ret 处会恢复原有的 int 9h

选项 4:ischoosefour
4)set clock 设置时间(输入字符串)
更改后返回主选单
调用 set_clock 子程序,该子程序由四个子程序组成,其中 clear_string_stack 负责将字符栈中内容全设为字符 ‘0’,show_string_stack 将字符栈中内容显示至屏幕上,get_string 负责处理键盘输入的数字,处理输入的退格键(backspace)与回车键(enter),set_time 负责将字符栈中的内容以 BCD 码的形式写入端口,从而完成设置时间的功能

注意,在调用完成四个功能的子程序后,jmp 转移的标号并不相同

完整程序如下

ks_2.asm
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
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
;注释掉的指令为边编写边调试过程中所用的指令
assume cs:code,ds:data,ss:stack

data segment
db 256 dup (0)
data ends

stack segment
db 128 dup (0)
stack ends

code segment
start: mov ax,stack
mov ss,ax
mov sp,128

;计算 boot 程序大小
mov bx,offset boot_end - offset boot

;将 introduce 程序写入软盘
call cpy_introduce_todiska
;将 boot 程序写入软盘
call cpy_boot_todiska

;call cpy_boot
;call save_old_int9

;mov ax,0
;push ax
;mov ax,7e00h
;push ax
;retf

mov ax,4c00h
int 21h

;程序长度大于 512 个字节,需要用多个扇区存放,
;处于软盘 0 道 0 面 1 扇区中的程序负责将其他扇区中的内容读入内存

;================================================================================
;introduce 程序
;负责将处于软盘中 2,3 号扇区的 boot 程序读到内存 0:7e00
;并将 CS:IP 指向 0:7e00
introduce: mov ax,0
mov ss,ax
mov sp,7c00h ;设置栈,用 0:7c00 前的空间当栈

; call save_old_int9
;将 boot 程序从软盘中读到内存去
call cpy_boot_fromdiska

;利用 retf 指令跳转至 0:7e00h
mov ax,0
push ax
mov ax,7e00h
push ax
retf

;save_old_int9: mov bx,0
; mov es,bx
;
; push es:[9*4]
; pop es:[200h]
; push es:[9*4+2]
; pop es:[202h]
;
; ret

;将 2、3 号扇区的程序读到内存 0:7e00
cpy_boot_fromdiska:
mov ax,0
mov es,ax
mov bx,7e00h

mov al,2 ;读取的扇区数
mov ch,0
mov cl,2
mov dl,0
mov dh,0
mov ah,2 ;读扇区
int 13h

ret

;introduce 程序结束
introduce_end: db 512 dup (0)
;填充字节
nop

;================================================================================
;将 introduce 程序写入软盘
cpy_introduce_todiska:
mov ax,cs
mov es,ax
mov bx,offset introduce

mov al,1
mov ch,0
mov cl,1 ;写入一号扇区
mov dl,0
mov dh,0
mov ah,3
int 13h

ret

;================================================================================
;将 boot 程序写入软盘
cpy_boot_todiska:
mov ax,cs
mov es,ax
mov bx,offset boot

mov al,2
mov ch,0
mov cl,2 ;从二号扇区开始写入
mov dl,0
mov dh,0
mov ah,3
int 13h

ret

;================================================================================
;boot 程序
;主要编写的程序,包含我们要实现的各种功能
boot: jmp boot_start

;********************************************************************************

;选项字符串
option_1 db '1) reset pc ',0 ;重新启动计算机
option_2 db '2) start system ',0 ;引导现有的操作系统
option_3 db '3) clock ',0 ;进入时钟程序
option_4 db '4) set clock ',0 ;设置时间

;选项字符串的实际地址(直接定址表)
address_option dw offset option_1 - offset boot + 7e00h
dw offset option_2 - offset boot + 7e00h
dw offset option_3 - offset boot + 7e00h
dw offset option_4 - offset boot + 7e00h

;COMS RAM 单元
;对应年月日时分秒
time_cmos db 9,8,7,4,2,0
;时间显示格式
time_style db 'YY/MM/DD HH:MM:SS',0
;字符栈
string_stack db 12 dup ('0'),0

;********************************************************************************

boot_start: call init_reg ;段寄存器初始化
call clear_screen ;屏幕初始化
call show_option ;显示选项

jmp choose_option ;处理选项

mov ax,4c00h
int 21h

;================================================================================
;choose_option 子程序
;读取键盘缓冲区,得到从键盘传来的对四个选项的选择
;逐个比较并进行跳转
;若读到无效输入则继续等待
choose_option: call clear_buff ;清理键盘缓冲区

mov ah,0
int 16h

;数字的 ASCII 码减 30h 得到数字本身
sub al,30h

cmp al,1
je ischooseone
cmp al,2
je ischoosetwo
cmp al,3
je ischoosethree
cmp al,4
je ischoosefour

jmp choose_option

;================================================================================
;各个选项的处理
;包含功能子程序和完成后的跳转指令
;跳转的地址标号并不相同
ischooseone: call change_option_color
call reset
jmp choose_option

ischoosetwo: call change_option_color
call start_syetem
jmp choose_option

ischoosethree: call change_option_color
call show_clock
jmp boot_start

ischoosefour: call change_option_color
call set_clock
jmp boot_start

;================================================================================
;reset 子程序
;将 CS:IP 指向 ffff:0
;ffff:0 处有一条跳转指令
;CPU 执行该指令后,转去执行 BIOS 中的硬件系统检测和初始化程序
;这样就实现了重新启动的功能
reset: mov ax,0ffffh
push ax
mov ax,0
push ax
retf

;================================================================================
;start_syetem 子程序
;模拟 int 19h 的功能
;读取硬盘 C 的 0 道 0 面 1 扇区的内容到 0:7c00
;将 CS:IP 指向 0:7c00
;C 盘中就存放了我们的 windows xp 系统
;这样就实现了引导现有操作系统的功能
start_syetem: mov ax,0
mov es,ax
mov bx,7c00h

mov al,1
mov ch,0
mov cl,1
mov dl,80h ;C 盘
mov dh,0
mov ah,2
int 13h ;读取硬盘 C 的 0 道 0 面 1 扇区
;的内容到 0:7c00
mov ax,0
push ax
mov ax,7c00h
push ax
retf ;将 CS:IP 指向 0:7c00

ret

;================================================================================
;set_clock 子程序
;顺序调用了 clear_string_stack、show_string_stack、get_string、set_time 这些子程序
;设置时间,更改后返回
set_clock: call clear_string_stack
call show_string_stack
call get_string
call set_time
ret

;================================================================================
;set_time 子程序
;将字符栈中的内容以 BCD 码的形式写入端口(此行为发生在按下 enter 键后)
set_time: mov bx,offset time_cmos - offset boot + 7e00h
mov si,offset string_stack - offset boot + 7e00h
mov cx,6

set_times: mov dx,ds:[si] ;将 ASCII 码转换成 BCD 码
sub dh,30h
sub dl,30h
shl dl,1
shl dl,1
shl dl,1
shl dl,1
and dh,00001111b
or dl,dh

mov al,ds:[bx] ;写入端口
out 70h,al
mov al,dl
out 71h,al

add si,2
inc bx
loop set_times

ret
;================================================================================
;get_string 子程序
;处理键盘输入的数字
;处理输入的退格键(backspace)与回车键(enter)
get_string: mov si,offset string_stack - offset boot + 7e00h
mov bx,0

get_strings: call clear_buff ;清理键盘缓冲区

mov ah,0 ;读取键盘输入
int 16h

cmp al,'0'
jb notnumber
cmp al,'9'
ja notnumber ;检测是否为数字 0 ~ 9
call char_push ;压入字符栈
call show_string_stack ;显示压入后字符栈中内容
jmp get_strings ;循环等待键盘输入

get_string_ret: ret ;按下 enter 键后跳转到这里,子程序返回

notnumber: cmp ah,0eh ;backspace
je isbackspace
cmp ah,1ch ;enter
je get_string_ret
jmp get_strings

isbackspace: call char_pop ;按下 backspace 键后,弹出栈顶元素
call show_string_stack ;显示弹出后字符栈中内容
jmp get_strings

;================================================================================
;编写栈操作时注意,(bx) 为栈中元素的个数,第 (bx) 个栈元素的偏移地址为 (bx)-1
;字符栈弹出操作
char_pop: cmp bx,0
je char_pop_ret ;栈空
dec bx
mov byte ptr ds:[si+bx],'0' ;在退栈处补 '0'

char_pop_ret: ret

;================================================================================
;字符栈压栈操作
char_push: cmp bx,11
ja char_push_ret ;栈满
mov ds:[si+bx],al
inc bx

char_push_ret: ret

;================================================================================
;show_string_stack 子程序
;将字符栈中内容显示至屏幕上
;需要调用 show_string 子程序
show_string_stack:
push si
push di

mov si,offset string_stack - offset boot + 7e00h
mov di,160*24

call show_string

pop di
pop si
ret

;================================================================================
;clear_string_stack 子程序
;将字符栈中内容全设为字符 '0'
;即初始化字符栈
clear_string_stack:
push ds
push cx
push dx
push si

mov si,offset string_stack - offset boot + 7e00h
mov dx,3030h
;30h 为字符 0

mov cx,6

clear_string_stacks:
mov ds:[si],dx
add si,2
loop clear_string_stacks

pop si
pop dx
pop cx
pop ds
ret

;================================================================================
;show_clock 子程序
;动态显示当前时间且按 F1 改变其显示颜色,按 Esc 回到主选单
;调用了 show_style、save_old_int9、set_new_int9、set_old_int9 这些子程序
show_clock: call show_style ;显示格式
call save_old_int9 ;保存原 int 9h 入口地址
call set_new_int9 ;设置新 int 9h 到中断向量表

mov bx,offset time_cmos - offset boot + 7e00h

;每显示完一次又跳转回 show_time
;循环读取 CMOS RAM
show_time: mov si,bx
mov di,160*20+30*2
mov cx,6

show_date: mov al,ds:[si]
out 70h,al
in al,71h

;解析 BCD 码
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1
and al,00001111b

;转换成 ASCII 码
add ah,30h
add al,30h

mov es:[di],ah
mov es:[di+2],al

;注意中间有分隔符,所以不是 add di,4
add di,6
inc si

;显示 1 次这里循环 6 次
;每次循环显示 2 个数字
loop show_date
jmp show_time

;通过按下 Esc 键引发键盘中断
;经新的 int 9h 中断例程处理,跳转到此处
;恢复原 int 9h 中断例程,并返回
show_time_ret: call set_old_int9
ret

;================================================================================
;show_style 子程序
;在屏幕上显示时间显示的格式
;即 'YY/MM/DD HH:MM:SS'
show_style: mov si,offset time_style - offset boot + 7e00h
mov di,160*20+30*2
call show_string
ret
;================================================================================
;clear_buff 子程序
;清理键盘缓冲区
clear_buff: mov ah,1
int 16h
jz clear_buff_ret
mov ah,0
int 16h
jmp clear_buff

clear_buff_ret: ret

;================================================================================
;show_option 子程序
;在屏幕中间显示四个选项(四行)
;需要调用 show_string 子程序
show_option: mov bx,offset address_option - offset boot + 7e00h
mov di,160*10+30*2
mov cx,4

show_options: mov si,ds:[bx]
call show_string
add bx,2
add di,160
loop show_options

ret

;================================================================================
;show_string 子程序
;在屏幕上显示内存中存放的字符串
;其结束符为 0,若检测到结束符则返回
;(es)=0b800h, 即显示缓冲区的段地址
;(di)=显示缓冲区的偏移地址
;ds:si=要显示的字符串在内存中存放的地址
show_string: push dx
push ds
push es
push si
push di

showstrings: mov dl,ds:[si]
cmp dl,0
je show_string_ret
mov es:[di],dl
add di,2
inc si
jmp showstrings

show_string_ret: pop di
pop si
pop es
pop ds
pop dx
ret

;================================================================================
;clear_screen 子程序
;将屏幕初始化为空字符(0x00),属性为黑底白字(0x70)
clear_screen: mov bx,0
mov dx,0700h ;高位:0000 0111 b
;低位:0x00
mov cx,2000

clear_screens: mov es:[bx],dx
add bx,2
loop clear_screens
ret

;================================================================================
;保存原有的 int 9h 中断例程的入口地址
save_old_int9: push es
push bx

mov bx,0
mov es,bx

push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]

pop bx
pop es
ret

;================================================================================
;恢复原有的 int 9h 中断例程
;将原有的 int 9h 中断例程的入口地址重新写入中断向量表
set_old_int9: push bx
push es

mov bx,0
mov es,bx

cli
push es:[200h]
pop es:[9*4]
push es:[202h]
pop es:[9*4+2]
sti

pop es
pop bx
ret

;================================================================================
;安装新的 int 9h 中断例程
;将新的 int 9h 中断例程的入口地址写入中断向量表
set_new_int9: push bx
push es
mov bx,0
mov es,bx

cli
mov word ptr es:[9*4],offset new_int9 - offset boot + 7e00h
mov word ptr es:[9*4+2],0
sti

pop es
pop bx
ret
;================================================================================
;新的 int 9h 中断例程
;对 Esc 键与 F1 键的按下进行处理
;按下 F1 后,改变显示颜色
;按下 Esc 后,返回到主选单
new_int9: push ax

call clear_buff ;清理键盘缓冲区

in al,60h ;从 60h 端口读取扫描码
pushf
call dword ptr cs:[200h] ;对 int 指令进行模拟,调用原来的 int 9h 中断例程

cmp al,01h ;ESC 的通码为 01h
je isesc

cmp al,3bh ;F1 的通码为 3bh
jne int9_ret
call change_time_color ;改变显示颜色

int9_ret: pop ax
iret

;模拟 int9_ret 并跳转到 show_time_ret
isesc: pop ax
add sp,4
popf
jmp show_time_ret

;================================================================================
;change_time_color 子程序
;改变显示时间的字符的颜色
;通过 inc byte ptr es:[bx] 即属性字节自增实现
change_time_color:
push bx
push cx
push es

mov bx,0b800h
mov es,bx
mov bx,160*20+30*2+1

mov cx,17

change_time_colors:
inc byte ptr es:[bx]
add bx,2
loop change_time_colors

pop es
pop cx
pop bx
ret

;================================================================================
;changee_option_color 子程序
;先将所有选项恢复显示为黑底白字(四行)
;再将选中选项显示为黑底绿字(一行)
;(al)=选中的选项的数字
change_option_color:
push bx
push cx
push es
push ax

mov bx,0b800h
mov es,bx
mov bx,160*10+1

mov cx,80*4 ;四行

change_p1s: mov byte ptr es:[bx],07h ;黑底白字
add bx,2
loop change_p1s

add al,9
mov bl,160
mul bl
mov bx,ax
inc bx ;计算选中的选项那一行的偏移

mov cx,80 ;一行

change_p2s: mov byte ptr es:[bx],02h ;黑底绿字
add bx,2
loop change_p2s

pop ax
pop es
pop cx
pop bx
ret

;================================================================================
;段寄存器初始化
;(es)=0b800h, (ds)=0
init_reg: mov ax,0b800h
mov es,ax

mov ax,0
mov ds,ax
ret

;boot 程序结束
boot_end: db 512 dup (0)
;填充字节
nop

;================================================================================
;cpy_boot: mov ax,cs
; mov ds,ax
; mov si,offset boot
;
; mov ax,0
; mov es,ax
; mov di,7e00h
;
; mov cx,offset boot_end-offset boot
; cld
; rep movsb
; ret

;================================================================================

code ends

end start


代码学习自 【bilibili: 紫狐遗梦】汇编语言从0开始 课程设计二,有一些修改

最终结果展示:


用 Oracle VM VirtualBox 建立的 windows xp 32位 虚拟机


从软驱启动


显示时钟


引导现有的操作系统


被写入程序的软盘


设置时间


设置成功


改变显示颜色


汇编语言实验合集

汇编语言实验合集

实验 1 查看 CPU 和内存,用机器指令和汇编指令编程
实验 2 用机器指令和汇编指令编程
实验 3 编程、编译、连接、跟踪
实验 4 [bx] 和 loop 的使用
实验 5 编写、调试具有多个段的程序
实验 6 实践课程中的程序
实验 7 寻址方式在结构化数据访问中的应用
实验 8 分析一个奇怪的程序
实验 9 根据材料编程
实验 10 编写子程序
课程设计 1
实验 11 编写子程序
实验 12 编写 0 号中断的处理程序
实验 13 编写、应用中断例程
实验 14 访问 CMOS RAM
实验 15 安装新的 int9 中断例程
实验 16 编写包含多个功能子程序的中断例程
实验 17 编写包含多个功能子程序的中断例程
课程设计 2