680 likes | 847 Views
第 4 章 16 位汇编应用举例. 如何用汇编去实现高级语言的功能。 如何在底层实现键盘控制。 如何在底层实现视频控制。 如何在底层实现磁盘扇区操作。 如何在底层实现定时器应用。. 模拟 C 语言函数实现. 1. 把数据转换为字符串 在 C 语言下有这样一个函数: char *_ltoa(long value , char *string , int radix) ; 它把数值 value 按 radix 进制转换到字符串 string ,为了简单起见,我们只实现最复杂的一种,即转换为用 10 进制表示的字符串。算法为:.
E N D
第4章 16位汇编应用举例 如何用汇编去实现高级语言的功能。 如何在底层实现键盘控制。 如何在底层实现视频控制。 如何在底层实现磁盘扇区操作。 如何在底层实现定时器应用。
模拟C语言函数实现 1. 把数据转换为字符串 在C语言下有这样一个函数: char *_ltoa(long value,char *string,int radix); 它把数值value按radix进制转换到字符串string,为了简单起见,我们只实现最复杂的一种,即转换为用10进制表示的字符串。算法为:
(1) 设置标志FLAG,以决定是否显示负号; (2) SOURCE和0比较,为负则取负数的绝对值,FLAG置1; (3) 将SOURCE送AX,AX循环除以10,余数(DX)为要显示的位值,加’0’变为字符压入堆栈。一直循环到AX为0为止,入栈次数存DI; (4) 若FLAG=1,负号入栈; (5) 按入栈次数依次出栈,送DL,用INT 21H功能2显示。
.MODEL SMALL, C _LTOA PROTO :WORD ;过程声明 .DATA CR = 13 ;定义回车键值常量,等于13 LF = 10 ;定义换行键值常量,等于10 DATA1 DD -8654 .CODE ;子程序_LTOA是按十进制输出16位二进制数值SOURCE .CODE .STARTUP INVOKE _LTOA, DATA1 INVOKE _LTOA, -2345 .EXIT 0
_LTOA PROC USES DI DX SI BX SOURCE:WORD LOCAL FLAG:BYTE ;用来描述正负 MOV FLAG,0 ;初始化 MOV AX,SOURCE XOR DI,DI MOV SI,10 CMP AX,0 JGE NEXT ;有符号比较指令,AX大于等于0则跳NEXT MOV FLAG,1 ;FLAG=1,负数 NOT AX ;取反 ADD AX, 1 ;上两条指令把AX变为绝对值,如-1234 1234 INC DI
NEXT: .REPEAT ;本循环把16位二进制数转换成十进制 XOR DX,DX ;能省掉么?不能,后面要用它放余数 DIV SI ADD DL,'0' PUSH DX INC DI .UNTIL AX==0 ;FLAG=1,为负,负号入堆栈 .IF FLAG == 1 MOV DL,'-' PUSH DX .ENDIF MOV CX,DI ;入栈的次数送给CX
;本循环把堆栈中的字符串显示出来 .REPEAT POP DX ;显示字符送DL MOV AH, 2 ;DOS功能2 INT 21H .UNTILCXZ ;CX=0,退出 ;下面六条指令显示回车、换行 MOV DL, CR MOV AH, 2 INT 21H MOV DL, LF MOV AH, 2 INT 21H RET _LTOA ENDP END
字符串操作 • 计算字符串长度 在C语言中有函数strlen,格式如下: size_t strlen( const char *string ); 它用来计算字符串长度,输入参数string为0结尾的字符串,返回值为长度。我们用_strlen模拟它,算法为: 从第一个字符开始计数,依次检查其是否为0; 为0则将计数值送AX,然后返回,否则检查下一个字符。
_strlen PROC USES BX CX _string:WORD ;_STRING为字符串的偏移地址 MOV BX,_string XOR CX,CX AGAIN: MOV AL,[BX] CMP AL,0 ;AL为0,到结尾 JZ _OK INC CX INC BX MOV AL,[BX] JMP AGAIN _OK: MOV AX,CX ;返回的长度送AX RET _strlen ENDP
4.2 键盘中断 • 键盘控制原理 • 键盘数据的接收由PC机8255可编程芯片来实现的。在键盘内部有一个微处理器INTEL8048,它从系统板接收时钟信号,读取每个键入的键值,将键的扫描码送到8255的PA端口(60H)内,同时产生一个中断号为9的中断。PB端口(61H)的第7位用来控制PA端口数据的接收,该位为0时表示允许键盘输入,为1时表示禁止键盘输入。中断9的任务是,读取扫描码并把应答信号送到键盘、把扫描码转换为字符码或控制变换键和将字符码放入键盘缓冲区内。
将PB端口的第7位设置为1,键盘将不能接收数据将PB端口的第7位设置为1,键盘将不能接收数据 IN AL, 61H OR AL, 80H OUT 61H,AL • 下面的代码恢复键盘的输入功能 IN AL,61H AND AL,7FH OUT 61H,AL
不依靠中断,直接从芯片读键盘信息 • 下面的代码读取键盘的扫描码并向键盘发送应答信号: IN AL,60H ;从PA端口读取键盘扫描码 PUSH AX ;存到堆栈 IN AL,61H ;读取PB端口信息 OR AL,80H ;位7置1 OUT 61H,AL ;设置PB端口位7为1 AND AL,7FH ;位7置0 OUT 61H,AL ;复位键盘应答位
从键盘缓冲区直接读键盘扫描码 • 键盘缓冲区是一个先进先出的环形队列,其所占内存区域如下: • 缓冲区头指针:WORD类型,其内存地址为0000:041AH。 • 缓冲区尾指针:WORD类型,其内存地址为0000:041CH。 • 键盘信息缓冲区:其内存地址为0000:041EH~0000:003DH,该缓冲区的缺省长度为16个字。
.MODEL SMALL .STACK .DATA .CODE .STARTUP MOV BX,0 MOV DS,BX ;DS=0 MOV BX,4000H MOV ES,BX ;ES=4000H MOV CX,30H ;循环30H次 XOR BX,BX
AGAIN: MOV AX,0000H INT 16H ;等待键盘输入 MOV AX,DS:[041AH] MOV ES:[BX],AX ;4000H:[BX] DS:[040AH] ADD BX,2 MOV AX,DS:[041CH] MOV ES:[BX],AX ;4000H:[BX] DS:[040CH] ADD BX,2 LOOP AGAIN ;CX=CX-1, CX=0则退出 .EXIT 0 END
通过中断获取键盘信息 • 1. 使用INT 16H 该中断由BIOS提供实现键盘输入操作。检测键盘状态的过程KBSTATUS如下: KBSTATUS PROC MOV AX,0100H INT 16H ;取键盘状态信息 JNZ HAD_KEY ;ZF=0,有键输入 MOV AX, 0 ;无键输入,返回0 RET HAD_KEY: ;取键值 XOR AX, AX INT 16H ;返回键值,AH为扫描码,AL为字符码 RET KBSTATUS ENDP
通过DOS的INT 21H DOS的INT 21H中的相关功能如下: • 01H——带回显的键盘输入; • 06H——控制台的输入/输出:当DL=0FFH,表示键盘输入; • 07H——不回显、不过滤的键盘输入; • 08H——不回显的键盘输入; • 0AH——键盘输入字符串; • 0BH——检查键盘输入状态; • 0CH——清除输入缓冲区的输入功能。
例如判断按键是Y还是N代码如下: GET_KEY: MOV AH, 1 INT 21H CMP AL,‘Y’ JE IS_Y CMP AL, ‘N’ JE IS_N …… IS_Y: …… IS_N: ……
4.3 视频控制程序 • 显示器是计算机的输出外部设备之一,它通过插在主板卡槽上的显示适配器和计算机连接。屏幕上的显示分为图形方式和文本方式,每种方式可能又分为若干模式,例如图形方式有800*600和1024*768象素点阵等。通过直接操作显存、BIOS中断10H和DOS的功能可以实现视频操作
直接控制显存 • 在视频控制器上有描述屏幕图象数据的显示缓冲区,显示器扫描缓冲区中的数据,在屏幕上显示出相应的字符或图形。如果缓冲区中有多幅图象,我们把每幅图象称之为一页。不同显示模式的显示缓冲区在内存中的地址不同,不同显卡的地址也不同。因此虽然直接往显示缓冲区写数据可以获得较快的速度,但兼容性很差。
使用BIOS • BIOS用INT 10H提供了设置显示模式、显示字符或图象、取屏幕字符或图象等功能。列举常用功能如下: • 设置显示模式 输入信息: AH = 0 ; AL = 模式号。例如,AL=4为彩色图形方式(320*200),AL=1为40列*25行文本方式; 输出信息:无。 • 设置光标大小 输入信息: AH = 1 ; CH = 光标起始行号; CL = 光标终止行号。 输出信息:无。
设置光标位置 输入信息: AH = 2 ; BH = 页号; DH = 行号; DL = 页号; 输出信息:无。 • 在屏幕上写点 输入信息: AH = 12; DX = 行号; CX = 列号; AL = 前景色号 • 写字符并光标进一格 输入信息: AH = 14; AL = 字符ASCII码; BH = 页号; BL = 前景色。 输出信息:无。
例2:设置屏幕为320x200的点阵,然后从左上角(100,100)到(140,140)画一条红线。例2:设置屏幕为320x200的点阵,然后从左上角(100,100)到(140,140)画一条红线。 • 程序首先需将屏幕设置为要求的显示模式(AH=0),然后使用写点功能往屏幕上写点
.MODEL SMALL .DATA X1 DW 100 ;起始值为(100,100) Y1 DW 100 .CODE .STARTUP ;AH=0,设置图形模式 ;AL=13H,为320x200的256色模式 MOV AH,0 MOV AL,13H INT 10H
AGAIN: ;AH=0CH为向屏幕写点模式 ;AL为颜色值,等于12为红色;DX为像素行值,CX为像素列值 MOV AH,0CH MOV AL,12 MOV DX,X1 MOV CX,Y1 INT 10H INC X1 INC Y1 CMP X1,140 JB AGAIN .EXIT 0 END
使用DOS功能 • 使用DOS功能可以显示一个字符或一个字符串,功能如下: • 在当前光标位置输出一个字符 输入信息: AH = 2 ; DL = 字符ASCII码; • ● 在当前光标位置输出一个字符串 输入信息: AH = 9; DS:DX = 字符串地址,必须以$结束; 输出信息:无。
例:编写一个显示字符串的宏 COUT .MODEL SMALL COUT MACRO STRING ;定义宏 MOV AH, 9 MOV DX, OFFSET STRING INT 21H ENDM .DATA S1 DB "ABCDEFG$" .CODE .STARTUP COUT S1 ;调用宏 .EXIT 0 END
4.4 磁盘控制程序 • 常用磁盘为软盘和硬盘。前者读取数据速度慢、存储容量大。硬盘的盘面一般以铝合金为基体,软盘以聚酯塑料为基片,在它们的表面涂有磁性材料。硬盘有多个盘片固定在同一个主轴上。为了方便存储和检索数据,盘片被划分为磁道和扇区。磁道是从盘片外圈到里排序的同心圆,每个磁道分为若干扇区,将几个扇区组合成一组叫簇。扇区是磁盘数据操作的最小单位,簇是文件操作的最小单位。每个盘片上同一编号的磁道组成柱面。磁盘控制器解释来自主机的指令并向磁盘驱动器发出相应的控制信号。
常用INT 13H功能 • 磁盘复位 输入信息: AH = 0 ; 输出信息: AH = 磁盘状态,为以下值: 00H—无错; 01H--送驱动器命令非法; 02H--地址标记未找到; 03H--磁盘写保护; 04H--未找到要求扇区; 08H—DMA运行超时; 10H—CRC错; 20H—控制器错; 40H—寻道错 80H—磁盘未响应。
读取磁盘状态 输入信息: AH = 1 ; 输出信息: AH = 磁盘状态,同上。
读磁盘扇区数据到内存缓冲区 AH = 2 ; AL = 要读的扇区数; CH = 柱面号(低8位); CL = 扇区号(0-5位);6-7位为柱面号; DH = 磁头号; DL = 驱动器号,软盘A,B为0,1,对第一块硬盘80H,第二块81H,……; ES:BX = 数据缓冲区的地址。 输出信息: CF=0,操作成功,AH=0 ,AL=读出的扇区数; CF=1,操作失败,AH=出错状态,同前。
内存缓冲区内容写入磁盘 输入信息:同读盘; 输出信息:同读盘。 检验指定扇区 输入信息:不要求ES:BX,其它同读盘; 输出信息:同读盘。 • 格式化磁盘 输入信息:ES:BX=格式化参数首址,其它同读盘; 输出信息:同读盘。
设计一个简单钥匙软盘程序 • 设计程序key.exe,在它运行时只有软驱上插有钥匙盘才能正确运行,否则提示错误信息。其原理是在软盘上设置一个特殊的信息,如特殊的扇区、坏扇区等办法。我们这里是利用引导扇区的空域设置密码,程序key.exe执行时会读该密码,正确则顺利执行,否则出错。
程序将软盘的0面0磁道1扇区(面和道从0开始排,扇区从1开始排,这里是引导扇区)。可见,在4000:4020处有几个字节为0,我们用这段空间来存放密码。设密码为字符串“12345”,用命令E修改内存数据,用D4000:4000再验证修改结果。程序将软盘的0面0磁道1扇区(面和道从0开始排,扇区从1开始排,这里是引导扇区)。可见,在4000:4020处有几个字节为0,我们用这段空间来存放密码。设密码为字符串“12345”,用命令E修改内存数据,用D4000:4000再验证修改结果。
命令W行表示将内存4000:4000开始的数据写入0驱(A:)的第0个逻辑扇区(引导扇区)开始共1个扇区。当然也可以编一段程序写入。命令W行表示将内存4000:4000开始的数据写入0驱(A:)的第0个逻辑扇区(引导扇区)开始共1个扇区。当然也可以编一段程序写入。 • 为查看修改引导扇区内容后软盘能否正常工作,可以尝试磁盘文件操作,若无误则可以。
编写识别程序 .MODEL SMALL,C CMPCODE PROTO ;声明比较字符串的子程序 .DATA SEC_CODE DB "12345",0 COUNT EQU $-OFFSET SEC_CODE-1 ;取密码字符串的长度 ;$表示当前偏移地址 SECTOR DB 512 DUP(0) INF1 DB "读软盘失败",0DH,0AH,'$' INF2 DB "请插入钥匙盘",0DH,0AH,'$' INF3 DB "密码正确,谢谢使用",0DH,0AH,'$‘ .CODE .STARTUP
MOV AX,0201H ;读引导扇区 MOV CX,1 MOV DX,0 MOV BX,SEG SECTOR MOV ES,BX MOV BX,OFFSET SECTOR INT 13H CMP AH,0 ;AH =0 读成功,否则失败 JE READ_OK MOV AH,9 MOV DX,OFFSET INF1 INT 21H .EXIT 0
READ_OK: ;读成功 CALL CMPCODE CMP AX,0 ;返回值为0,相等 JZ CMP_OK ;比较读出的相应位置内容是否和密码相等 MOV AH,9 MOV DX,OFFSET INF2 ;不相等,提示插入软盘 INT 21H .EXIT 0 CMP_OK: ;相等 MOV AH,9 MOV DX,OFFSET INF3 INT 21H .EXIT 0
;比较sector+20h和SEC_CODE是否相等,返回值为AX CMPCODE PROC LOCAL FLAGS:WORD MOV FLAGS,0 ;初始化 MOV SI,OFFSET SEC_CODE MOV DI,OFFSET SECTOR ADD DI,20H ;在扇区偏移20H的地方放密码 MOV CX,COUNT ;设置循环的最大次数 AGAIN: MOV AL,[SI] CMP AL,[DI] JNZ NO_EQUAL INC SI INC DI LOOP AGAIN JMP _EXIT_ NO_EQUAL: MOV FLAGS,1 ;不相等 _EXIT_: MOV AX,FLAGS RET CMPCODE ENDP END
4.4.3 设计软盘扫描程序 • 我们在用Format命令格式化软盘时,如果软盘上有坏扇区,该命令能提示出来。我们设计一个类似的程序,程序在数据段定义2个512字节的缓冲区,循环读磁盘的每个扇区,每个扇区同时读2次。如果第一次读失败,则认为该扇区为坏扇区。如果读两次成功了,比较两次读取的数据不相同也认为该扇区是坏的。
4.4.4 读写大硬盘扇区数据 • 使用中断INT 13H的扩展功能可以读写大硬盘扇区数据。前面介绍的INT 13H中断只能采用10位二进制来表示磁道,所以对于大于8.4G的硬盘它已无能为力了。扩展INT 13H又称为INT 13 EXTENSION APIS,它主要就是用来对超过1024个磁道的硬盘进行读写的。
首先要介绍一个与逻辑扇号相关的结构为扩展INT 13H使用,描述如下: DISK_ADDR_PKT STRUCT ;磁盘地址包 PACKET_SIZE DB 16 ;磁盘参数包的尺寸,必须为10H RESERVED DB 0 ;保留,必须为零 BLOCK_COUNT DW ? ;传输的扇区数 BUFFER_ADDR DD ? ;内存缓冲区地址(段:偏移) BLOCK_NUM DQ ? ;起始绝对扇区号(即起始扇区的LBA号码) DISK_ADDR_PKT ENDS
结构中前两项是固定的,用到的是后三项。起始绝对扇区号即前面讲到的逻辑扇区号,从0开始编号。扩展功能如下:结构中前两项是固定的,用到的是后三项。起始绝对扇区号即前面讲到的逻辑扇区号,从0开始编号。扩展功能如下: (1) 扩展读的功能 入口:AH=42H DL=驱动器号(硬盘是80H) DS:SI=DISK_ADDR_PKT的地址 出口:成功则 CF=0, AH=0; 失败则 CF=1, AH=错误码
(2) 扩展写的功能 入口:AH=43H AL =0 (数据不校验) =1(数据校验) DL=驱动器号 DS:SI=磁盘地址包 出口:成功则 CF=0, AH=0 失败则 CF=1, AH=错误码 注意:一般取AL=0。
(3) 获得驱动器参数 入口:AH=48H DL=驱动器号 DS:SI=返回结果的地址 出口:成功则 CF=0, AH=0 DS:SI指向返回数据结构的地址: 失败则 CF=1, AH=错误码 成功时指向如下的结构地址: DISK_INFO STRUCT INFO_SIZE DW 26 ;缓冲区的大小 FLAGS DW ? ;信息标志 CYLINDERS DD ? ;磁道数目 HEADS DD ? ;磁头数目 SEC_PER_TRACK DD ? ;每磁道扇区数目 SECTORS DQ ? ;磁盘扇区总数 SECTOR_SIZE DW ? ;每扇区字节数 DISK_INFO ENDS
4.5 中断程序设计 • 设计自己使用的中断程序 在程序设计时,有时为了方便,使用某个中断号来设置自己的中断,在程序退出时恢复原来的中断程序。该种中断程序和子程序很相似,只不过使用寄存器传参数,中断程序第一条指令为sti,中断返回时使用指令iret,而不是子程序的ret。在主程序中的调用方式为INT n(n为中断号)。
(1) 定义中断程序,格式为: MYINTERUPT PROC STI …… IRET ;不同于子程序的 RET MYINTERUPT ENDP
(2) 获取要设置的中断号的原来入口地址,使用DOS中断的功能35h: 功能号:AH=35h。 入口参数:AL=要设置的中断号。 出口参数:ES为中断服务程序段地址,BX为中断服务程序的偏移地址。 获取原来的地址是为了在结束使用自己的中断后,恢复原来的入口地址。也可以根据中断号直接从中断向量表中取入口地址。