0%

13:8086CPU_Learning(11)

这几天一直在玩,要抓紧学完,去学别的东西哦

直接定址表

学习如何有效的组织数据,以及相关的编程技术

描述了单元长度的标号

我们先以一个程序为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code 
code segment
a: db 1,2,3,4,5,6,7,8
b: dw 0
start:
mov si,offset a
mov bx,offset b
s:
mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop s
mov ax,4c00H
int 21h
code ends
end start

这里的 a b start s code都是标号。这些标号仅仅表示了内存单元的地址

但是我们还有一种标号,这种标号不仅表示内存单元的地址,同时还表示了内存单元的长度

上面的程序可以写成下面的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code 
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start:
mov si,offset a
mov bx,offset b
s:
mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop s
mov ax,4c00H
int 21h
code ends
end start

在code段中使用的标号a,b后面没有 :,他们是同时描述内存地址和长度的标号,例如:

  • 标号a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元
  • 标号b,描述了地址code:8,和从这个地址开始,以后的内存单元都是字单元

可见,使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据。我们将这种标号称为 数据标号

在其他段中使用数据标号

指定段寄存器

在其他段中,也可以使用数据标号来描述存储数据的单元的地址和长度

不过要注意,在后面加有”:“的地址标号,只能在代码段中使用,不能在其他段中使用

比如我们以一个累加程序为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code,ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov bx,8
s:
mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00H
int 21h
code ends
end start

注意,如果想在代码段中直接用数据标号访问数据,需要用都安寄存器和标号所在的段进行关联,否则编译器无法却确定标号的段地址在哪一个寄存器里。

这里并不是说,如果用assume指令将段寄存器和某个段相联系,段寄存器中就真的回存放该段的地址。我们在程序中,仍然需要指令对段寄存器进行设置

将标号当作数据定义

可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。比如:

1
2
3
4
5
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends

数据标号c存储的两个字型数据为标号a,b的偏移地址。相当于:

c dw offset a, offset b

再比如:

1
2
3
4
5
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends

数据标号c处存储的两个双字型为标号a的偏移地址和段地址,标号b的偏移地址和段地址。相当于:

c dw offset a,seg a,offset b,seg b

这里的seg操作符,功能为取得某一标号的段地址

直接定址表

我们注意到数值015和字符”0”“F”之间并没有直接的映射关系,所以我们需要再他们之间建立新的映射关系

数值09和字符”0”“9”之间的映射关系最明显,有 数值 + 30H = 对应字符的ASCII值

数值1015和字符”A”“F”之间的映射关系则是,数值 + 37H = 对应字符的ASCII值

具体的做法是,建立一张表,表中依次存储字符”0”“F”,我们可以通过数值015直接查找到对应的字符

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
assume cs:code
stack segment
dw 16 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,3AH
call show

mov ax,4c00H
int 21h

showbyte:
jmp short show
table db '0123456789ABCDEF' ;字符表

show:
push bx
push es

mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1 ;取高四位
and al,00001111b ;取低四位

mov bl,ah
mov bh,0
mov ah,table[bx]

mov bx,0b800H
mov es,bx
mov es:[160*12+40*2],ah

mov bl,al
mov bh,0
mov al,table[bx]

mov es:[160*12+40*2+2],al

pop es
pop bx

ret

code ends
end start

这张表定义后可以实现映射操作,当我们向ax中传输一个数值时,它会返回这个数值到显示器上

这种映射关系一般用作以下几种用途:

  • 为了算法的清晰和简洁
  • 为了加快运算速度
  • 为了使程序易于填充

接下来编写一个子程序sin (x),  x ∈ {0, 30, 60, 90, 120, 150, 180}并在屏幕中央显示结果,我们可以用麦克劳林公式进行计算sin(x),不过为了加快运算速度,在这里我们使用映射来加快这个过程。我们可以写出以下程序:

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
assume cs:code
stack segment
dw 16 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,120
call show

mov ax,4c00H
int 21h

