实验三 子程序、宏与中断程序设计

实验三 子程序、宏与中断程序设计

实验目的

  1. 掌握子程序、宏和中断程序的设计方法。
  2. 熟悉在 PC 机上建立、汇编、连接、调试和运行 8086/8088 汇编语言程序的过程。

实验内容

  1. 编写一个子程序计算 z = f (x, y) = x / y + x - y(x, y, z 有符号数内存字数)。要求通过堆栈传送输入参数,输出参数通过 AX 传递。(要求有输入和输出,且有提示)。
  2. 编写一个宏,求三个数的最大数,原型为:MAX3 x, y, z, min,最大值要求输出。
  3. 挂接 1CH 中断,正计时 90 秒后退出。要求屏幕显示 0 ~ 89 的秒数。

实验过程和程序

任务一

编写一个子程序计算 z = f (x, y) = x / y + x - y(x, y, z 有符号数内存字数)。要求通过堆栈传送输入参数,输出参数通过 AX 传递。(要求有输入和输出,且有提示)。

折叠/展开代码
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
; function.asm
stack segment stack
db 1024 dup(?)
stack ends

data segment
xMsg db 'enter x:', '$'
yMsg db 'enter y:', '$'
zMsg db 'f(x, y) = x / y + x - y = $'
xStr db 20, ?, 20 dup('$')
yStr db 20, ?, 20 dup('$')
zStr db 20 dup('$')
x dw 0
y dw 0
z dw 0
data ends

code segment 'code'
assume cs:code, ds:data, ss:stack
start:
; init segment register
mov ax, data
mov ds, ax

; 提示输入 x
lea dx, xMsg
mov ah, 09H
int 21H
call newLine

; 读取 x
lea dx, xStr
mov ah, 0aH
int 21H
call newLine

; 提示输入 y
lea dx, yMsg
mov ah, 09H
int 21H
call newLine

; 读取 y
lea dx, yStr
mov ah, 0aH
int 21H
call newLine

; 转换 xStr
lea ax, xStr
adc ax, 02H ; 获取字符串首地址
push ax
xor ax, ax
mov al, xStr[1] ; 获取字符串位数
push ax
call far ptr strToNumber
add sp, 4 ; 平衡堆栈
mov x, ax

; 转换 yStr
lea ax, yStr
adc ax, 02H ; 获取字符串首地址
push ax
xor ax, ax
mov al, yStr[1] ; 获取字符串位数
push ax
call far ptr strToNumber
add sp, 4 ; 平衡堆栈
mov y, ax

; 计算 f(x, y)
mov ax, x
push ax
mov ax, y
push ax
call far ptr cal ; 调用计算子过程
add sp, 4 ; 平衡堆栈

; 将结果转换为 str
push ax
lea ax, zStr
push ax
call far ptr numberToStr
add sp, 4 ; 平衡堆栈

; 显示结果
lea dx, zMsg
mov ah, 09H
int 21H
lea dx, zStr
mov ah, 09H
int 21H


; Exit
mov ax, 4c00H
int 21H

; 计算 (x / y + x - y) 的通用过程
; 入口参数:调用子程序前按顺序压入 x, y
; 出口参数:AX = 计算结果
cal proc far
push bp
mov bp, sp
push bx
push cx

mov bx, [bp + 6]
mov cx, [bp + 8]
mov ax, cx
xor dx, dx
div bx
add ax, cx
sub ax, bx

pop cx
pop bx
pop bp
ret
cal endp

; 字符串转数值的通用过程
; 入口参数:调用前按顺序压入字符串首地址,字符个数
; 出口参数:转换结果保存在 AX 中
strToNumber proc far
push bp
mov bp, sp

push bx
push cx
push dx
push si
; ax 不需要压入

mov cx, [bp + 6] ; 字符个数
mov si, [bp + 8] ; 字符串首地址
mov ch, 0
; dec cl

