80×86 实模式入门(4): 基础程序设计
80x86 实模式入门(4)基础程序设计
一、 条件结构与循环结构
有了之前两部分的内容,条件与循环就是个套路问题
关键指令:控制转移、比较
例子: 在 ADDR 单元中存放着数 NUMBER 的地址,试编制一程序把 NUMBER 中 1 的个数存入 COUNT 单元
DATA SEGMENT
ADDR DW NUMBER
NUMBER DW ?
COUNT DW ?
DATA ENDS
;
STACK SEGMENT
DB 256 DUP(0)
TOS LABEL WORD
STACK ENDS
;
CODE SEGMENT
MAIN PROC FAR
ASSUME DS:DATA, SS:STACK, CS:CODE
MOV AX, STACK
LEA SP, TOS
;
MOV AX, DATA
MOV DS, AX
;
MOV CX, 0
MOV BX, ADDR
MOV AX, [BX]
REPEAT:
TEST AX, 0FFFFH ; 回顾一下,TEST 是 AND 的不记录结果的形式
JZ EXIT ; AX AND FFFF 如果为 0 (其实就是 AX 为 0)则进入退出代码行
JNS SHIFT ; 如果 AX 首位为 0 跳 SHIFT
INC CX ; 如果 AX 首位为 1 则计数器加 1,然后自然进入 SHIFT
SHIFIT:
SHL AX, 1 ; 逻辑左移
JMP REPEAT
EXIT:
MOV COUNT, CX
;
MOV AX, 4C00H
INT 21H
MAIN ENDP
CODE ENDS
END MAIN
二、子程序调用与中断
1. 子程序调用
子程序又称为过程,它相当于高级语言中的过程和函数,所以后面其实就是总结一下如何在汇编语言中定义和调用“函数”
(1)过程定义伪操作
其实之前定义 MAIN 过程的时候就已经是这个套路了
-
格式
<function_name> PROC <type> ... RET <function_name> ENDP
-
注意 type 可以为 FAR 或 NEAR,这个将直接影响 RET 的机制:
- 如果为 FAR,RET 将从栈中取出 CS:IP ,也就是 32 位(4个字节)的内容
- 如果为 NEAR,RET 将从栈中取出 IP,也就是 16 位的内容
(2)过程调用指令
-
格式
CALL <function_name>
-
这个过程操作系统执行的操作
-
看调用过程是什么类型
-
FAR 类型将当前 CS:IP (先 IP 后 CS)压入栈,NEAR 类型将当前 IP 压入栈
这个和 RET 完全对应,由操作系统自动完成
-
(3)参数传递
汇编语言和高级语言在这部分看起来最大的差别就是如何传递参数了,可以看到过程定义并没有给参数的传递留有位置,这就需要一些其他操作。
I. 通过寄存器传参
-
思路:就是把要用的参数放到寄存器里面,然后子过程中直接使用这些寄存器就行
-
举例
... MAIN PROC FAR ... MOV AX, 233 CALL INCREASE ... MAIN ENDP INCREASE PROC NEAR INC AX RET INCREASE ENDP ...
II. 通过存储器传参
-
思路:建立数据段的时候为将来的参数留好位置,调用过程前将参数放到存储器里面
-
举例
DATA SEGMENT ... ARG1 ? ... DATA ENDS ... MAIN PROC FAR ... MOV ARG1, 233 CALL INCREASE ... MAIN ENDP INCREASE PROC NEAR MOV AX, ARG1 INC AX MOV ARG1, AX RET
III. 通过地址表传参
- 思路:其实就是升级版存储器传参,在存储器中建个地址表,传参的时候把表头传给子程序即可
- 举例:略
IV. 通过堆栈传参
-
思路:通过堆栈将参数传递过去,这个在递归程序中有重要应用
-
注意:RET n 的使用,n 代表除了弹出 CS:IP 还要弹出几个值(一般这些参数都没有从栈中弹出),以保护堆栈平衡
-
举例
MAIN PROC FAR ... PUSH AX CALL INCREASE ... MAIN PROC ENDP INCREASE PROC FAR POP AX INC AX RET INCREASE ENDP
2. 中断
基础概念:计算机在执行正常程序的过程中,当出现异常事件或事先安排好的事件,迫使 CPU 暂时中止现行程序,执行中断处理程序,处理完后,CPU 继续处理之前被暂时中止的程序
中断和子程序调用的差别:
- 中断自动保存标志寄存器、子程序不会
- 中断可以随机发生也可以定制、子程序只能定制
其实中断有好多,看看它们的分类:
-
硬件中断
-
非屏蔽中断(NMI:Non Maskable Interrupt)
-
可屏蔽中断(MI:Maskable Interrupt)
-
中断允许位:IF(Interrupt Flag)
-
开中断指令:STI
-
关中断指令:CLI
-
中断屏蔽寄存器(IMR):可实现对指定设备的中断屏蔽
-
中断命令寄存器
用于控制中断是否结束
-
-
软件中断(不可屏蔽)
- 程序中中断指令 INT n
- CPU 的某些运行结果
- 除法错误
- 溢出
- DEBUG 设置的中断
- 单步中断
- 断点中断
-
中断优先级
- 软件中断 > 非屏蔽中断 > 可屏蔽中断 > 单步中断
- 可根据需要给端口 20H 送命令,改变优先级
乱起八糟一大堆,但是其实初级阶段看看软件中断就好,因为在编程的时候能自己设计的只有软件中断,下面总结一下软件中断
(1)中断向量表
-
中断类型号:每个中断对应一个中断类型号,0-FFH
-
中断向量:CS:IP
-
中断向量表
- 中断处理程序的入口地址表
- 保存在存储器中,大小 1 kB(00000H - 003FFH)
- 每个中断向量占 4 Bytes,对应 CS、IP
- 每个中断向量的地址 = 4 * 中断类型号
-
DOS 调用取中断向量
MOV AH, 35; DOS 接口,取中断向量 MOV AL, 1CH; 所取中断向量号 INT 21H; 取出的中断向量保存在 ES:BX 中
-
DOS 调用设置中断向量
MOV AH, 25; DOS 接口,设置中断向量 MOV AL, 1CH INT 21H; 根据 DS:DX 设置中断向量
(2)软件中断过程
要想设计软件中断,就得了解一下软件中断的相应过程
- 中断响应(由 CPU 自动完成)
- 取中断类型号 N
- 标志寄存器内容压栈
- CS:IP 压栈(先 CS 后 IP)
- 禁止硬件、单步中断
- IF = 0
- TF = 0
- 转到中断服务处理程序入口(从中断向量表取 4 N 单元内容送 IP,4 N + 2 单元内容送 CS)
- 中断处理(自己设计)
- 保存现场
- 开中断(STI)(根据需要)
- 程序主体
- 中断结束(EOI)(硬件中断时)
- 关中断(CLI)
- 恢复现场
- 中断返回(IRET)(不要协程 RET)
- 中断返回(CPU 自动完成)
- 恢复 CS、IP
- 恢复标志寄存器
(3)自定义中断
I. 直接设置
; 设置中断向量
MOV AX, 0
MOV ES, AX ; 设置中断向量基地址
MOV BX, N*4 ; 设置中断向量 n 偏移地址
MOV AX OFFSET INTHAND
MOV ES: WORD PRT[BX], AX ; 设置中断处理程序入口地址偏移量
MOV AX, SEG INTHAND
MOV ES:WORD PTR[BX+2], AX ; 设置中断处理程序入口段基地址
; 中断处理程序
INTHAND PROC FAR
...... ; 程序主体
IRET
INTHAND ENDP
II. DOS 调用设置
MOV AH, 35; DOS 接口,取中断向量
MOV AL, 1CH; 所取中断向量号
INT 21H; 取出的中断向量保存在 ES:BX 中
PUSH ES
PUSH BX ; 保护原有中断向量
... ; 把中断处理程序的入口 CS:IP 存到 DS:DX 中
MOV AH, 25; DOS 接口,设置中断向量
MOV AL, 1CH
INT 21H; 根据 DS:DX 设置中断向量

原文链接:80x86 实模式入门(4): 基础程序设计
nightmorning的博客 版权所有,转载请注明出处。
还没有任何评论,你来说两句吧!