showsin:
jmp short show
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180 ;字符串偏移地址
ag0 db '0',0
ag30 db '0.5',0
ag60 db '0.866',0
ag90 db '1',0
ag120 db '0.866',0
ag150 db '0.5',0
ag180 db '0',0
show:
push bx
push es
push si
mov bx,0b800H
mov es,bx
;用角度/30作为相对于table的偏移值,取得对应的字符串的偏移地址,放在bx中
mov ah,0
mov bl,30
div bl
mov bl,al
mov bh,0
add bx,bx ;注意每个标号实际上使双字节的
mov bx,table[bx]
;显示
mov si,160*12+40*2
shows:
mov ah,cs:[bx] ;此时bx存储了表中的偏移地址
cmp ah,0
je showret
mov es:[si],ah
inc bx
add si,2
jmp short shows
showret:
pop si
pop es
pop bx
ret


code ends
end start

上面这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称之为直接定址表

程序入口的直接定址表

我们可以在直接定址表中存储子程序的地址,从而方便的实现不同子程序的调用。

我们编写以下程序

image.png

得知需求之后,分析一下功能的实现:

  1. 清屏:讲显存中当前屏幕中的字符设置为空格符
  2. 设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0,1,2位
  3. 设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4,5,6位
  4. 向上滚动一行:依次将第n+1行的内容复制到第n行;最后一行为空

我们可以写出以下程序:

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
assume cs:code
stack segment
dw 32 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,32

mov ah,3
mov al,1
call setscreen

mov ax,4c00h
int 21h
setscreen:
jmp short set
table dw sub1,sub2,sub3,sub4
set:
push bx
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx

call word ptr table[bx]

sret:
pop bx
ret

;清屏
sub1:
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s:
mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
;设置前景色
sub2:
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s:
and byte ptr es:[bx],11111000b
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
;设置背景色
sub3:
push bx
push cx
push es
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub3s:
and byte ptr es:[bx],10001111b
or es:[bx],al
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
;上移一行
sub4:
push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld
mov cx,24
sub4s:
push cx
mov cx,160
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1:
mov byte ptr [160*24+si],' '
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
code ends
end start

根据上面的程序需求即可修改对应的参数

最后的挑战

字符串打印

image.png

程序如下,利用栈和int 16h模拟了键盘的输入和撤回:

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
assume cs:code
stack segment
dw 128 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
call clean
mov dh,12
mov dl,25
call getstr

mov ax,4c00h
int 21h

charstack:
jmp short charstart
table dw charpush,charpop,charshow
top dw 0 ;栈顶
charstart:
push bx
push dx
push di
push es

cmp ah,2
ja sret
mov bl,ah
mov bh,0
add bx,bx ;表项是二字节的,所以偏移位置要左移一位
jmp word ptr table[bx]
charpush:
mov bx,top
mov [si][bx],al
inc top
jmp sret
charpop:
cmp top,0
je sret
dec top
mov bx,top
mov al,[si][bx]
jmp sret
charshow:
mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh ;行号
mov di,ax
add dl,dl
mov dh,0 ;列号
add di,dx
mov bx,0
charshows:
cmp bx,top
jne noempty
mov byte ptr es:[di],' '
jmp sret
noempty:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' '
inc bx
add di,2
jmp charshows
sret:
pop es
pop di
pop dx
pop bx
ret
; bye:
; mov ax,4c00h
; int 21h
getstr:
push ax
getstrs:
mov ah,0
int 16h
; cmp al,1
; je bye
cmp al,20h
jb nochar ;字符码小于20h,说明不是字符
mov ah,0
call charstack ;字符入栈
mov ah,2
call charstack ;显示字符
jmp getstrs
nochar:
cmp ah,0eh ;BS的扫描码
je backspace
cmp ah,1ch ;Enter的扫描码
je enter
jmp getstrs
backspace:
mov ah,1
call charstack ;字符出栈
mov ah,2
call charstack ;显示字符
jmp getstrs
enter:
mov al,0
mov ah,0
call charstack ;0入栈
mov ah,2
call charstack ;显示字符
pop ax
ret
;清屏
clean:
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
cleans:
mov byte ptr es:[bx],' '
add bx,2
loop cleans
pop es
pop cx
pop bx
ret
code ends
end start