; 打印该字符串
; mov dx, si
; mov ah, 09H
; int 21H
; call newLine

; 显示字符串位数
; mov dl, cl
; mov ah, 02H
; adc dl, '0'
; int 21H
; call newLine

xor ax, ax ; 将 ax 清零,用于保存转换结果
xor dx, dx ; dx 用于暂存每一位字符
xor bx, bx ; bx 用于存放乘数 10
mov bl, 0aH

convertNum:
mul bx ; 将 ax 乘以 10
mov dl, [si]
sub dl, '0' ; dh 为 0
add ax, dx
inc si
loop convertNum

pop si
pop dx
pop cx
pop bx
pop bp
ret
strToNumber endp

; 将数值转换为字符串的通用方法
; 入口参数: 按顺序压入数值、目的字符串首地址
; 出口参数: 无
numberToStr proc far
push bp
mov bp, sp

push ax
push bx
push cx
push dx
push si

mov ax, [bp + 8] ; 取出数值
mov si, [bp + 6] ; 取出目的地址
mov bx, 0aH ; 用于保存 10

mov dx, ax ; 保存 ax
xor cx, cx ; 用于保存字符串的长度
getLength:
inc cx
div bl
xor ah, ah
cmp ax, 0
jnz getLength

mov ax, dx ; 恢复 ax

dec cx
add si, cx ; 将 si 指向字符串的末尾

convertStr:
xor dx, dx
div bx ; dx.ax / bx 余数在 dx, 商在 ax
add dx, '0'
mov [si], dl
dec si
cmp ax, 0
jnz convertStr

pop si
pop dx
pop cx
pop bx
pop ax
pop bp
ret
numberToStr endp

; 换行的通用过程
newLine proc
push ax
push dx

mov ah, 02H
mov dl, 0dH
int 21H
mov ah, 02H
mov dl, 0aH
int 21H

pop dx
pop ax
ret
newLine endp

code ends
end start

任务二

编写一个宏,求三个数的最大数,原型为:MAX3 x, y, z, min,最大值要求输出。

折叠/展开代码
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
; max3.asm

; 获取两个数中较大的数的宏
; 入口参数: num1, num2
; 出口参数: AX = max(num1, num2)
max2 macro num1, num2
local bigFlag, endFlag
push dx
mov dx, num1
cmp dx, num2
jnb bigFlag
mov ax, num2
jmp endFlag
bigFlag:
mov ax, dx
endFlag:
pop dx
endm

; 获取三个数中较大的数的宏
; 入口参数: num1, num2, num3
; 出口参数: AX = max(num1, num2, num3)
max3 macro mnum1, mnum2, mnum3
max2 mnum1, mnum2

max2 ax, mnum3
endm

stack segment stack
db 1024 dup(?)
stack ends

data segment
x dw 0
y dw 0
z dw 0
xMsg db 'enter x:$'
yMsg db 'enter y:$'
zMsg db 'enter z:$'
rMsg db 'the bigest num is $'
xStr db 20, ?, 20 dup('$')
yStr db 20, ?, 20 dup('$')
zStr db 20, ?, 20 dup('$')
result db 20 dup('$')
data ends

code segment 'code'
assume cs:code, ds:data, ss:stack
start:
; init segment register
mov ax, data
mov ds, ax

; 提示输入 x
lea dx, xMsg
mov ah, 09H
int 21H
call newLine

; 读取 x
lea dx, xStr
mov ah, 0aH
int 21H
call newLine

; 提示输入 y
lea dx, yMsg
mov ah, 09H
int 21H
call newLine

; 读取 y
lea dx, yStr
mov ah, 0aH
int 21H
call newLine

; 提示输入 z
lea dx, zMsg
mov ah, 09H
int 21H
call newLine

; 读取 z
lea dx, zStr
mov ah, 0aH
int 21H
call newLine

