深入探索MS - DOS编程:I/O端口、指令及MASM参考
1. I/O端口与指令基础
1.1 I/O端口概述
每个输入 - 输出端口都有一个介于0到FFFFh之间的特定编号。端口在控制硬件设备时发挥着重要作用,例如控制扬声器,可通过快速切换扬声器锥体进出状态来实现发声;还能通过串口与异步适配器直接通信,只需设置端口参数(如波特率、奇偶校验等)并通过端口发送数据。
以键盘端口为例,当按下一个键时,键盘控制器芯片会向端口60h发送一个8位扫描码。这个按键操作会触发一个硬件中断,促使CPU调用ROM BIOS中的INT 9。INT 9从端口读取扫描码,查找该键的ASCII码,并将这两个值存储在键盘输入缓冲区中。实际上,甚至可以完全绕过操作系统,直接从端口60h读取字符。
大多数硬件设备除了有用于传输数据的端口外,还有用于监控设备状态和控制设备行为的端口。
1.2 IN和OUT指令
IN指令用于从端口输入一个字节、字或双字,而OUT指令则用于向端口输出一个值。这两条指令的语法如下:
IN accumulator,port
OUT port,accumulator
其中,port可以是0到FFh范围内的常量,也可以是DX寄存器中0到FFFFh之间的值。对于8位传输,accumulator必须是AL;对于16位传输,必须是AX;对于32位传输,必须是EAX。以下是一些示例:
in al,3Ch ; 从端口3Ch输入字节
out 3Ch,al ; 向端口3Ch输出字节
mov dx, portNumber ; DX可以包含端口号
in ax,dx ; 从DX指定的端口输入字
out dx,ax ; 向同一端口输出字
in eax,dx ; 从端口输入双字
out dx,eax ; 向同一端口输出双字
1.3 端口操作流程
graph TD;
A[开始] --> B[设置端口参数];
B --> C[使用IN或OUT指令进行数据传输];
C --> D{是否完成操作};
D -- 是 --> E[结束];
D -- 否 --> C;
2. PC声音程序实现
2.1 声音控制原理
可以编写一个使用IN和OUT指令通过PC内置扬声器发声的程序。扬声器控制端口(编号61h)通过操纵Intel 8255可编程外围接口芯片来打开和关闭扬声器。要打开扬声器,需输入端口61h的当前值,设置最低2位,然后将该字节通过端口输出;要关闭扬声器,则清除位0和1并再次输出状态。
Intel 8253定时器芯片控制所产生声音的频率(音调)。使用时,需向端口42h发送一个介于0到255之间的值。
2.2 扬声器演示程序代码
TITLE Speaker Demo Program (Spkr.asm)
; This program plays a series of ascending notes on
; an IBM-PC or compatible computer.
INCLUDE Irvine16.inc
speaker EQU 61h
; address of speaker port
timer EQU 42h
; address of timer port
delay1 EQU 500
delay2 EQU 0D000h
; delay between notes
.code
main PROC
in al,speaker
; get speaker status
push ax
; save status
or al,00000011b
; set lowest 2 bits
out speaker,al
; turn speaker on
mov al,60
; starting pitch
L2:
out timer,al
; timer port: pulses speaker
; Create a delay loop between pitches.
mov cx,delay1
L3:
push cx
; outer loop
mov cx,delay2
L3a:
; inner loop
loop L3a
pop cx
loop L3
sub al,1
; raise pitch
jnz L2
; play another note
pop ax
; get original status
and al,11111100b
; clear lowest 2 bits
out speaker,al
; turn speaker off
exit
main ENDP
END main
2.3 程序执行步骤
- 打开扬声器 :通过端口61h设置扬声器状态字节的最低2位来打开扬声器。
or al,00000011b
out speaker,al
- 设置音调 :向定时器芯片发送60来设置初始音调。
mov al,60
out timer,al
- 添加延迟 :使用延迟循环使程序在改变音调之前暂停。由于不同计算机的处理器速度不同,延迟量可能会有所不同,可能需要调整delay1和delay2的值。
mov cx,delay1
L3:
push cx
mov cx,delay2
L3a:
loop L3a
pop cx
loop L3
- 提高音调 :每次循环从周期(1/频率)中减去1,从而提高音调。
sub al,1
jnz L2
- 关闭扬声器 :恢复扬声器端口的原始状态字节,清除最低2位以关闭扬声器。
pop ax
and al,11111100b
out speaker,al
2.4 声音程序执行流程
graph TD;
A[开始] --> B[打开扬声器];
B --> C[设置初始音调];
C --> D[添加延迟];
D --> E[提高音调];
E --> F{是否继续};
F -- 是 --> D;
F -- 否 --> G[关闭扬声器];
G --> H[结束];
3. 其他编程概念
3.1 段定义与相关指令
在编程中,有时需要创建显式的段定义。SEGMENT和ENDS指令分别用于定义段的开始和结束。当定义的段与其他段组合时,其对齐类型告知链接器要跳过多少字节,组合类型告知链接器如何组合具有相同名称的段,段的类类型提供了另一种组合段的方式。可以通过给多个段指定相同的名称并使用PUBLIC组合类型来组合它们。
ASSUME指令使汇编器能够在汇编时计算标签和变量的偏移量。段覆盖前缀指示处理器在当前指令中使用与默认段不同的段寄存器。
3.2 瞬态程序与中断处理
MS - DOS命令处理器解释在命令提示符下输入的每个命令。扩展名为COM和EXE的程序称为瞬态程序,它们被加载到内存中执行,执行完毕后释放所占用的内存。MS - DOS在瞬态程序的开头创建一个名为程序段前缀的特殊256字节块。
COM程序是机器语言程序的未修改二进制映像,而EXE程序存储在磁盘上时,前面有一个EXE头,后面跟着包含程序本身的加载模块。EXE程序的头区域用于MS - DOS正确计算段和其他组件的地址。
中断处理程序(中断服务例程)简化了输入/输出以及基本系统任务。还可以用自己的代码替换默认的中断处理程序,以提供更完整或定制的服务。中断向量表位于RAM的前1024字节(位置0:0到0:03FF),表中的每个条目是一个指向中断服务例程的32位段 - 偏移地址。
3.3 硬件中断与相关控制
硬件中断由8259可编程中断控制器(PIC)生成,它会向CPU发出信号,暂停当前程序的执行并执行中断服务例程。硬件中断允许CPU在重要数据丢失之前注意到后台的重要事件。中断可以由许多不同的设备触发,每个设备根据其中断请求级别(IRQ)具有不同的优先级。
中断标志控制CPU对外部(硬件)中断的响应方式。如果中断标志被设置,则启用中断;如果标志被清除,则禁用中断。STI(设置中断)指令启用中断,CLI(清除中断)指令禁用中断。
3.4 终止并驻留程序
终止并驻留(TSR)程序会将自身的一部分留在内存中。TSR程序最常见的用途是安装中断处理程序,这些处理程序会一直留在内存中,直到计算机重新启动或通过特殊的卸载程序移除TSR。
3.5 硬件输入 - 输出类型
x86系统提供两种类型的硬件输入 - 输出:内存映射式和基于端口式。使用内存映射式I/O时,程序可以将数据写入特定的内存地址,数据会被传输到输出设备。基于端口式I/O则需要使用IN和OUT指令来读写称为端口的特定编号位置。
3.6 编程概念总结表
| 概念 | 描述 |
|---|---|
| 段定义 | SEGMENT和ENDS指令定义段的开始和结束,多种类型控制段的组合 |
| 瞬态程序 | COM和EXE程序,加载执行后释放内存,有程序段前缀 |
| 中断处理 | 中断向量表存储中断服务例程地址,可替换默认处理程序 |
| 硬件中断 | 8259 PIC生成,中断标志控制响应,STI和CLI指令操作 |
| TSR程序 | 部分驻留内存,常用于安装中断处理程序 |
| 硬件I/O类型 | 内存映射式和基于端口式 |
4. MASM参考
4.1 MASM概述
MASM(Microsoft Macro Assembler)是一款强大的汇编工具。ML程序(ML.EXE)可对一个或多个汇编语言源文件进行汇编和链接,其语法如下:
ML [[options ]] filename [[ [[ options ]] filename ]]. . . [[/link linkoptions ]]
其中,至少需要一个文件名作为必需参数,该文件名是用汇编语言编写的源文件的名称。例如,以下命令将汇编源文件AddSub.asm并生成目标文件AddSub.obj:
ML -c AddSub.asm
4.2 ML命令行选项
ML的选项参数由零个或多个命令行选项组成,每个选项以斜杠(/)或破折号(–)开头,多个选项之间至少用一个空格分隔。命令行选项区分大小写,具体如下表所示:
| 选项 | 操作 |
| ---- | ---- |
| /AT | 启用微小内存模型支持,对违反.COM格式文件要求的代码结构生成错误消息,但不等同于.MODEL TINY指令 |
| /Blfilename | 选择备用链接器 |
| /c | 仅进行汇编,不进行链接 |
| /coff | 生成Microsoft通用对象文件格式的目标文件,通常用于32位汇编语言,但64位汇编器不支持 |
| /Cp | 保留所有用户标识符的大小写 |
| /Cu | 将所有标识符映射为大写,64位汇编器不支持 |
| /Cx | 保留公共和外部符号的大小写(默认) |
| /Dsymbol [[=value]] | 定义具有给定名称的文本宏,若值缺失则为空,多个用空格分隔的标记必须用引号括起来 |
| /EP | 生成预处理源列表(发送到STDOUT),参见/Sf |
| /ERRORREPORT [NONE|PROMPT|QUEUE|SEND] | 若汇编器在运行时失败,将诊断信息发送给Microsoft |
| /Fhexnum | 将堆栈大小设置为hexnum字节(与/link /STACK:number相同),值必须用十六进制表示,/F和hexnum之间必须有空格 |
| /Fefilename | 命名可执行文件 |
| /Fl[[filename]] | 生成汇编代码列表,参见/Sf |
| /Fm[[filename]] | 创建链接器.MAP文件 |
| /Fofilename | 命名目标文件 |
| /FPi | 为浮点算术生成仿真器修正(仅适用于混合语言),64位汇编器不支持 |
| /Fr[[filename]] | 生成源浏览器.SBR文件 |
| /FR[[filename]] | 生成扩展形式的源浏览器.SBR文件 |
| /Gc | 指定使用FORTRAN或Pascal风格的函数调用和命名约定,64位汇编器不支持 |
| /Gd | 指定使用C风格的函数调用和命名约定,64位汇编器不支持 |
| /Gz | 使用STDCALL调用约定,64位汇编器不支持 |
| /H number | 将外部名称限制为number个有效字符,默认值为31个字符,64位汇编器不支持 |
| /help | 调用QuickHelp获取ML帮助 |
| /I pathname | 设置包含文件的路径,最多允许10个/I选项 |
| /link | 链接器选项和库 |
| /nologo | 抑制成功汇编的消息 |
| /omf | 生成OMF(Microsoft对象模块格式)文件,此格式是旧的16位Microsoft链接器(LINK16.EXE)所必需的,64位汇编器不支持 |
| /Sa | 开启列出所有可用信息 |
| /safeseh | 将对象标记为不包含异常处理程序或包含所有用.SAFESEH声明的异常处理程序(在32位汇编语言中,设置为:NO),ml64.exe中不可用 |
| /Sf | 将首次通过列表添加到列表文件中 |
| /Sl width | 设置源列表的行宽(字符/行),范围为60到255或0,默认值为0,与PAGE width相同 |
| /Sn | 在生成列表时关闭符号表 |
| /Sp length | 设置源列表的页面长度(行/页),范围为10到255或0,默认值为0,与PAGE length相同 |
| /Ss text | 指定源列表的文本,与SUBTITLE text相同 |
| /St text | 指定源列表的标题,与TITLE text相同 |
| /Sx | 在列表中开启假条件语句 |
| /Ta filename | 汇编名称不以.ASM扩展名结尾的源文件 |
| /w | 与/W0相同 |
| /Wlevel | 设置警告级别,level = 0, 1, 2, 或3 |
| /WX | 若生成警告则返回错误代码 |
4.3 MASM指令
MASM有众多指令,以下是部分常见指令的介绍:
-
.386
:启用80386处理器的非特权指令汇编,禁用后续处理器引入的指令汇编,同时启用80387指令。
-
ALIGN [[number ]]
:将下一个变量或指令对齐到number的倍数的字节上。
-
ASSUME segregister:name [[, segregister:name ]]. . .
:启用对寄存器值的错误检查,汇编器会监视给定寄存器值的变化。
-
.BREAK [[.IF condition ]]
:若条件为真,则生成代码以终止.WHILE或.REPEAT块。
-
[[name ]] BYTE initializer [[, initializer ]] . . .
:为每个初始化器分配并可选地初始化一个字节的存储空间。
4.4 MASM指令操作流程
graph TD;
A[开始] --> B[选择合适的指令];
B --> C{是否需要参数};
C -- 是 --> D[设置指令参数];
C -- 否 --> E[使用指令];
D --> E;
E --> F{是否完成编程};
F -- 是 --> G[结束];
F -- 否 --> B;
5. MASM符号与运算符
5.1 MASM符号
MASM中有许多特殊符号,其含义如下表所示:
| 符号 | 含义 |
| ---- | ---- |
| $ | 当前位置计数器的值 |
|? | 在数据声明中,是汇编器分配但不初始化的值 |
| @@: | 定义一个仅在label1和label2之间可识别的代码标签,label1可以是代码开始或上一个@@:标签,label2可以是代码结束或下一个@@:标签 |
| @B | 上一个@@:标签的位置 |
| @CatStr( string1 [[, string2. . . ]] ) | 连接一个或多个字符串的宏函数,返回一个字符串 |
| @code | 代码段的名称(文本宏) |
| @CodeSize | 对于TINY、SMALL、COMPACT和FLAT模型为0,对于MEDIUM、LARGE和HUGE模型为1(数值等同) |
| @Cpu | 指定处理器模式的位掩码(数值等同) |
| @CurSeg | 当前段的名称(文本宏) |
| @data | 默认数据组的名称,除FLAT模型外,所有模型都计算为DGROUP,在FLAT内存模型下计算为FLAT(文本宏) |
| @DataSize | 对于TINY、SMALL、MEDIUM和FLAT模型为0,对于COMPACT和LARGE模型为1,对于HUGE模型为2(数值等同) |
| @Date | 系统日期,格式为mm/dd/yy(文本宏) |
| @Environ( envvar ) | 环境变量envvar的值(宏函数) |
| @F | 下一个@@:标签的位置 |
| @fardata | 由.FARDATA指令定义的段的名称(文本宏) |
| @fardata? | 由.FARDATA?指令定义的段的名称(文本宏) |
| @FileCur | 当前文件的名称(文本宏) |
| @FileName | 正在汇编的主文件的基本名称(文本宏) |
| @InStr( [[ position ]], string1, string2 ) | 查找string2在string1中首次出现位置的宏函数,若未指定position,则从string1的开头开始搜索,若未找到则返回0(整数) |
| @Interface | 关于语言参数的信息(数值等同) |
| @Line | 当前文件中的源行号(数值等同) |
| @Model | TINY模型为1,SMALL模型为2,COMPACT模型为3,MEDIUM模型为4,LARGE模型为5,HUGE模型为6,FLAT模型为7(数值等同) |
| @SizeStr( string ) | 返回给定字符串长度的宏函数,返回一个整数 |
| @stack | 近堆栈为DGROUP,远堆栈为STACK(文本宏) |
| @SubStr( string, position [[, length ]] ) | 返回从position开始的子字符串的宏函数 |
| @Time | 系统时间,格式为24小时制的hh:mm:ss(文本宏) |
| @Version | MASM 6.1中为610(文本宏) |
| @WordSize | 16位段为2,32位段为4(数值等同) |
5.2 MASM运算符
MASM的运算符可分为普通运算符和运行时运算符:
5.2.1 普通运算符
- expression1 + expression2 :返回expression1加expression2的结果。
- expression1 - expression2 :返回expression1减expression2的结果。
- expression1 * expression2 :返回expression1乘expression2的结果。
- expression1 / expression2 :返回expression1除以expression2的结果。
5.2.2 运行时运算符
运行时运算符仅用于.IF、.WHILE或.REPEAT块内,在运行时而非汇编时求值,例如:
-
expression1 == expression2
:判断是否相等。
-
expression1 != expression2
:判断是否不相等。
-
expression1 > expression2
:判断是否大于。
5.3 运算符使用流程
graph TD;
A[开始] --> B[确定运算类型];
B --> C{是否为运行时运算符};
C -- 是 --> D[在相应块中使用];
C -- 否 --> E[正常使用运算符];
D --> F{是否完成运算};
E --> F;
F -- 是 --> G[结束];
F -- 否 --> B;
综上所述,深入了解MS - DOS编程中的I/O端口、相关指令以及MASM的各种参考内容,对于掌握汇编语言编程和开发相关应用程序具有重要意义。通过合理运用这些知识和工具,可以实现更高效、更复杂的程序设计。
超级会员免费看
2760

被折叠的 条评论
为什么被折叠?