; 转换 xStr
lea ax, xStr
adc ax, 02H ; 获取字符串首地址
push ax
xor ax, ax
mov al, xStr[1] ; 获取字符串位数
push ax
call far ptr strToNumber
add sp, 4 ; 平衡堆栈
mov x, ax

; 转换 yStr
lea ax, yStr
adc ax, 02H ; 获取字符串首地址
push ax
xor ax, ax
mov al, yStr[1] ; 获取字符串位数
push ax
call far ptr strToNumber
add sp, 4 ; 平衡堆栈
mov y, ax

; 转换 zStr
lea ax, zStr
adc ax, 02H ; 获取字符串首地址
push ax
xor ax, ax
mov al, zStr[1] ; 获取字符串位数
push ax
call far ptr strToNumber
add sp, 4 ; 平衡堆栈
mov z, ax

max3 x, y, z

; 将结果转换为字符串
push ax
lea ax, result
push ax
call far ptr numberToStr
add sp, 4 ; 平衡堆栈

; 显示结果提示信息
lea dx, rMsg
mov ah, 09H
int 21H

; 显示结果
lea dx, result
mov ah, 09H
int 21H

; Exit
mov ax, 4c00H
int 21H

; 字符串转数值的通用过程
; 入口参数:调用前按顺序压入字符串首地址,字符个数
; 出口参数:转换结果保存在 AX 中
strToNumber proc far
push bp
mov bp, sp

push bx
push cx
push dx
push si
; ax 不需要压入

mov cx, [bp + 6] ; 字符个数
mov si, [bp + 8] ; 字符串首地址
mov ch, 0
; dec cl

; 打印该字符串
; mov dx, si
; mov ah, 09H
; int 21H
; call newLine

; 显示字符串位数
; mov dl, cl
; mov ah, 02H
; adc dl, '0'
; int 21H
; call newLine

xor ax, ax ; 将 ax 清零,用于保存转换结果
xor dx, dx ; dx 用于暂存每一位字符
xor bx, bx ; bx 用于存放乘数 10
mov bl, 0aH

convertNum:
mul bx ; 将 ax 乘以 10
mov dl, [si]
sub dl, '0' ; dh 为 0
add ax, dx
inc si
loop convertNum

pop si
pop dx
pop cx
pop bx
pop bp
ret
strToNumber endp

; 将数值转换为字符串的通用方法
; 入口参数: 按顺序压入数值、目的字符串首地址
; 出口参数: 无
numberToStr proc far
push bp
mov bp, sp

push ax
push bx
push cx
push dx
push si

mov ax, [bp + 8] ; 取出数值
mov si, [bp + 6] ; 取出目的地址
mov bx, 0aH ; 用于保存 10

mov dx, ax ; 保存 ax
xor cx, cx ; 用于保存字符串的长度
getLength:
inc cx
div bl
xor ah, ah
cmp ax, 0
jnz getLength

mov ax, dx ; 恢复ax

dec cx
add si, cx ; 将 si 指向字符串的末尾

convertStr:
xor dx, dx
div bx ; dx.ax / bx 余数在 dx, 商在 ax
add dx, '0'
mov [si], dl
dec si
cmp ax, 0
jnz convertStr

pop si
pop dx
pop cx
pop bx
pop ax
pop bp
ret
numberToStr endp

; 换行的通用过程
newLine proc
push ax
push dx

mov ah, 02H
mov dl, 0dH
int 21H
mov ah, 02H
mov dl, 0aH
int 21H

pop dx
pop ax
ret
newLine endp

code ends
end start

任务三

挂接 1CH 中断,正计时 90 秒后退出。要求屏幕显示 0 ~ 89 的秒数。

折叠/展开代码
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
; timer.asm

stack segment stack
db 1024 dup(?)
stack ends

data segment
oldInt dw 0, 0
timer dw 0
count dw 0
addr equ 1cH * 4 ; 1cH 号中断向量的地址
msg db 20 dup('$')
data ends

code segment 'code'
assume cs:code, ds:data
start:
; Init ds, es
mov ax, data
mov ds, ax
mov ax, 0
mov es, ax

; 保存原中断
mov ax, es:[addr]
mov oldInt[0], ax
mov ax, es:[addr + 2]
mov oldInt[2], ax

; 设置新中断
mov word ptr es:[addr], offset printTime
mov word ptr es:[addr + 2], seg printTime

mov dl, '0' ; 由于我写的数值转字符串不能正确处理数值 0
mov ah, 02H ; 在这里先直接显示 0
int 21H
call newLine
again:
; 比较输出次数
cmp timer, 89
jae exit
jmp again
exit:
; 恢复原中断
mov ax, oldInt[0]
mov es:[addr], ax
mov ax, oldInt[2]
mov es:[addr + 2], ax

; Exit
mov ax, 4c00H
int 21H

printTime proc far
push ax
push bx
push cx
push dx

; 开中断
sti

inc count
cmp count, 20 ; 比较被调用次数,若小于,则还没到 1 s
jb exitFlag
inc timer
mov count, 0 ; 将计次清零

; 显示计时器中的数值
mov ax, timer ; 将数值转换成字符串
push ax
lea ax, msg
push ax
call far ptr numberToStr
add sp, 4 ; 平衡堆栈
lea dx, msg
mov ah, 09H
int 21H
call newLine ; 换行

exitFlag:
pushf
call dword ptr oldInt ; 调用原中断,形成中断链
pop dx
pop cx
pop bx
pop ax
iret
printTime endp

; 将数值转换为字符串的通用方法
; 入口参数: 按顺序压入数值、目的字符串首地址
; 出口参数: 无
numberToStr proc far
push bp
mov bp, sp

push ax
push bx
push cx
push dx
push si

mov ax, [bp + 8] ; 取出数值
mov si, [bp + 6] ; 取出目的地址
mov bx, 0aH ; 用于保存 10

mov dx, ax ; 保存 ax
xor cx, cx ; 用于保存字符串的长度
getLength:
inc cx
div bl
xor ah, ah
cmp ax, 0
jnz getLength

mov ax, dx ; 恢复ax

dec cx
add si, cx ; 将 si 指向字符串的末尾

convertStr:
xor dx, dx
div bx ; dx.ax / bx 余数在 dx, 商在 ax
add dx, '0'
mov [si], dl
dec si
cmp ax, 0
jnz convertStr

pop si
pop dx
pop cx
pop bx
pop ax
pop bp
ret
numberToStr endp

; 换行的通用过程
newLine proc
push ax
push dx

mov ah, 02H
mov dl, 0dH
int 21H
mov ah, 02H
mov dl, 0aH
int 21H

pop dx
pop ax
ret
newLine endp

code ends
end start

实验结果

任务一

运行结果

运行结果

任务二

运行结果

运行结果

任务三

运行结果

运行结果

实验体会

本次实验第一题主要在于学会编写子程序的格式、向子程序传递参数的方法。子程序分为近调用和远调用,本次实验中,用于换行的子程序不需要参数,我编写为近调用;其他的子程序都需要传递参数,我编写为远调用,并且需要调用者来平衡堆栈。

第二题主要为编写宏,宏的编写比子程序要简单一些,尤其是传递参数时,并且宏中还可以嵌套宏。在编译时,编译器会帮我们把宏进行展开。

编写两道题的代码时,共同的难点就是如何处理数字的输入和输出,这涉及到数值和对应字符串的转换,我编写了两个通用过程来处理数值和字符串的相互转换。

第三题主要为中断的使用。一般挂接中断时,分为以下几个步骤:

  1. 保存原中断程序地址
  2. 挂接自己的中断
  3. 在自己的中断程序中,调用原中断,形成中断链
  4. 退出时,恢复原中断链