汇编语言学习笔记(【汇编语言】小甲鱼零基础汇编)

目录

第〇章 课程资料

第一章 基础知识

第二章 寄存器(CPU工作原理)

第三章 寄存器(内存访问)

第四章 第一个程序

第五章 [BX]和loop指令

第六章 包含多个段的程序

第七章 更灵活定位内存地址

第八章 数据处理的两个基本问题

第九章 转移指令的原理

第十章 call和ret指令

第十一章 标志寄存器

第十二章 内中断

第十三章 int指令

第十四章 端口

第十五章 外中断

第十六章 直接定址表

第十七章 使用BIOS进入键盘输入和磁盘读写

综合研究(自己看书)


第〇章 课程资料

1.课件+源代码
2.《汇编语言(第3版) 》王爽著 电子书
3.课后习题答案

百度云:https://pan.baidu.com/s/1TE1Egc0ZmeJfLP5zvamo0Q
提取码:3y72

第一章 基础知识

【学习汇编主要是:学习汇编的编程思想,掌握机器运行的思维】
汇编语言是直接在硬件上工作的编程语言,首先要了解硬件系统的结构,才能有效的应用汇编语言对其编程。
1.汇编课程的研究重点如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作
2.汇编语言的主体是汇编指令
3.汇编指令和机器指令的差别在于指令的表示方法上汇编指令是机器指令便于记忆的书写格式
4.汇编语言时机器指令的助记符
5.汇编语言的组成1.汇编指令(机器码的助记符)2.伪指令(由编译器执行)3.其他符号(由编译器识别,如:+ - * /)汇编语言的核心是汇编指令,他决定了汇编语言的特性
6.CPU对存储器的读写CPU要想进行数据的读写,必须和外部器件(即芯片)进行三类信息的交互1.地址信息:存储单元的地址2.控制信息:芯片的选择,读或写命令3.数据信息:读或写的数据

第二章 寄存器(CPU工作原理)

CPU=运算器+控制器+【寄存器】,器件之间通过总线相连
8086CPU有14个寄存器,名称分别为:AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW
2.1 通用寄存器1.8086CPU所有的寄存器都是16位的,可以存放2个字节2.AX、BX、CX、DX通常用来存放一般性数据被称为通用寄存器3.8086上一代CPU中的寄存器都是8位的,为了保证兼容性这四个寄存器都是可以分为2个独立的8位寄存器使用AX=AH+ALBX=BH+BLCX=CH+CLDX=DH+DL4.AX的低8位(0-7)构成AL寄存器高8位(8-15)构成了AH寄存器AH和AL寄存器是可以独立使用的8位寄存器
2.2 字在寄存器中的存储8086一个字16位

2.3 几条汇编指令1.汇编指令不区分大小写 2.几条汇编指令mov ax,18 ;AX=18mov ah,78    ;AH=78add ax,8 ;AX=AX+8mov ax,bx ;AX=BXadd ax,bx    ;AX+=BX3.用目前学过的汇编指令,最多使用四条指令,编程计算2的4次方mov ax,2  ;ax=2add ax,ax ;ax=4add ax,ax ;ax=8add ax,ax ;ax=16
2.4 物理地址1.CPU访问内存单元时,要给出内存单元的地址。2.所有的内存单元够成的存储空间是一个一维的线性空间3.我们将这个唯一的地址称为物理地址
2.5 16位结构的CPU16位结构描述了一个淳朴具有以下几个方面特征:1.运算器一次最多可以处理16位的数据2.寄存器的最大宽度为16位3.寄存器和运算器之间的通路是16位的
2.6 8086CPU给出物理地址的方法1.8086有20位地址总线,可传送20位地址,实际上的寻址能力为1M2.8086内部为16位结构,它只能传送16位的地址,理论上表现出的寻址能力却只有64K3.问题:8086CPU如何用内部16位的数据转换成20位的地址?1.8086CPU采用一种在内部用两个16位地址合成的方法,来形成20位的物理地址即:段地址+偏移地址=物理地址2.地址加法器合成物理地址的方法:物理地址=段地址×16+偏移地址3.“地址段×16”即是数据左移4位(二进制位的左移4位,十六进制的左移1位)在地址加法器中,如何完成“段地址×16”?二进制形式的段地址左移4位
2.7 “段地址×16+偏移地址=物理地址”的本质含义1.即可以用两个16位的二进制数来表示一个20位的二进制数2.8086CPU中内部为16位结构,但地址线却是20位的,使用地址加法器可以把16位地址变成20位地址具体操作就是:段地址×16+偏移地址
2.8 段的概念1.内存并没有分段,段的划分来自于CPU,由于8086CPU用“段地址×16+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存2.以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,使用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元3.注意1.段地址必然是16的倍数,即一个段的起始地址必然是16的倍数2.偏移地址为16位,16位地址的寻址能力为64K,所以一个段的长度最大为64K3.CPU可以用不同的段地址和偏移地址形成同一个物理地址
2.9 段寄存器1.段寄存器就是提供段地址的8086CPU有4个段寄存器:1.CS(code segment)2.DS(data segment)3.SS(stack segment)4.ES(extra segment)2.当8086CPU要访问内存时,有这4个段寄存器提供内存单元的段地址
2.10 CS和IP1.CS和IP时候8086CPU中最关键的寄存器他们指示了CPU当前读取指令的地址。2.CS和IP的含义CS:代码段寄存器IP:指令指针寄存器【专用寄存器】3.8086CPU工作过程的简要描述1.从CS:IP指向内存单元,读取指令,读取的指令进入指令缓冲器2.IP=IP+所读取指令的长度,从而指向下一条指令3.执行指令,转到步骤1,重复这个过程4.开机时的CS和IP1.在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=0000H2.即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行3.FFFF0H单元中的指令是8086PC机开机后执行的第一条指令5.修改CS、IP的指令1.在CPU中,程序员能够【用指令读写】的部件只有【寄存器】,程序员可以通过改变寄存器中的内容实现对CPU的控制2.CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通过改变CS、IP中的内容控制CPU执行目标指令3.如何修改CS和IP?1.通过mov改变AX等,但是不能通过mov改变CS和IP2.【jmp 段地址:偏移地址】  可以用来同时修改CS和IP指令中的段地址修改CS偏移地址修改IP3.【jmp 某一合法的寄存器】   仅修改IP的内容比如:jmp ax 或者 jmp bx(类似于mov IP ax)4.jmp是只具有一个操作对象的指令
2.11 代码段1.可以将长度为N(N<=64KB)的一组代码,存放在一组地址连续、其实地址为16的倍数的内存单元中这段内存是用来存放代码的,从而定义了一个代码段2.CPU中只认被CS:IP指向的内存单元中的内容为指令
【实验一】查看CPU和内存,用机器指令和汇编指令编程1.R命令:查看、改变CPU寄存器的内容r后面加寄存器的名称可以改变CPU寄存器的内容2.D命令:查看内存中的内容3.E命令:改写内存中的内容4.U命令:将内存汇总的机器指令翻译成汇编指令5.T命令:执行一条机器指令6.A命令:以汇编指令的格式在内存中写入一条机器指令1.debug中输入的默认是16位数2.空格数量任意7.按Q可以退出

第三章 寄存器(内存访问)

3.1 内存中字的存储

    1.任何两个地址连续的内存单元,N号单元和N+1号单元,可以将他们看成两个存储单元也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元2.注意:在内存的表示中,从高到低,是从0号单元开始,然后逐渐变大,即在书写时,低位写在高的地方,高位写在低的地方,如上图所示:4E20H即是0号字节存储20,1号字节存储4E
3.2 DS和[address]1.8086中有一个DS寄存器,通常用来存放要访问的数据的段地址2.例如:我们要读取10000H单元的内容可以用如下程序段进行:mov bx,1000Hmov ds,bxmov al,[0]上面的三条指令将10000H(1000:0)中的数据读到al中1.复习:已知mov指令可以完成的两种传送功能1.将数据直接送入寄存器2.将一个寄存器中的内容送入另一个寄存器中2.除此之外,mov指令还可以将一个内存单元中的内容送入一个寄存器mov指令格式:mov 寄存器名,内存单元地址[...]表示一个内存单元,“[...]”中的...表示内存单元的【偏移地址】执行指令时,8086CPU自动取DS中的数据为内存单元的【段地址】3.如何把1000H放入DS中?要通过通用寄存器把段地址传入到DS中8086CPU不支持将数据直接送入段寄存器的操作,DS是一个段寄存器即:mov ds,1000H  是非法的数据->通用寄存器->段寄存器3.写几条指令,将AL中的数据送入内存单元10000H?mov bx,1000Hmov ds,bxmov [0],al      ;al中的字节型数据送入到1000H:0中
3.3 字的传送1.8086CPU是16位结构,有16根数据线,所以可以一次性传送16位的数据即:一次可以传送一个字2.比如mov bx,1000Hmov ds,bxmov ax,[0]      ;1000H:0处的字型数据送入ax中mov [0],cx      ;cx中的16位数据送入到1000H:0中
3.4 mov、add、sub指令1.复习:已学mov指令的几个形式1.mov 寄存器,数据         ;立即寻址2.mov 寄存器,寄存器        ;寄存器寻址3.mov 寄存器,内存单元      ;直接寻址4.mov 内存单元,寄存器      ;寄存器寻址?5.mov 段寄存器,寄存器      ;寄存器寻址6.mov 寄存器,段寄存器      ;寄存器寻址2.add、sub同mov一样,都有两个操作对象1.add的用法1.add 寄存器,数据      ;立即寻址2.add 寄存器,寄存器    ;寄存器寻址3.add 寄存器,内存单元  ;直接寻址4.add 内存单元,寄存器  ;2.sub的用法【不带借位的减法】指令格式 sub op1,op2    ;意为:op1=op1-op21.sub 寄存器,数据      ;立即寻址2.sub 寄存器,寄存器    ;寄存器寻址3.sub 寄存器,内存单元  ;直接寻址4.sub 内存单元,寄存器  ;
3.5 数据段如何访问数据段中的数据?将一段内存当作数据段,是我们在编程时的一种安排具体操作:用DS存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元
3.6 栈1.8086CPU提供相关的指令来以栈的方式访问内存空间这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用2.8086CPU提供入栈和出栈指令:(最基本的)push(入栈)pop(出栈)1.push ax:将寄存器ax中的数据送入栈中2.pop ax:从栈顶取出数据送入ax3.8086CPU的入栈和出栈操作都是以【字(16位)】为单位进行的4.pop和push可以在寄存器和内存之间传送数据3.CPU如何知道一段内存空间被当做栈使用?1.8086CPU中,有两个寄存器1.段寄存器SS:存放栈顶的段地址2.寄存器SP:存放栈顶的偏移地址【专用寄存器】2.任意时刻SS:SP指向栈顶元素,当栈为空的时候,也就不存在栈顶元素ss:sp也就指向栈最高地址单元的下一个单元4.执行push和pop的时候,如何知道哪个单元是栈顶单元?1.执行push ax时1.sp=sp-22.将ax中的内容送入到ss:sp指向的内存单元ss:sp此时指向新栈顶2.执行pop ax时1.将ss:sp指向的内存单元的内容送入到ax中注意:这里取出的内容在内存中还是存在的,并没有被重置下一轮push会覆盖2.sp=sp+25.如果栈是空的,sp指向哪里?sp指向最高地址单元的下一个单元
3.7 栈顶超界的问题ss、sp只记录了栈顶的地址,依靠ss、sp可以保证在入栈和出栈时找到栈顶可以,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?1.8086CPU不保证栈的操作不会越界2.当栈空的时候,再执行pop出栈 或者 当栈满的时候再使用push入栈都会发生栈顶超界问题,会操作到栈以外的数据,这些数据可能是其他用途的数据或者代码栈顶超界是危险的!!!3.8086CPU没有记录栈顶上下限的寄存器
3.8 栈段1.将一段内存当做栈段,仅仅是我们在编程时的一种安排,2.ss:sp指向我们定义的栈段的栈顶;3.当栈空时,sp指向最高地址的下一个单元4.思考:一个栈段最大可以设为多少?64KB5.设栈顶的变化范围是0-FFFFH,从栈空时sp=0(最高地址单元FFFFH的下一个单元0000H)一直压栈,直到栈满,sp=0;如果再次压栈,栈顶将环绕,覆盖原来栈中的内容6.一段内存,既可以是代码的存储空间,又可以是数据的存储空间,还可以是栈空间也可以是什么都属实。关键在于CPU中寄存器的设置,即:cs、ip、ss、sp、ds的设置**可以通过mov直接给sp赋值【立即数寻址】,但是不能通过mov给cs、ip、ss、ds赋值给cs和ip赋值需要使用jum指令给ss和ds赋值需要使用mov ss或ds,寄存器   ;【寄存器寻址】
【实验二】

第四章 第一个汇编程序

4.1 一个源程序从写出到执行的过程1.一个汇编语言程序从写出到最终执行的简要过程编写->编译连接->执行2.对源程序进行编译连接1.使用汇编语言编译程序(MASM.EXE)对源程序文件中的源程序进行编译,产生目标文件【.obj文件】2.再用连接程序(LINK.EXE)对目标文件进行连接,生成可在操作系统中直接运行的可执行文件【.EXE文件】。3.可执行文件包含两部分内容1.程序(从源程序的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)2.相关的描述信息(比如:程序有多大、要占多少内存空间等)4.执行可执行文件中的程序1.在操作系统(如:MSDOS)中,执行可执行文件中的程序2.操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序
4.2 源程序的主要结构源程序由 汇编指令+伪指令+宏指令 组成伪指令:编译器处理汇编指令:编译为机器码    1.伪指令1.没有对应的机器码的指令,不能由CPU直接执行2.伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作2.segment和ends【定义一个段】1.segment和ends是一对成对使用的伪指令2.编写汇编程序【必须】使用到的指令3.segment和ends的功能是定义一个段segment:说明一个段开始ends:说明一个段结束4.一个段必须有一个名称来标识,使用格式为段名 segment段名 ends5.一个汇编程序由多个段组成这些段用来存放【代码、数据、或当作栈空间】来使用一个有意义的汇编程序至少要有一个段,这个段用来存放代码。3.end【真正的没了】1.end是一个汇编程序的结束标记2.编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译3.如果程序写完了,要在结尾处加上伪指令end否则,编译器无法知道程序在何处结束4.【切记】不要把end和ends搞混了end:汇编程序的结束标记ends:与segment成对出现4.assume【寄存器和段的关联假设】1.它假设某一段寄存器和程序中的某一个用segment...ends定义的段相关联2.通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一具体的段相联系5.程序和源程序1.我们将源程序文件中的所有内容称为【源程序】2.将源程序中最终由计算机执行处理的指令或数据称为【程序】3.程序最先以汇编指令的形式,存储在源程序中然后经过编译、连接后转变为机器码,存储在可执行文件中6.标号,标号与段名称有所区别1.一个标号指代了一个地址,即是段名称。2.段名称 放在segment的前面,作为一个段的名称这个段的名称最终将被汇编、连接程序处理为一个段的段地址7.DOS中的程序运行1.DOS是一个单任务操作系统2.一个程序结束后,将CPU的控制权交还给是他得以运行的程序我们称这个过程为:程序返回8.程序返回mov ax,4c00Hint 21H      ;【中断机制】是DOS最伟大的机制,Windows系统上是【消息机制】这两条指令所实现的功能就是程序返回9.几个和结束相关的内容1.段结束:伪指令通知编译器一个段的结束【ends】2.程序结束:伪指令通知编译器程序的结束【end】3.程序返回:汇编指令mov ax,4c00Hint 21H10.语法错误和逻辑错误1.语法错误1.程序在编译时被编译器发现的错误2.容易发现2.逻辑错误1.在编写时不会表现出来的错误、在运行时会发生的错误2.不容易发现
4.3 以简化的方式进行汇编和连接汇编使用的程序:masm.exe连接使用的程序:link.exe简化方式进行汇编和连接的程序:ml.exe
4.4 汇编和连接的作用连接的作用1.当源程序很大时,可以将他们分成多个源程序文件夹编译每个源程序编译成为目标文件后,再用连接程序将它们连接在一起,生成一个可执行文件2.程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起生成一个可执行文件3.一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件
4.5 可执行文件中的程序装入内存并运行的原理1.在DOS中,可执行文件中的程序P1若要运行,必须有一个正在运行的程序P2将P1从可执行文件中加载入内存,将CPU的控制权交给P1,P1才能得以运行2.当P1运行完毕后,应该将CPU的控制权交还给使他得以运行的程序3.操作系统的外壳1.操作系统是由多个功能模块组成的庞大、复杂的软件系统任何通用的操作系统,都需要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统工作2.DOS中有一个程序command.com,这个程序在DOS中称为命令解释器也就是DOS系统的shell4.执行可执行文件1.exe时,(1)什么程序将CPU的控制权交给了1.exe?(2)将程序1.exe加载入内存后,如何使程序得以运行?(3)1.exe程序运行结束后,返回到了哪里?1.在DOS中直接执行1.exe时,是正在运行的cmd.exe将1.exe中的程序加载入内存2.cmd.exe设置CPU的CS:IP指向程序的第一条指令(即,程序的入口)从而使程序得以运行3.程序运行结束后,返回cmd.exe中,CPU继续运行cmd.exe
【实验三】

第五章 【bx】和loop指令

5.1 [bx]1.和[0]类似,[0]表示内存单元,它的偏移地址是0;2.[bx]同样也表示一个内存单元,它的段地址在DS中它的偏移地址在bx中,至于是取字还是取字节,要看他放入的寄存器是8位还是16位3.补充:inc指令:相当于C语言中的++运算符
5.2 Loop指令这个指令和循环有关1.指令格式:loop 标号CPU执行loop指令的时候,要进行两步操作1.(cx)=(cx)-1;2.判断cx中的值,若不为零,则转至标号处执行程序若为零,则向下执行。2.通常,loop指令实现循环,cx中存放循环的次数3.标号在汇编语言中,标号代表了一个地址,标号标识了一个地址4.使用cx和loop指令相配合实现循环功能的三个要点1.在cx中存放循环次数2.loop指令中的标号所标识地址要在前面3.要循环执行的程序段,要写在标号和loop指令的中间5.用cx和loop指令相配合实现循环功能的程序框架mov cx,循环次数S:循环执行的程序段loop s
5.3 在Debug中跟踪供loop指令实现的循环程序**注意:在汇编程序中,数据不能以字母开头,如果要输入像FFFFH这样的数则要在前面添加一个0在debug程序中引入G命令和P命令1.G命令G命令如果后面不带参数,则一直执行程序,直到程序结束G命令后面如果带参数,则执行到ip为那个参数地址停止2.P命令T命令相当于单步进入(step into)P命令相当于单步通过(step over)
5.4 Debug和汇编编译器Masm对指令的不同处理1.在debug中,可以直接用指令 mov ax,[0] 将偏移地址为0号单元的内容赋值给ax2.但通过masm编译器,mov ax,[0] 会被编译成 mov ax,01.要写成这样才能实现:mov ax,ds:[0]2.也可以写成这样:mov bx,0mov ax,[bx]  ;或者mov ax,ds:[bx]
5.5 loop和[bx]的联合应用1.计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中1.注意两个问题1.12个8位数据加载一起,最后的结果可能会超出8位(越界),故要用16位寄存器存放结果2.将一个8位的数据加入到16位寄存器中,类型不匹配,8位的数据不能与16位相加2.【解决办法】把原来8位的数据,先通过通用寄存器ax,将它们转化成16位的3.代码如下
assume cs:codesgcodesg segment
start:;指定数据段mov ax,0ffffhmov ds,ax;初始化mov ax,0mov dx,0mov bx,0;指定循环次数,12次mov cx,0ch
circ:;把8位数据存入al中,即ax中存放的是[bx]转化之后的16位数据,前8位都是0mov al,[bx];进行累加add dx,ax;bx自增,变化内存的偏移地址inc bxloop circ;程序返回mov ax,4c00hint 21H
codesg endsend start
5.6 段前缀1.指令“mov ax,[bx]”中,内存单元的偏移地址由bx给出,而段地址默认在ds中2.我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器比如  mov ax,ds:[0]mov ax,ds:[bx]这里的ds就叫做【段前缀】
5.7 一段安全的空间1.8086模式中,随意向一段内存空间写入内容是很危险的因为这段空间中可能存放着【重要的系统数据或代码】2.在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用【0:200~0:2FF】的256个字节的空间。所以,我们使用这段空间是安全的

第六章 包含多个段的程序

6.1在代码段中使用数据1.dw的含义【定义字型数据:define word,16字节】在数据段中使用dw定义数据,则数据在数据段中在代码段中使用dw定义数据,则数据在代码段中堆栈段也是一样2.在程序的第一条指令前加一个标号start,并且这个标号在伪指令end后面出现可以通知编译器程序在什么地方结束,并且也可以通知编译器程序的入口在哪里
6.2在代码段中使用栈**补充:如果题目要求【逆序】存放,就要想到栈(FILO)使用dw向系统申请一段空间,然后把这个空间当做栈
6.3将数据、代码、栈放入不同的段1.在前面的6.1和6.2中,我们在程序中用到了数据和栈,我们在编程的时候要注意何处是数据,何处是栈、何处是代码2.这样做显然有两个问题1.把他们放在一个段中是程序显得混乱2.前面程序中处理的数据很少,用到的栈空间也小,放在一个段里面没有问题但数据、栈、代码需要的空间超过64KB,就不能放在一个段中(8086中一个段的容量不能大于64KB)3.我们可以和定义代码段一样的方法来定义多个段然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间4.将数据、代码、栈放入不同的段1.我们可以在源程序中为这三个段起具有含义的名称用来存放数据的段,我们将其命名为“data”用来存放代码的段,我们将其命名为“code”用来作栈空间的段,我们将其命名为“stack”但是CPU看得懂吗?【不能】2.我们在源程序中用伪指令“assume cs:code,ds:data,ss:stack”将cs、ds和ss分别和code、data、stack段相连这样做了之后,CPU是都就会将cs指向code,ds指向data,ss指向stack从而按照我们的意图来处理这些段呢?【不能】伪指令CPU看不懂,伪指令是给编译器看的3.若要CPU按照我们的安排行事,就要用机器指令控制它,源程序中的汇编指令才是CPU要执行的内容需在在code段中给DS,CS、SS设置相应的值才能让CPU识别出数据段、代码段、堆栈段其中汇编程序开始的地方(即代码段开始的地方)由end后面的标号所指向的地方给出5.assume指令不可省略,至于为什么,需要以后多多体会
【实验五】1.如果段中的数据占N个字节,则程序加载后,这段实际占有的空间为:N%16==0?N:16×(N/16+1);因为一个段最小占用16字节,即有16个字节只有这个段可以访问到2.在编辑源程序的时候,如果调换各个段的编写位置,最后CS、DS、SS的值会发生变化3.如果去掉start,编译器会从上到下执行,如果第一个段是代码段,则可以正常运行若第一个段不是代码段,则不会正常运行4.代码示例1
assume cs:code,ds:data,ss:stack;数据段
data segment;8个数据dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends;栈段
stack segment;8个数据dw 0,0,0,0,0,0,0,0
stack ends;代码段
code segment
start:;栈空间初始化mov ax,stackmov ss,axmov sp,16;数据段初始化mov ax,datamov ds,axpush ds:[0];一个栈单元是一个字push ds:[2];存放数据不会改变pop ds:[2]pop ds:[0];程序返回mov ax,4c00hint 21h
code ends
end
    5.将a,b数据段中的内容分别相加,结果放入data数据段中
assume cs:code;数据段
a segmentdb 1,2,3,4,5,6,7,8
a ends;数据段
b segmentdb 1,2,3,4,5,6,7,8
b ends;数据段
data segmentdb 0,0,0,0,0,0,0,0
data ends;代码段
code segment
start:mov bx,0mov ax,0mov dx,amov ss,dxmov dx,bmov es,dxmov dx,datamov ds,dxmov cx,8
circ:add al,ss:[bx]add al,es:[bx]mov [bx],alinc bxmov al,0loop circ;程序返回mov ax,4c00hint 21h
code ends
end start
    6.将a数据段中的前8个字型数据逆序存储到b段中
assume cs:code
a segmentdw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh
a endsb segmentdw 0,0,0,0,0,0,0,0
b endscode segment
start:mov ax,0mov ax,amov ss,axmov sp,0mov ax,0mov ax,bmov ds,axmov bx,0mov cx,8
circ:pop [bx]add bx,2loop circmov ax,4c00hint 21h
code ends
end start

第七章 更灵活地定位内存地址

本章主要讲解一些更灵活的定位内存地址的方法和相关的编程方法
7.1 and和or指令1.and指令:逻辑与指令,按位进行与运算1.如:mov al,01100011Band al,00111011B执行后:al=00100011B2.通过and指令可将操作对象的相应位设为0,其他位保持不变例如al的第6位设为0:and al,10111111B例如al的第7位设为0:and al,01111111B例如al的第0位设为0:and al,11111110B2.or指令,逻辑或运算,按位进行或运算1.如:mov al,01100011Bor  al,00111011B执行后:al=01111011B2.通过该指令可将操作对象的相应位设为1,其他位不变or al,01000000B;将al的第6位设为1or al,10000000B;将al的第7位设为1or al,00000001B;将al的第0位设为1
7.2 关于ASCII码一种编码方案,在计算机系统中通常被采用,8位

7.3 以字符形式给出的数据1.在汇编程序中,可以使用'×××'的方式指明数据是以字符的形式给出的2.编译器会将它们转化为相应的ASCII码3.例如1.db 'unIX'   ;相当于:db 75H,6EH,49H,58H'u'、'n'、'I'、'X'的ASCII码分别为75H,6EH,49H,58H2.mov al,'a'  ;相当于:mov al,61H'a'的ASCII码为61H4.ASCII码中,大写字母和小写字母之间的规律小写字母=大写字母+32小写字母=大写字母+20H大写字母从41H开始排,小写字母从61H开始排
大写 二进制 小写 二进制
A 01000001 a 01100001
B 01000010 b 01100010
C 01000011 c 01100011
D 01000100 d 01100100
7.4 大小写转换的问题1.方案一:1.识别出是该字节是表示一个的大写英文字符,还是小写的用于条件判断的汇编程序,目前还没有学到2.根据+20H 或者 -20H进行大小写转换2.方案二:1.若全部转化为大写,则将第5位置0and al,11011111B2.若全部转化为小写,则将第5位置1or  al,00100000B
7.5 [bx+常数]mov ax,[bx+200]的含义:1.将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个入一个子单元该字单元的偏移地址为bx中的数值加上200,段地址在ds中2.也可以写成1.mov ax,200[bx]2.mov ax,[bx].200
7.6 用[bx+idata]的方式进行数组的处理在codesg中填写代码,将datasg中定义的第一个字符串转化为大写,第二个字符串转化为小写1.我们观察datasg段中的两个字符串,一个的起始地址为0,另一个的起始地址为52.我们可以将这两个字符串看作两个数组,一个从0地址开始存放,另一个从5开始存放3.我们可以用[0+bx]和[5+bx]的方式在同一个循环中定位这两个字符串中的字符4.注意这个数组的定位方式,对比C语言C语言的数组定位方式:a[i],b[i],  a、b是地址常量汇编语言的数组定位方式:0[bx],5[bx]所以:[bx+常数]的方式为高级语言实现数组提供了便利的机制
assume cs:codesg,ds:datasgdatasg segmentdb 'BaSiC'db 'MinIX'
datasg endscodesg segment
start:mov ax,datasgmov ds,axmov bx,0mov cx,5    ;做5次循环
circ:mov al,[bx]and al,11011111bmov [bx],almov al,[bx+5];等价于mov al,5[bx];等价于mov al,[bx].5or al,00100000bmov 5[bx],alinc bxloop circmov ax,4c00hint 21h
codesg ends
end start
7.7 SI和DI已经学过的10个寄存器:AX、BX、CX、DX、DS、CS、SS、ES、IP、SP1.SI和DI是8086CPU中和bx功能相近的寄存器bx不够用,所以引进了SI和DI2.SI和DI(16位)不能够分成两个8位寄存器来使用【和bx的区别】3.下面三组指令实现了相同的功能1.mov bx,0mov ax,[bx]2.mov si,0mov ax,[si]3.mov di,0mov ax,[di]4.下面三组指令也实现了相同的功能1.mov bx,0mov ax,[bx+123]2.mov si,0mov ax,[si+123]3.mov di,0mov ax,[di+123]5.用寄存器SI和DI实现将字符串'welcome to masm!'复制到它后面的数据区中通常用ds:si指向要复制的源始字符串通常用ds:di指向要复制的目的空间**注意si、di是16位寄存器,循环中自增时,应该+2
assume cs:code,ds:data
data segmentdb 'welcome to masm!'db '................'
data endscode segment
start:mov ax,datamov ds,axmov si,0mov di,16mov cx,8
circ:mov ax,0[si]mov [di],axinc diinc diinc siinc siloop circmov ax,4c00hint 21h
code ends
end start
7.8 [bx+si]和[bx+di]1.[bx+si]和[bx+di]的含义类似,我们以[bx+si]为例进行讲解[bx+si]表示一个内存单元,它的偏移地址为bx中的数值加上si中的数值它的偏移地址在ds中2.[bx+si]也可以写成[bx][si]
7.9 [bx+si+常数]和[bx+di+常数]1.以[bx+Si+常数]为例讲解[bx+si+常量]表示一个内存单元,偏移地址为bx的值+si的值+常数2.指令mov ax,[bx+si+常数]也可以写成如下形式1.mov ax,200[bx+si]2.mov ax,200[bx][si]3.mov ax,[bx].200[si]
7.10 不同的寻址方式的灵活应用1.总结几种定位内存的方法1.ds:[常数]   【直接寻址】用一个常量来表示地址,可用于直接定位一个内存单元2.[bx]      【寄存器间接寻址】用一个寄存器的值来表示内存地址,可以间接定位一个内存单元3.[bx+常数]   【??】用一节寄存器的值和常量表示内存地址,可在一个起始地址的基础上用变量间接定位一个内存单元4.[bx+si]5.[bx+si+常数]2.编程,给定数据段data,将data段中每个单词的头一个字母改写成大写字母
assume cs:code,ds:data
data segmentdb '1. file         'db '2. edit         'db '3. search       'db '4. view         'db '5. options      'db '6. help         '
data endscode segment
start:mov ax,datamov ds,axmov bx,0mov cx,6
circ:mov al,[bx+3]and al,11011111bmov [bx+3],aladd bx,16loop circmov ax,4c00hint 21h
code ends
end start
    3.编程,给定数据段data,将data段中的每个单词改为大写字母1.【loop指令cx-1之后,在判断是否为0】2.双重循环用汇编怎么实现?应该在每次开始内循环的时候,将外层循环的cx的值保存起来,在执行外层循环的loop指令前,在恢复外层循环的cx数值。**可以用寄存器来临时保存,也可以用栈空间(内存)保存【没有多余的寄存器】更好的方法是使用:栈1.使用寄存器实现
assume cs:code,ds:data
data segmentdb 4,4,6,4,7,4;单词的字母数db '          ';补齐db '1. file         'db '2. edit         'db '3. search       'db '4. view         'db '5. options      'db '6. help         '
data endscode segment
start:mov ax,datamov ds,axmov bx,16mov si,0mov di,0mov cx,6;外层循环6次
outer:;外层循环mov dx,cx;用寄存器将外层循环的次数保存,C语言中是用栈来保存的mov cx,0mov cl,[di];内循环的次数    inner:;内层循环mov al,[bx][si+3]and al,11011111bmov [bx][si+3],alinc siloop inneradd bx,16mov si,0inc dimov cx,dx;恢复外层循环的次数loop outermov ax,4c00hint 21h
code ends
end start
          2.使用栈实现【更好的方法】
assume cs:code,ds:data,ss:stack
data segmentdb 4,4,6,4,7,4;单词的字母数db '          ';补齐db '1. file         'db '2. edit         'db '3. search       'db '4. view         'db '5. options      'db '6. help         '
data endsstack segmentdw 1,2,3,4,5,6,7,8
stack endscode segment
start:mov ax,datamov ds,axmov ax,stackmov ss,axmov sp,16mov bx,16mov si,0mov cx,6;外层循环6次
outer:;外层循环push cx;将外层循环的次数保存mov cx,0mov cl,[di];内循环的次数 inner:;内层循环mov al,[bx][si+3]and al,11011111bmov [bx][si+3],alinc siloop inneradd bx,16mov si,0inc dipop cx;恢复外层循环的次数loop outermov ax,4c00hint 21h
code ends
end start

第八章 数据处理的两个基本问题

本章对前面的所有内容是具有总结性的
计算机是进行数据处理、运算的机器,那么有两个基本的问题就包含在其中:1.处理的数据在什么地方?2.要处理的数据有多长?这两个问题,在机器指令中必须给以明确或隐含的说明,否则计算机就无法工作
8.1 bx、si、di、bp1.在8086CPU中,只有这4个寄存器(bx、bp、si、di)可以用在“[...]”中,用来进行内存单元的寻址2.在“[...]”中,这四个寄存器(bx、bp、si、di)可以单个出现,或者只能以以下4种组合出现1.bx和si2.bx和di3.bp和si4.bp和di3.错误的用法mov ax,[bx+bp]mov ax,[si+di]4.只要在[...]中使用寄存器bp,则指令中没有显性给出段地址,那么段地址就默认在ss中,比如:mov ax,[bp]       ax的值为栈空间中,偏移地址为bp的内存单元mov ax,[bp+常数]      mov ax,[bp+si]mov ax,[bp+si+常数]
8.2 机器指令处理的数据所在的位置1.绝大部分机器指令进行数据处理的指令大致可分为3大类读取、写入、运算2.在机器指令这一层,并不关心数据的值是多少,而关心指令执行前一刻它将要处理的数据所在的位置3.指令在执行前,所要处理的数据可以在三个地方CPU内部(寄存器)、内存、端口
8.3 汇编语言中数据位置的表达汇编语言中用三个概念来表达数据的位置1.立即数2.寄存器3.段地址(SA)和偏移地址(EA)1.存放段地址的寄存器可以是默认的,既可以是默认在ds中,也可以是在ss中(使用bp寄存器)2.存放段地址的寄存器也可以显性的给出mov ax,ds:[bp]mov ax,es:[bx]mov ax,ss:[bx+si]mov ax,cs:[bx+si+8]
8.4 寻址方式

8.5 指令要处理的数据有多长?1.8086CPU的指令,可以处理两种尺寸的数据,byte和word所以在机器指令中要指明,指令进行的是字操作还是字节操作2.8086CPU确定数据长度的几种方法1.通过寄存器名指明要处理的数据的尺寸mov al,1        ;指明数据是字节型的mov bx,ds:[0]   ;指明数据是字型的2.在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度X在汇编指令中可以为word或byte1.下面的指令中,用byte ptr指明了指令访问的内存单元是字节型单元mov byte ptr ds:[0],1inc byte ptr [bx]inc byte ptr ds:[0]add byte ptr [bx],22.下面的指令中,用word ptr指明了指令访问的内存单元是字型单元mov word ptr ds:[0],1inc word ptr [bx]inc word ptr ds:[0]add word ptr [bx],23.其他方法有些指令默认了访问的内存单元类型pop、push指令,一定是字型数据3.在没有寄存器参与的内存单元访问指令中,用word ptr或者byte ptr显性地指明所要访问的内存单元的长度,是非常有必须要的否则,CPU无法得知所要访问的单元是字单元,还是字节单元
8.6 寻址方式的综合应用
8.7 div指令1.div是除法指令(division),使用div作除法的时候,要求1.除数:8位或16位,在寄存器或内存单元中2.被除数:(默认)放在AX或DX和AX中3.除数与被除数的相互关系除数  被除数8位   16位(AX)16位  32位(DX+AX)4.结果存放的位置运算  8位  16位商    AL   AX余数  AH   DX2.div指令格式1.div 寄存器2.div 内存单元除数是寄存器或内存单元的内容3.div指令示例1.div byte ptr ds:[0]   ;被除数是16位,除数是ds:[0]的内容(8位)含义:(al)=(ax)/((ds)*16+0)的商(ah)=(ax)/((ds)*16+0)的余数2.div word ptr es:[0]    ;被除数是32位,除数是es:[0]的内容(16位)含义:(ax)=[(dx)*10000H+(ax)]/((es)*16+0)的商(dx)=[(dx)*10000H+(ax)]/((es)*16+0)的余数   4.利用除法指令计算100001/1001.被除数100001大于65535,要使用dx和ax两个寄存器联合存放即说要进行的16位的除法2.除数100小于255,可以在一个8位寄存器中存放,但是,因为被除数是32位除数应为16位,所以要用16位寄存器来存放除法1003.现将100001表示成十六进制数:186A1H,即dx中存放1H,ax中存放86A1H
mov dx,1
mov ax,86A1H
mov bx,100
div bx  ;默认除数是16位的
8.8 伪指令dd1.dd是用来定义双字型数据的2.示例data segmentdb 1    ;字节型数据dw 1    ;字型数据dd 1    ;双字型数据data ends3.已知data段数据,用div计算data中第一个数据除以第二个数据后的结果,商存放在第3个数据的内存单元中
assume cs:code,ds:data
data segmentdd 100001dw 100dw 0
data endscode segment
start:mov ax,datamov ds,axmov bx,0mov ax,[bx]     ;低位存放在ax中mov dx,[bx+2]   ;高位存放在dx中div word ptr [bx+4]mov [bx+6],ax   ;商存放在ax中,把ax中的内容放入内存中mov ax,4c00hint 21h
code ends
end start
8.9 dup1.dup是一个操作符,在汇编语言中,同db、dw、dd等一样,也是有编译器识别处理的符号2.dup和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复3.dup示例1.db 3 dup(0)   ;定义了3个字节,他们的值都是02.db 3 dup(0,1,2)   ;定义了9个字节,他们是0、1、2、0、1、2、0、1、23.db 3 dup('abc','ABC') ;定义了18个字节,相当于db'abcABCabcABCabcABC'4.dup的使用格式db 重复的次数 dup(重复的字节型数据)dw 重复的次数 dup(重复的字型数据)dd 重复的次数 dup(重复的双字型数据)
【实验七】
没调试成功
assume cs:code,ds:data,ss:stack,es:tablestack segment;空栈时,sp指向16dw 8 dup(0)
stack endsdata segment;表示21年的21个字符串;起始地址0,终止地址21*4-1:83db '1975','1976','1977','1978','1979','1980','1981','1982','1983'db '1984','1985','1986','1987','1988','1989','1990','1991','1992'db '1993','1994','1995';表示21年公司总收入的21个双字型数据;起始地址21*4:84,终止地址21*4+21*4-1:167dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000;表示21年公司雇员人数的21个字型数据;起止地址21*8:168,终止地址21*8+21*2-1:209dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226dw 11542,14430,15257,17800
data endstable segmentdb 21 dup('year summ ne ?? ')
table endscode segment
start:mov ax,datamov ds,axmov ax,tablemov es,axmov ax,stackmov ss,axmov sp,16mov si,0mov di,0mov bx,0mov bp,0 mov cx,21
outer:push siadd si,simov ax,ds:[bp]mov es:[bx][di],axmov ax,ds:84[bp]mov es:[bx][di+5],axpop simov al,168[si]mov es:[bx][di+10],alinc siadd di,2push siadd si,simov ax,ds:[bp]mov es:[bx][di],axmov ax,ds:84[bp]mov es:[bx][di+5],axpop simov al,168[si]mov es:[bx][di+10],alinc siadd di,2add bx,16loop outermov ax,4c00hint 21h
code ends
end start

第九章 转移指令的原理

8086CPU的转移指令分为以下几类:1.无条件跳转指令(如:jmp)2.条件跳转指令3.循环指令(如:loop)4.过程,就像C语言中的函数5.中断
9.1 操作符offset操作符offset在汇编语言中由编译器处理,它的功能是取标号的偏移地址如:s:mov ax,offset s
9.2 jmp指令1.无条件转移,可以只修改ip,也可以同时修改cs和ip1.【jmp 段地址:偏移地址】  可以用来同时修改CS和IP指令中的段地址修改CS偏移地址修改IP这种用法编译器不认识,只能做在debug中使用2.【jmp 某一合法的寄存器】   仅修改IP的内容比如:jmp ax 或者 jmp bx(类似于mov IP ax)2.jmp指令要给出两种信息:1.转移的目的地址2.转移的距离(段间转移、段内短转移、段内近转移)
9.3 依据位移进行转移的jmp指令1.jmp short 标号【转到标号处执行指令,段内短转移】此格式实现的是:段内短转移,它对ip的修改范围为-128~1272.也就是说,它向前转移时可以最多越过128个字节,负数使用补码表示向后转移可以最多越过127个字节3.CPU不需要目的地址就可以实现对ip的修改jmp指令的机器码中不包含目的地址,但是可以实现跳转实现的方式,是在原地址的基础上进行一个偏移量,即位移4.还有一种和指令“jmp short 标号”功能类似的指令格式:jmp near ptr 标号,它实现的是段内近转移 功能为:(ip)=(ip)+16位位移jmp short 标号是8位的位移,而jmp near ptr 标号是16位位移
9.4 转移的目的地址在指令中的jmp指令前面讲的jmp指令,其对应的机器码中并没有转移的目的地址,而是相对于当前ip的转移位移1.指令“jmp far ptr 标号”实现的是段间转移,又称为远转移,这时机器码中应该明确给出【段地址】2.指令“jmp far ptr 标号”功能如下:(CS)=标号所在段的段地址(IP)=标号所在段中的偏移地址far ptr 指明了指令用标号的段地址和偏移地址修改cs和ip
9.5 转移地址在寄存器中的jmp指令指令格式:jmp 16位寄存器功能:修改ip寄存器中的值,把16位寄存器中的值送入到ip寄存器中
9.6 转移地址在内存中的jmp指令转移地址在内存中的jmp指令有两种格式:1.jmp word ptr 内存单元地址(段内转移)功能:将内存中的那个字视为一个偏移地址,然后跳转到那个偏移地址与【jmp 寄存器】功能相似内存单元地址可用寻址方式的任意格式给出2.jmp dword ptr 内存单元地址(段间转移)(ip)=(内存单元地址)   ;双字中的低位字是给ip的(cs)=(内存单元地址+2) ;双字中的高位字是给cs的跟【jmp 段地址:偏移地址】功能类似内存单元地址可用寻址方式的任意格式给出**补充:不能直接向内存单元中加入立即数要通过寄存器,把立即数加进去
9.7 jcxz指令1.有条件跳转指令,所有的有条件跳转指令都是短转移对应的机器码中包含转移的位移,而不是目的地址。对ip的修改范围都为:-128~127**另一个有条件跳转指令【loop指令】2.指令格式:jcxz 标号如果(cx)=0,则跳转到标号处执行3.jcxz 标号 指令的操作:1.当(cx)=0时,(ip)=(ip)+8位位移2.当(cx)!=0时,什么也不做(程序继续向下执行)
9.8 loop指令1.循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移2.指令格式:loop 标号3.指令的内部操作1.cx=cx-12.如果cx!=0,(ip)=(ip)+8位位移,跳转3.(cx)=0,什么也不做,程序向下执行cx用来控制循环的次数
9.9 根据位移进行转移的意义1.根据位移进行转移,这样设计,方便了程序段在内存中的浮动装配可以实现代码的复用2.如果在机器码中直接给出【段地址:偏移地址】,这段程序在内存中换一个位置,则会运行不正确3.段内近转移、段内短转移都是根据位移进行转移,一共有四种方式1.jmp short ptr 标号2.jmp near ptr 标号3.jcxz 标号4.loop 标号
9.10 编译器对转移位移超界的检测注意,根据位移进行转移的指令,他们的转移范围会受到限制如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错
【实验八、九】【这个实验要重点看】

第十章 call和ret指令

call和ret指令都是转移指令,它们都能修改ip,或同时修改cs和ip
10.1 ret和ref1.ret指令用栈中的数据,修改ip的内容,从而实现【近转移】CPU执行ret指令时,进行下面两步操作:1.(ip)=((ss)*16+(sp))   ;ip的值修改为栈顶的内容2.(sp)=(sp)+2           ;栈顶移动2.retf指令用栈中的数据,修改cs和ip的内容,从而实现【远转移】CPU执行retf指令时,进行下面四步操作1.(ip)=((ss)*16+(sp))   ;ip的内容修改为栈顶的内容2.(sp)=(sp)+2           ;栈顶移动3.(cs)=((ss)*16+(sp))   ;cs的内容修改为栈顶移动之后,栈顶的内容4.(sp)=(sp)+2           ;栈顶移动栈顶的两个字,低位字修改为ip,高位字修改为cs3.可以看出,如果我们用汇编语法来解释ret和retf指令,则1.CPU执行ret指令,相当于pop ip2.执行retf指令时,相当于pop ippop cs
10.2 call指令1.call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作:1.将当前的ip或cs和ip压入栈中2.转移2.call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同【依据位移进行转移的call指令】3.CPU执行“call 标号”这种格式的call指令时,进行如下操作:1.(sp)=(sp)-2         ;栈顶移动2.((ss)*16+(sp))=(ip) ;当前ip内容压栈3.(ip)=(ip)+16位位移   ;跳转到标号处4.call指令格式:call 标号相当于执行:push ipjmp near ptr 标号
10.4 转移的目的地址在指令中的call指令1.指令格式:call far ptr 标号实现的是段间转移2.执行这种格式的call指令时CPU的操作1.(sp)=(sp)-2           ;栈顶移动2.((ss)×16+(sp))=(cs)   ;先把cs压栈3.(sp)=(sp)-2           ;栈顶移动4.((ss)×16+(sp))=(ip)   ;然后把ss压栈3.CPU执行“call far ptr 标号”时,相当于进行push cspush ipjmp far ptr 标号
10.5 转移地址在寄存器中的call指令1.指令格式:call 16位寄存器2.执行这种指令时,在CPU中的操作1.(sp)=(sp)-22.((ss)×16+(sp))=(ip)3.(ip)=(16位寄存器)3.相当于push ipjmp 16位寄存器
10.6 转移地址在内存中的call指令转移地址在内存中的call指令有两种格式:1.call word ptr 内存单元地址汇编语法解释push ipjmp word ptr 内存单元地址2.call dword ptr 内存单元地址汇编语法解释push cs   ;cs存放在高位push ip   ;ip存放在低位jmp dword ptr 内存单元地址
10.7 call和ret的配合使用
10.8 mul指令相乘的两个数;要么都是8位,要么都是16位1.8位:AL中和8位寄存器或内存字节单元中AL中的内容作为被乘数结果放在AX中2.16位:AX中和16位寄存器或内存字单元中AX中的内容作为被乘数结果放在DX(高位)和AX(低位)中。3.格式如下:mul 寄存器mul 内存单元(byte ptr或 word ptr指明是字还是字节)
10.9 模块化程序设计
10.10 参数和结果传递的问题【编程】计算data段中第一组数据的3次方,结果保存在后面一组dword单元中
data sgementdw 1,2,3,4,5,6,7,8dd 0,0,0,0,0,0,0,0
data ends
10.11 批量数据的传递使用寄存器、内存、栈传递数据【编程】将一个全是字母,以0结尾的字符串,转化为大写
【实验十 编写子程序】1.显示字符串2.解决除法溢出问题3.数值显示
【课程设计1】

第十一章 标志寄存器

8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)
本章中的标志寄存器(以下简称为flag)是我们要学习的最有一个寄存器
flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息
8086CPU的flag寄存器的结构:1.flag的1、3、4、12、13、14、15位共7位在8086CPU中没有使用,不具有任何含义而0、2、4、6、7、8、9、10、11位共9位都具有特殊的含义2.示意图

11.1 ZF标志1.flag的第6位是ZF,零标志位。它记录相关指令执行后,1.结果为0,ZF=12.结果不为0,ZF=02.示例:mov ax,1sub ax,1指令执行后,结果为0,则ZF=1mov ax,2sub ax,1指令执行后,结果不为0,则ZF=03.注意,在8086CPU的指令集中,有的指令的执行会影响标志寄存器比如:add、sub、mul、div、inc、or、and等他们大都是运算指令(逻辑运算或者算术运算)有的指令的执行对标志寄存器没有影响,比如:mov、push、pop等,他们大都是传送指令
11.2 PF标志flag的第2位是PF,奇偶标志位它记录指令执行后,结果的所有二进制位中1的个数1.为偶数,PF=12.为奇数,PF=0
11.3 SF标志1.flag的第7位是SF,符号标志位2.它记录指令执行后1.结果为负。sf=12.结果为正,sf=0sf标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负sf标志把所有数当作有符号数如果把数据当作无符号数运算,sf的值则没有意义,虽然相关指令会影响它的值3.也就是说,CPU在执行add等指令时,是必然要影响sf标志位的值至于我们需不需要这种影响,那就看我们如何看待指令所进行的运算
11.4 CF标志   1.flag的第0位是CF,进位标志位一般请况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值代表假想的更高位2.CPU在运算时,不会丢弃进位值,而是记录在一个特殊的寄存器的某一位上8086CPU就用flag的cf为来记录这个进位值,借位也一样3.在debug中的显示

    4.无符号的时候产生的结果
11.5 OF标志flag中的第11位进行有符号数运算的时候,如果结果超过了机器所能表示的范围称为溢出1.这里所讲的溢出,只是对有符号数运算而言就像进位只是相对于无符号数而言!2.一定要注意cf和of的区别当需要把机器码看成有符号数则使用of当需要把机器码看成无符号数则使用cf
11.6 adc标志adc是带进位的加法指令,他利用了cf上记录的进位值1.格式:adc 操作对象1,操作对象22.功能:操作对象1=操作对象1+操作对象2+cf比如:adc ax,bx实现的功能是:(ax)=(ax)+(bx)+cf3.执行adc指令的时候,加上的cf的值的含义,由adc指令前的指令决定也就是说,关键在于所加上的cf值是被什么指令设置的4.如果cf是被sub指令设置的,那么他的含义就是借位值如果是被add指令设置的,那么它的含义就是进位值5.下面的指令和add ax,bx具有相同的结果add al,bladc ah,bhCPU提供adc指令的目的,就是来进行加法的第二步运算的adc指令和add指令相配合就可以对更大的数据进行加法运算【实验:编程计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中】
11.7 sbb标志sbb是带借位减法指令,他利用了cf位上记录的借位值1.格式:sbb 操作对象1,操作对象22.功能:操作对象1=操作对象1-操作对象2-cf3.利用sbb指令,我们可以对任意大的数据进行减法运算4.sbb和adc是基于相同的思想设计的两条指令,在应用思路上和adc类似
11.8 cmp标志1.cmp是比较指令,功能相当于减法指令,只是不保存结果2.cmp指令执行后,将对标志寄存器产生影响3.其他相关指令通过识别这些被影响的标志寄存器,来得知比较结果4.cmp指令格式:cmp 操作对象1,操作对象25.功能:计算操作对象1-操作对象2,但并不保存结果,仅仅根据计算结果对标志寄存器进行设置6.比如:cmp ax,ax做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关位指令执行后zf=1    ;结果为0pf=1    ;结果的1的个数为偶数sf=0    ;结果为正号cf=0    ;结果没有产生进位或借位of=0    ;结果没有溢出7.根据flag,判断cmp指令的结果(无符号数)

    8.cmp既可以对无符号数进行比较,也可以对有符号数进行比较cmp 操作数1,操作数2   ;操作数1、操作数2都是有符号数1.of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负of=0,sf=1   则 操作数1比操作数2小of=0,sf=0   则 操作数1比操作数2大2.of=1,说明有溢出,逻辑上真正结果的正负与实际结果的正负相反of=1,sf=1   则 操作数1比操作数2大of=1,sf=0   则 操作数1比操作数2小
11.9 检测比较结果的条件转移指令1.这些条件转移指令通常和cmp相配合使用2.因为cmp指令可以同时进行两种比较,无符号数和有符号数的比较所以,这些转移指令也分为两种,即:1.根据【无符号数】的比较结果进行转移的条件转移指令,他们检测zf、cf的值2.根据【有符号数】的比较结果进行转移的条件转移指令他们检测sf、of和zf的值3.无符号比较,条件转移指令小结【无符号,6个】1.je  等于则转移     zf=12.jne 不等于则转移   zf=03.jb  低于则转移     cf=1      【b表示below】4.jnb 不低于则转移   cf=05.ja  高于则转移     cf=0,zf=0【a表示above】6.jna 不高于则转移   cf=1或zf=1
11.10 DF标志和串传送指令1.flag的第10位DF,方向标志位在串处理指令(movsb,movsw)中,控制每次操作后si、di的增减df=0:每次操作后si,di递增df=1:每次操作后si,di递减2.格式:movsb3.功能:(以字节为单位传送)1.((es)*16+(di))=((ds)*16+(si))2.如果df=0,则:(si)=(si)+1(di)=(di)+1如果df=1,则:(si)=(si)-1(di)=(di)-13.功能文字描述movsb的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减4.movsw 传送一个字5.movsb和movsw都和rep配合使用格式:rep movsbrep的作用根据cx的值,重复执行后面的串传送指令6.cld指令和std指令cld指令:将标志寄存器的df置为0【c:clear】std指令:将标志寄存器的df置为1【s:set】
11.11 pushf和popfpushf:将标志寄存器的值压栈popf:从栈中弹出数据,送入标志寄存器中pushf和popf为直接访问标志寄存器提供了一种方法
11.12 标志寄存器在debug中的表示

第十二章 内中断

**引言和简介1.中断是CPU处理外部突发事件的一个重要技术2.它能使CPU在运行过程中对外部事件发出的中断请求及时地进行处理,处理完成后又立即返回断点,继续进行CPU原来的工作。3.引起中断的原因【即:发出中断请求的来源叫作中断源】4.根据中断源的不同,可以把中断分为:【软件中断】和【硬件中断】两大类而硬件中断又可以分为【外部中断】和【内部中断】两类
12.1 内中断的产生1.外部中断一般是指计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断。外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备的中断请求。2.内部中断是指因硬件出错(如突然掉电、奇偶校验错等)或运算出错(除数为零、运算溢出、单步中断)所引起的中断。内部中断是不可屏蔽的中断3.软件中断其实并不是真正的中断,他们只是可被调用执行的一般程序,DOS的系统功能调用(int 21h)都是软件中断4.CPU为了处理并发的中断请求,规定了中断的优先权,优先权由高到低的顺序是:1.除法错、溢出中断、软件中断2.不可屏蔽中断3.可屏蔽中断4.单步中断
12.2 中断处理程序简介1.CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系使得CPU根据中断信息可以找到要执行的处理程序。2.中断信息中包含有表示中断的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序的。3.CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址即中断类型码是中断向量在中断向量表中的索引
12.3 中断向量表【中断向量表就是中断向量的列表】1.中断向量表在内存中保存,其中存放着256个【2^8,8位中断类型码】中断源所对应的中断处理程序的入口对于8086PC机,中断向量表指定放在内存地址0处2.从0:0-0:03ffh的1024个字节【256*4,物理地址使用段地址和偏移地址存放,需要4个字节】中存放着中断向量表
12.4 中断过程1.可以用中断类型码,在中断向量表中找到中断处理程序的入口找到这个入口地址的最终目的是用它设置cs和ip,使CPU执行中断处理程序2.用中断类型码找到中断向量,并用它设置cs和ip,这个工作时由CPU的硬件自动完成的CPU硬件完成这个工作的过程被称为【中断过程】3.中断过程8086CPU的中断过程1.(从中断信息中)取得中断类型码2.标志寄存器的值入栈(保护标志位)3.设置标志寄存器的第8位TF和第9位IF设置为0(后面讲解本步的目的)4.cs内容入栈5.ip内容入栈6.从内存地址为中断类型码*4和中断类型码*4+2的两个子单元中读取中断处理程序的入口地址设置cs和ip4.使用汇编语言描述中断过程,如下1.取得中断类型码N2.pushf3.TF=0,IF=04.push cs5.push ip6.(ip)=(N*4),(cs)=(N*4+2)
12.5 中断处理程序1.由于CPU随时都可能检测到中断信息,也就是说,CPU随时都可能执行中断处理程序,所以,中断处理程序必须一致存储在内存某段空间中2.而中断处理程序的入口地址,即【中断向量】,必须存储在对应的中断向量表表项中3.中断处理程序的编写方法和子程序的比较类似,下面是常规的步骤1.保存用到的寄存器2.处理中断3.恢复用到的寄存器4.用iret指令返回**iret指令的功能用汇编语法描述为pop ippop cspopfiret通常和硬件自动完成的中断过程配合使用iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序
12.6 除法错误中断的处理当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的终端信息CPU将检测到这个信息,然后引发中断程序,转去执行0号中断对应的中断处理程序例如:mov ax 1000hmov bh,1div bh此程序会产生溢出运行之后,会显示

12.7 编程处理0号中断现在重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“Welcome to here!”的广告词,然后返回到操作系统把中断处理程序放到安全空间中中断程序的框架

12.8 安装计算中断程序的长度:offset 标号1-offset 标号2在代码段中存放数据
12.9 do0
12.10 设置中断向量
12.11 单步中断如果检测到标志寄存器的tf位为1,则产生单步中断,引发中断过程
12.12 响应中断的特殊情况


第十三章 int指令

13.1 int指令1.int格式:int n  ;n为中断类型码它的功能是引发中断过程2.CPU执行int n指令,相当于引发一个n号中断的中断过程,执行过程如下1.取中断类型码2.标志寄存器入栈,if=0,tf=03.cs,ip入栈4.从此处转去执行n号中断的中断处理过程3.可以在程序中使用int指令调用任何一个中断的中断处理程序可以用int指令调用这些子程序,也可以自己编写一些中断处理程序供别人使用
13.2 编写供应用程序调用的中断例程【实例1】编写、安装中断7ch的中断例程,实现求一个word型数据的平方1.功能:求一word型数据的平方2.参数:(ax)=要计算的数据3.返回值:dx、ax中存放结果的高16位和低16位4.应用举例:求2*3456^2
;程序1:调用中断程序计算平方
code segmentassume cs: code
start:mov ax,3456; (ax)=3456int 7ch;调用中断7ch的中断例程,计算ax中的数据的平方add ax,axadc dx,dx ;存放结果,讲结果乘以2mov ax,4c00hint 21h
code ends
end start;程序2:编写中断程序
;程序2中要做三部分工作
;   1.编程实现求平方功能的程序
;   2.安装程序,我们将其安装在0:200处
;   3.设置中断向量表,将程序的入口地址保存在7ch表项中,使其成为中断7ch的中断例程。
code segmentassume cs:code
start:mov ax,csmov ds,axmov si,offset sqr                   ;设置ds:si指向源地址mov ax,0mov es,axmov di,200h                           ;设置es:di指向目的地址mov cx,offset sqrend - offset sqr ;设置cx为传输长度cld                                   ;设置传输方向为正rep movsbmov ax,0mov es,axmov word ptr es:[7ch*4],200h        ;设置中断向量地址,偏移地址mov word ptr es:[7ch*4+2],0         ;设置中断向量地址,段地址mov ax,4c00hint 21hsqr:  mul axiret
sqrend: nopcode ends
end start
    【实例2】编写、安装中断7ch的中断例程,实现将一个全是字母,以0结尾的字符串,转化为大写。
code segmentassume cs:code
start:mov ax,csmov ds,axmov si,offset capitalmov ax,0mov es,axmov di,200hmov cx,offset capitalend - offset capitalcldrep movsbmov ax,0mov es,axmov word ptr es:[7ch*4],200hmov word ptr es:[7ch*4+2],0mov ax,4c00hint 21hcapital:push cxpush sichange: mov cl,[si]mov ch,0jcxz okand byte ptr [si],11011111binc sijmp short change
ok: pop sipop cxiretcapitalend:nopcode ends
end start
13.3 对int、iret和栈的深入理解【问题】用7ch中断例程完成loop指令的功能不要随便修改sp,可以使用bp进行间接访问
13.4 BIOS和DOS所提供的中断例程
13.5 BIOS和DOS中断例程的安装过程1.开机后,CPU一加电,初始化(cs)=0ffffh,ip=0,自动从ffff:0单元开始执行程序ffff:0处有一条跳转指令,CPU执行该指令后,转去执行bios中的硬件系统的检测和初始化程序。2.初始化程序将建立bios所支持的中断向量,即将bios提供的中断例程的入口地址登记在中断向量表中。3.硬件系统检测和初始化完成后,调用19h进行操作系统的引导。从此将计算机交由操作系统控制。4.DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量
13.6 BIOS中断例程的应用1.int 10h中断例程是bios提供的中断例程,其中包含了多个和屏幕输出相关的子程序一般来说,一个供程序员调用的中断例程中,往往包括多个子程序,中断例程内部用传递进来的参数来决定执行哪个子程序2.bios和dos提供的中断例程,都用ah来传递内部子程序的编号
13.7 DOS中断例程应用int 21h中断例程是dos提供的中断例程,其中包含了dos提供给程序员造编程时调用的子程序【实验13】
**介绍一本汇编语言的书《The Art of Assembly Language》

第十四章 端口

CPU可以直接读写3个地方的数据1.CPU内部的寄存器2.内存单元3.端口
14.1 端口的读写1.对端口的读写不能用mov、push、pop等内存读写指令端口的读写指令只有两条:【in】和【out】分别用于从端口读取数据和往端口写入数据2.CPU执行内存访问指令和端口访问指令时,总线上的信息:1.访问内存mov ax,ds:[8];假设执行前(ds)=0执行时,与总线相关的操作:1.CPU通过地址线将地址信息8发出2.CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据3.存储器将8号单元中的数据通过数据线送入CPU2.访问端口这里的【端口】是对硬件开放的端口in al,60h; 从60h号端口读入一个字节执行时与总线相关的操作1.CPU通过地址线将地址信息60h发出2.CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据3.端口所在的芯片将60h端口中的数据通过数据线送入CPU**注意:在in和out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据访问8位端口时用al,访问16位端口时用ax3.对0-255以内的端口进行读写in al,20h       ;从20h端口读一个字节out 20h,al      ;往20h端口写一个字节4.对256-65535的端口进行读写时,端口号放在【dx】中mov dx,3f8h     ;将端口号3f8送入dxin al,dx        ;从3f8h端口读一个字节out dx,al        ;从3f8h端口写一个字节
14.2 CMOS RAM芯片1.PC机中有一个CMOS RAM芯片,其有如下特征1.包含一个实时钟和一个有128个存储单元的RAM存储器。(早期的计算机为64字节)2.该芯片靠电池供电。因此,关机后其内部的实时钟仍可以正常工作,RAM中的信息不丢失3.128字节的RAM中,内部实时钟占用0-0dh单元来保存时间信息,其余大部分分单元用于保存系统配置信息,供系统启动时bios程序读取bios也提供了相关的程序,使我们可以在开机的时候配置CMOS RAM中的系统信息**补充:BIOSBIOS是英文"Basic Input Output System"的缩略词,直译过来后中文名称就是"基本输入输出系统"。在IBM PC兼容系统上,是一种业界标准的固件接口。BIOS这个字眼是在1975年第一次由CP/M操作系统中出现。BIOS是个人电脑启动时加载的第一个软件4.该芯片内部有两个端口,端口地址为70h和71h。CPU通过这两个端口读写CMOS RAM。5.70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM单元中读取的数据或要写入到其中的数据2.比如:读CMOS RAM的2号单元:1.将2送入端口70h2.从71h读取2号单元的内容
14.3 shl和shr指令shl和shr是逻辑移位指令,后面的课程中我们要用到移位指令1.shl逻辑左移指令,功能为:1.将一个寄存器或内存单元中的数据向左移位2.将最后移出的移位写入cf中3.最低位用0补充例如有如下指令:mov al,01001000bshl al,1        ;将al中的数据左移一位执行后(al)=100100000b,cf=0.如果移动位数大于1时,必须将移动位数放在cl中2.shr逻辑右移指令,与shl刚好相反
14.4 CMOS RAM中存储的时间信息在CMOS RAM中存放着当前时间秒:00h分:02h时:04h日:07h月:08h年:09h这6个信息的长度都为1个字节这些数据以BCD码的方式存放,一个字节可以表示两个BCD码CMOS RAM存储时间信息的单元中存储了用两个BCD码表示的两个十进制数高4位的BCD码表示十位,低四位的BCD码表示个位【编程】:在屏幕中间显示当前的月份1.CMOS RAM芯片回顾:1.70h为地址端口,存放要访问的CMOS RAM单元的地址2.71h为数据端口,存放从选定的CMOS RAM单元中【读取】的数据,或【写入】其中的数据2.分析这个程序主要做两部分工作1.从CMOS RAM的8号单元读取当前月份的BCD码要读取CMOS RAM的信息,我们首先要向地址端口70h写入要访问的单元的地址mov al,8out 70h,al然后从数据端口71h中取得指定单元中的数据in al,71h2.将用BCD码表示的月份以十进制的形式显示到屏幕上
;编程:在屏幕中间显示当前的月份
code segmentassume cs:code
start:mov   al,8out 70h,alin    al,71hmov ah,almov cl,4shr ah,cland al,00001111badd ah,30hadd al,30hmov bx,0b800h   ;显存mov es,bxmov byte ptr es:[160*12+40*2],ah     ;显示月份的十位数码mov byte ptr es:[160*12+40*2+2],al   ;显示月份的个位数码mov ax,4c00hint 21h
code ends
end start
    【实验十四】编程:以“年/月/日 时:分:秒”的格式,显示当前日期和时间

第十五章 外中断

**CPU除了有运算能力,还有I/O能力
15.1 接口芯片和端口1.在PC系统的接口卡和主板上,装有各种接口芯片,这些外设接口芯片的内部装有若干寄存器CPU将这些寄存器当做【端口】访问2.外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的【端口】中3.CPU向外设的输出也是要先送入【端口】中,再由相关芯片送入到外设4.CPU可以向外设输出控制命令,这些控制命令也是先送到【端口】中,然后相关芯片根据命令进行相关工作5.可见:CPU与外部设备的交流是通过【端口】进行的CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入
15.2 外中断信息1.在PC系统中,外中断源一共有两类1.可屏蔽中断2.不可屏蔽中断2.可屏蔽中断是CPU可以不响应的外中断。CPU是否响应可屏蔽中断要看标志寄存器的IF位的设置当CPU检测到可屏蔽中断信息时:1.若IF=1,则CPU在执行完当前指令后相应中断,引发中断过程2.若IF=0,则不响应可屏蔽中断3.可屏蔽中断所引发的中断过程,除在第一步的实现上与内中断有所不同外,基本上和内中断的中断过程相同4.因为可屏蔽中断信息来自于CPU外部,中断类型码是通过数据总线送入CPU的而内中断的中断码是在CPU内部产生的5.IF设置为0的原因:在进入中断处理程序后,禁止其他的可屏蔽中断当然,如果中断处理程序中需要处理可屏蔽中断,可以用指令将IF设置为16.8086CPU提供的设置IF的指令如下:sti         ;用于设置IF=1cli         ;用于设置IF=07.不可屏蔽中断是CPU必须相应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后立即响应,应发中断过程8.8086CPU不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码9.不可屏蔽中断的中断过程1.标志寄存器入栈,IF=0,TF=02.CS,IP入栈3.(IP)=(8),(CS)=(0AH)   ;固定地址10.几乎所有外中断,都是可屏蔽中断。当外设有需要处理的事件发生时相关芯片向CPU发出可屏蔽中断信息。不可屏蔽中断是系统中有必须处理的紧急情况发生时用来通知CPU的中断信息,本门课程中,主要讨论可屏蔽中断
15.3 PC机键盘的处理过程1.下面看一个键盘输入的处理过程,并以此来体会PC机处理外设输入的基本方法1.键盘输入2.引发9号中断3.执行int 9中断例程2.PC机键盘的处理过程1.键盘上每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一触键的开关状态进行扫描。2.按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明按下的键在键盘上的位置扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60H3.松开控下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置,松开按键时产生的扫描码也被送入60H端口中。一般按下一个键时,产生的扫描码称为通码,松开一个键产生的扫描码称为断码扫描码长度为一个字节,通码的第七位为0,断码的第七位为1即:断码=通码+80H**BIOS提供了int9中断例程,用来进行基本的键盘输入处理,主要的工作如下:1.读出60H端口中的扫描码2.如果是字符键的扫描码,将该扫描码对应的字符码(即:ASCII码)送入内存中的BIOS键盘缓冲区3,如果是控制键和切换键的扫描码,则将其转变为状态字节,写入内存中存储状态字节的单元4.键盘的输入到达60H端口时,相关的芯片会向CPU发出中断类型码为9的可屏蔽中断信息。5.CPU检测到中断信息后,如果IF=1,则相应中断,同时将IF设置为0(不让其他可屏蔽中断进行干扰),引发中断过程,转去执行int9中断例程3.BIOS键盘缓冲区是系统启动后,BIOS用于存放int9中断例程所接受的键盘输入的内存区4.该内存区可以存储15个键盘输入,int9中断例程除了接收扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高字节存放扫描码,低字节存放字符码5.0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节各位记录的信息如下:

15.4 编写int9中断例程,并安装梳理键盘输入的处理过程1.键盘产生扫描码2.扫描码送入60H端口3.一旦侦测到60H端口有动静,引发9号中断4.CPU执行int9中断例程处理输入以上的过程,前三步都由硬件系统自动完成,能够修改的只有第四步,修改int9中断程序【任务演示】在屏幕中依次显示“a”~“z”并可以让人看清。在显示过程中,按下Esc键后,该表显示的颜色
;程序1:实现连续显示“a”~“z”
;编程:在屏幕中间依次显示“a”~“z”,并可以让人看清。在显示的过程中,按下'Esc'键后,改变显示的颜色。
;部分功能代码:
stack segmentdb 128 dup (0)
stack endscode segmentassume cs:code
start:  mov ax,stackmov ss,axmov sp,128mov ax,0b800hmov es,axmov ah,'a'
s:  mov es:[160*12+40*2],ahcall delayinc ahcmp ah,'z'jna smov ax,4c00hint 21hdelay:  push axpush dxmov dx,0010h  ;循环10000000h次mov ax,0
s1:                    sub ax,1sbb dx,0cmp ax,0jne s1cmp dx,0jne s1pop dxpop axretcode ends
end start ;程序2:实现改变颜色
;编程:在屏幕中间依次显示“a”~“z”,并可以让人看清。在显示的过程中,按下'Esc'键后,改变显示的颜色。
stack segmentdb 128 dup (0)
stack endsdata segmentdw 0,0
data endscode segmentassume cs:code
start:  mov ax,stackmov ss,axmov sp,128mov ax,datamov ds,axmov ax,0mov es,axpush es:[9*4]pop ds:[0]push es:[9*4+2]pop ds:[2]           ;将原来的int 9中断例程的入口地址保存在ds:0、ds:2单元中mov word ptr es:[9*4],offset int9mov es:[9*4+2],cs   ;在中断向量表中设置新的int 9中断例程的入口地址mov ax,0b800hmov es,axmov ah,'a'
s:  mov  es:[160*12+40*2],ahcall delayinc ahcmp ah,'z'jna smov ax,0mov es,axpush ds:[0]pop es:[9*4]push ds;[2]pop es;[9*4+2]    ;将中断向量表中int 9中断例程的入口恢复为原来的地址mov ax,4c00hint 21hdelay:   push axpush dxmov dx,0010hmov ax,0
s1:     sub ax,1sbb dx,0cmp ax,0jne s1cmp dx,0jne s1pop dxpop axret;------以下为新的int 9中断例程--------------------
;int9中断例程是在进行键盘输入之后,由系统自动调用
int9:   push axpush bxpush esin al,60hpushfpushfpop bxand bh,11111100bpush bxpopfcall dword ptr ds:[0]              ;对int指令进行模拟,调用原来的int 9中断例程cmp al,1jne int9retmov ax,0b800hmov es,axinc byte ptr es:[160*12+40*2+1]     ;属性增加1,改变颜色int9ret:pop espop bxpop axiretcode ends
end start

第十六章 直接定址表

16.1 描述了单元长度的标号1.本章讨论如何有效合理地组织数据,以及相关的编程技术1.前面的课程中,我们一直在代码段中使用标号来标记指令、数据、段的起始地址2.还可以使用一种标号,这种标号不但可以表示内存单元的地址,还表示了内存单元的长度即:表示在此标号处的单元,是一个字节单元,还是字单元还是双字单元2.例如1.标号1a : db 1,2,3,4,5,6,7,8b : dw 0此种标号只能标记地址此种加有“:”的地址标号,只能在代码段中使用,不能在其他段中使用2.标号2a db 1,2,3,4,5,6,7,8    ;标号a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元b dw 0                  ;标号b描述了地址code:8,和从这个地址开始,以后的内存单元都是字单元此种标号既可以标记地址,也可以表示此标号处的单元3.使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据这种标号此后称为数据标号,它标记了存储数据的单元的地址和长度4.数据标号的用法指令:mov ax,b             ;相当于:mov ax,cs:[8]指令:mov b,2              ;相当于:mov word ptr cs:[8],2指令:inc b                ;相当于:inc word ptr cs:[8]指令:mov al,a [si]        ;相当于:mov al,cs:0[si]指令:mov al,a[3]          ;相当于:mov al,cs:0[3]指令:mov al,a[bx+si+3]    ;相当于:mov al,cs:0[bx+si+3]
16.2 在其他段中使用数据标号1.注意,如果想在代码段中,直接用数据标号访问数据,则需要用伪指令assume 将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。2. 当然,这种联系是编译器需要的,但绝对不是说,我们因为编译器的工作需要,用assume指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。3.我们可以将数据标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。 1.把数据标号当做数据来定义时,使用【dw】定义数据比如:     data segmenta db 1,2,3,4,5,6,7,8b dw 0c dw a,b        ;数据标号c处存储的两个字型数据为标号a、b 的偏移地址。data ends数据标号c处存储的两个字型数据为标号a、b 的偏移地址。相当于:data segmenta db 1,2,3,4,5,6,7,8b dw 0c dw offset a, offset bdata ends2.把数据标号当做数据来定义时,使用【dd】定义数据再比如:data segmenta db 1,2,3,4,5,6,7,8b dw 0c dd a,b    ;数据标号c处存储的两个双字型数据为标号a的偏移地址和段地址、标号b 的偏移地址和段地址。data ends数据标号c处存储的两个双字型数据为标号a的偏移地址和段地址、标号b 的偏移地址和段地址。相当于:data segmenta db 1,2,3,4,5,6,7,8b dw 0c dw offset a, seg a, offset b, seg b   ;seg操作符,功能为取得某一标号的段地址。data endsseg操作符,功能为取得某一标号的段地址。
16.3 直接定址表本节课,我们将使用“查表”的方法,编写相关程序的技巧【任务】编写子程序,以十六进制的形式在屏幕中间显示给定的byte型数据
code segmentassume cs:code
start:  mov al,0eh          ;al中存放了byte型数据call showbytemov ax,4c00hint 21h;子程序:
;用al传送要显示的数据showbyte:jmp short showtable db '0123456789ABCDEF'    ;字符表show:   push bx                 ;保护现场push esmov ah,alshr ah,1           shr ah,1shr ah,1shr ah,1                ;右移4位,ah中得到高4位的值and al,00001111b     ;al中为低4位的值mov bl,ahmov bh,0mov ah,table[bx]     ;用高4位的值作为相对于table的偏移,取得对应的字符mov bx,0b800hmov es,bxmov es:[160*12+40*2],ahmov bl,almov bh,0mov al,table[bx]      ;用低4位的值作为相对于table的偏移,取得对应的字符mov es:[160*12+40*2+2],alpop espop bxretcode ends
end start
16.4 程序入口地址的直接定址表【编程】实现一个子程序setscreen,为显示输出提供如下功能:1.清屏2.设置前景色3.设置背景色4.向上滚动一行1.入口参数说明:1.用ah寄存器传递功能号0:清屏;1:设置前景色;2:设置背景色;3:向上滚动一行2.对于2、3号功能,用al传递颜色值al∈{0,1,2,3,4,5,6,7}2.各种功能如何实现1.清屏:将显存中当前屏幕中的字符设为空格符;2.设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位;012位存放前景色3.设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位;456位存放背景色4.向上滚动一行:依次将第 n+1行的内容复制到第n行处:最后一行为空。
;功能子程序1:清屏
sub1:      push bx      ;保护现场,调用子程序的时候,注意要保护现场,运行子程序的时候,可能会修改一些寄存器的值push cxpush esmov bx,0b800hmov es,bxmov bx,0mov cx,2000
sub1s:    mov byte ptr es:[bx],' '  ;循坏2000次add bx,2loop sub1spop es          ;恢复现场pop cxpop bxret
;功能子程序2:设置前景
sub2:   push bxpush cxpush esmov bx,0b800hmov es,bxmov bx,1mov cx,2000
sub2s:  and byte ptr es:[bx],11111000b  or es:[bx],al add bx,2loop sub2spop espop cxpop bxret
;功能子程序3:设置背景色
sub3:   push bxpush cxpush esmov cl,4shl al,clmov bx,0b800hmov es,bxmov bx,1mov cx,2000
sub3s:  and byte ptr es:[bx],10001111bor es:[bx],al add bx,2loop sub2spop espop cxpop bxret
;功能子程序4:向上滚动一行
sub4:   push cxpush sipush dipush espush dsmov si,0b800hmov es,simov ds,simov si,160            ;ds:si指向第n+1行,第1行mov di,0           ;es:di指向第n行,第0行cldmov cx,24;共复制24行sub4s: push cxmov cx,160rep movsb          ;复制pop cxloop sub4smov cx,80    mov si,0sub4s1: mov byte ptr es:[160*24+si],' '      ;最后一行清空add si,2loop sub4s1pop dspop espop dipop sipop cxret ;sub4 ends
    3.可以将这些功能子程序的入口地址存储在一个表中,他们在表中的位置和功能号相对应
;编程:实现一个子程序setscreen,为显示输出提供如下功能:
;(1) 清屏。
;(2) 设置前景色。
;(3) 设置背景色。
;(4) 向上滚动一行。
;
;入口参数说明:
;(1) 用 ah 寄存器传递功能号:0 表示清屏,1表示设置前景色,2 表示设置背景色,3 表示向上滚动一行;
;(2) 对于2、3号功能,用 al 传送颜色值,(al) ∈{0,1,2,3,4,5,6,7}setscreen: jmp short settable  dw sub1,sub2,sub3,sub4set: push bx cmp ah,3        ;判断传递的是否大于 3ja sretmov bl,ahmov bh,0add bx,bx       ;根据ah中的功能号计算对应子程序的地址在table表中的偏移call word ptr table[bx]  ;调用对应的功能子程序,学会本句代码,是本章节的【精髓】sret: pop bx  iret;功能子程序1:清屏
sub1:   push bxpush cxpush esmov bx,0b800hmov es,bxmov bx,0mov cx,2000sub1s:  mov byte ptr es:[bx],' 'add bx,2loop sub1spop espop cxpop bxret ;sub1 ends;功能子程序2:设置前景色
sub2:   push bxpush cxpush esmov bx,0b800hmov es,bxmov bx,1mov cx,2000sub2s:    and byte ptr es:[bx],11111000b  or es:[bx],al add bx,2loop sub2spop espop cxpop bxret ;sub2 ends;功能子程序3:设置背景色
sub3:   push bxpush cxpush esmov cl,4shl al,clmov bx,0b800hmov es,bxmov bx,1mov cx,2000sub3s:   and byte ptr es:[bx],10001111bor es:[bx],al add bx,2loop sub2spop espop cxpop bxret ; sub3 ends;功能子程序4:向上滚动一行
sub4:   push cxpush sipush dipush espush dsmov si,0b800hmov es,simov ds,simov si,160            ;ds:si指向第n+1行mov di,0          ;es:di指向第n行cldmov cx,24;共复制24行sub4s:    push cxmov cx,160rep movsb          ;复制pop cxloop sub4smov cx,80    mov si,0sub4s1: mov byte ptr es:[160*24+si],' '      ;最后一行清空add si,2loop sub4s1pop dspop espop dipop sipop cxret ;sub4 ends

第十七章 使用BIOS进入键盘输入和磁盘读写

**引言1.大多数有用的程序都需要处理用户的输入,键盘输入是最基本的输入。2.程序和数据通常需要长期存储,磁盘是最常用的存储设备。3.BIOS 为这两种外设的I/O提供了最基本的中断例程,在本章中,我们对它们的应用和相关的问题进行讨论。
17.1 int9中断例程对键盘输入的处理CPU 在9 号中断发生后,执行int 9中断例程,从60h 端口读出扫描码,并将其转化为相应的ASCII 码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。
17.2 使用int16h中断例程读取键盘缓冲区1.BIOS提供了int 16h 中断例程供程序员调用。2.int 16h 中断例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为0。3.下面的指令从键盘缓冲区(缓冲区的最低位)中读取一个键盘输入,并且将其从缓冲区中删除:mov ah,0int 16h结果:(ah)=扫描码,(al)=ASCII码。4.int 16h 中断例程的 0 号功能,进行如下的工作:(1)检测键盘缓冲区中是否有数据;(2)没有则继续做第1 步;(缓冲区随时有可能输入数据)(3)读取缓冲区第一个字单元中的键盘输入;(4)将读取的扫描码送入ah,ASCII 码送入al;(5)将己读取的键盘输入从缓冲区中删除。5.可见,B1OS 的int 9 中断例程和int 16h 中断例程是一对相互配合的程序,int 9 中断例程向键盘缓冲区中写入,int 16h 中断例程从缓冲区中读出。它们写入和读出的时机不同,int 9 中断例程在有键按下的时候向键盘缓冲区中写入数据;而int 16h 中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。【编程】接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色:输入“g”, 将屏幕上的字符设置为绿色;输入“b ”,将屏幕上的字符设置为蓝色。
;编程:
;接收用户的键盘输入,输入“r”,将屏幕上的字符设置为红色:输入“g”,
;将屏幕上的字符设置为绿色;输入“b ”,将屏幕上的字符设置为蓝色。
;A、B、C处的程序指令比较有技巧,请读者自行分析
code segmentassume cs:code
start:  mov ah,0int 16h             ;int 16h 0号功能实现从键盘缓冲区读取一个键盘输入mov ah,1           ;Acmp al,'r'je redcmp al,'g'je greencmp al,'b'je bluejmp short sretred:   shl ah,1            ;B
green:  shl ah,1            ;Cblue: mov bx,0b800hmov es,bxmov bx,1mov cx,2000
s:  and byte ptr es:[bx],11111000b      ;设置颜色or es:[bx],ah                       ;设置颜色add bx,2loop ssret:   mov ax,4c00hint 21hcode ends
end start
17.3 字符串的输入int 21h的0a号功能可以实现字符串的输入也可以用int 16h,通过显示键盘缓冲区中的内容,实现字符串的显示1.使用int 16h显示字符串程序的处理过程如下① 调用int 16h读取键盘输入;② 如果是字符,进入字符栈,显示字符栈中的所有字符;继续执行① ;③ 如果是退格键,从字符栈中弹出一个字符,显示字符栈中的所有字符;继续执行① ;④ 如果是Enter 键,向字符栈中压入0,返回。2.子程序:字符栈的入栈、出栈和显示参数说明(ah)=功能号,0表示入栈,1表示出栈,2表示显示;ds : si 指向字符栈空间;对于0 号功能:(al)=入栈字符;对于1 号功能:(al)=返回的字符;对于2 号功能:(dh)、(dl) =字符串在屏幕上显示的行、列位置。
;使用int 16h显示字符串的子程序:字符栈
;最基本的字符串输入程序,需要具备下面的功能:
;(1) 在输入的同时需要显示这个字符串;
;(2)一般在输入回车符后,字符串输入结束;
;(3)能够删除已经输入的字符。;编写一个接收字符串的输入子程序,实现上面三个基本功能。
;因为在输入的过程中需要显示,子程序的参数如下:
;   (dh)、(dl)=字符串在屏幕上显示的行、列位置;
;   ds:si 指向字符串的存储空间,字符串以O 为结尾符。;功能子程序实现charstack:jmp short charstarttable dw charpush,charpop,charshowtop dw 0                              ;栈顶charstart:push bxpush dxpush dipush escmp ah,2ja sretmov bl,ahmov bh,0add bx,bxjmp word ptr table[bx]      ;使用直接定址表charpush:mov bx,topmov [si][bx],alinc topjmp sretcharpop:cmp top,0je sretdec topmov bx,topmov al,[si][bx] jmp sretcharshow:mov bx,0b800hmov es,bxmov al,160mov ah,0   mul dhmov di,axadd dl,dlmov dh,0add di,dxmov bx,0charshows:cmp bx,topjne noemptymov byte ptr es:[di],' '  jmp sretnoempty:mov al,[si][bx]mov es:[di],almov byte ptr es:[di+2],' 'inc bxadd di,2jmp charshowssret:  pop espop dipop dxpop bxret
17.4 应用int13h中断例程对键盘进行读写1.磁盘的实际访问由磁盘控制器进行,我们可以通过控制磁盘控制器来访问磁盘。2.注意,我们只能以扇区为单位对磁盘进行读写。在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从0开始,而扇区号从1开始。3.BIOS提供了对扇区进行读写的中断例程,这些中断例程完成了许多复杂的和硬件相关的工作。4.我们可以通过调用BIOS中断例程来访问磁盘。BIOS 提供的访问磁盘的中断例程为int 13h 。如下,读取0面0道1扇区的内容到0:200:

        返回参数:操作成功:(ah)=0,(al)=读入的扇区数操作失败:(ah)=出错代码将0:200中的内容写入0面0道1扇区示例返回参数:

            操作成功: (ah)=0,(al)=写入的扇区数操作失败: (ah)=出错代码5.注意:使用int 13h 中断例程对软盘进行读写。直接向磁盘扇区写入数据是很危险的,很可能覆盖掉重要的数据。【编程】将当前屏幕的内容保存在磁盘上分析:1 屏的内容占4000个字节,需要8 个扇区(一个扇区512B),我们用0面0道的1~8扇区存储显存中的内容。
code segmentassume cs:code
start:  mov ax,0b800hmov es,axmov bx,0  ;es:bx  指向将写入磁盘的数据的内存区mov al,8  ;写入的扇区数mov ch,0     ;磁道号,从0开始mov cl,1    ;扇区号 从1开始mov dl,0   ;驱动器号0:软驱A,  1:软驱B,硬盘从80h开始, 80h:硬盘C,81h:硬盘Dmov dh,0  ;磁头号,(对于软盘即面号,因为一个面用一个磁头来读写)mov ah,3    ;传递 int 13h 写入数据的功能号int 13h;返回参数;操作成功:(ah) = 0,(al) = 写入的扇区数;操作失败:(ah) = 出错代码return: mov ax,4c00hint 21hcode ends
end start
    【实验17和课程设计2】课程设计1在第十章

综合研究

研究试验1 搭建一个精简的C语言开发环境
研究试验2 使用寄存器
研究试验3 使用内存空间
研究试验4 不用main函数编程
研究试验5 函数如何接受不定数量的参数

汇编语言学习笔记(【汇编语言】小甲鱼零基础汇编)相关推荐

  1. 第031讲:永久存储,腌制一缸美味的泡菜 | 学习记录(小甲鱼零基础入门学习Python)

    (标答出处: 鱼C论坛) <零基础入门学习Python> 测试题: 0.pickle的实质是什么? pickle的实质是利用一些算法,将你的数据对象腌制成二进制文件,存储在磁盘上,也可以放 ...

  2. 第019讲:我的地盘听我的 | 学习记录(小甲鱼零基础入门学习Python)

    (标答出处: 鱼C论坛) <零基础入门学习Python> 测试题: 0.下边程序会输入什么? def next():print('我在next()函数里...')pre()def pre( ...

  3. 第023、024讲:这帮小兔崽子汉诺塔 | 学习记录(小甲鱼零基础入门学习Python)

    (标答出处: 鱼C论坛) <零基础入门学习Python> 测试题: 0.使用递归编写一个十进制转换为二进制的函数(要求采用'取2取余'的方式,结果与调用bin()函数一样返回字符串形式) ...

  4. 第063讲: 论一只爬虫的自我修养11:Scrapy框架之初窥门径 | 学习记录(小甲鱼零基础入门学习Python)

    上一节课我们好不容易装好了 Scrapy,今天我们就来学习如何用好它,有些同学可能会有些疑惑,既然我们懂得了Python编写爬虫的技巧,那要这个所谓的爬虫框架又有什么用呢?其实啊,你懂得Python写 ...

  5. 第094讲: Pygame:飞机大战5 | 学习记录(小甲鱼零基础入门学习Python)

    我们接下来在游戏界面的左上角设置一个得分显示区域,实时显示玩家得分,我们的规则如下: 击落小中大敌机分别可以获得1000,6000,10000分. 我们现在main 函数中添加一个 score 变量统 ...

  6. 第088讲: Pygame:摩擦摩擦 | 学习记录(小甲鱼零基础入门学习Python)

    现在 Play The Ball 这个小游戏现在已经有了背景音乐,有了小球,有了碰撞检测,接下来我们要做的就是摩擦摩擦. 我们有一块玻璃面板的图片,如下图所示:这些是我的图片素材. 在这里,因为再看了 ...

  7. 第028讲:文件:因为懂你,所以永恒 | 学习记录(小甲鱼零基础入门学习Python)

    c 0.下边只有一种方式不能打开文件,请问是哪一种,为什么? f = open('E:/test.txt','w') #A f = open('E:\test.txt','w') #B f = ope ...

  8. 第087讲: Pygame:播放声音和音效 | 学习记录(小甲鱼零基础入门学习Python)

    这节课我们来谈谈 Pygame 中的 播放声音和音效,因为几乎没有任何游戏是一声不吭的,多重的感官体验更能刺激玩家的神经,没有声音的游戏就好比 不蘸番茄的薯条,尽管如此,Pygame 对于声音的处理并 ...

  9. 小甲鱼零基础入门学习Python(绝对干货,值得学习)

    小甲鱼零基础入门学习Python(绝对干货,值得学习) 链接: https://pan.baidu.com/s/1jJmIrlk 密码: ktp2

  10. 小甲鱼零基础入门学习python--课后作业(更新至第19讲,持续更新)

    本章内容: 小甲鱼零基础入门学习python--课后作业 1.基础部分的作业 2.函数部分的作业 3.字典.集合.文件部分作业 4.异常 5.EasyGui 6.类.对象.魔法方法 7.模块 8.爬虫 ...

最新文章

  1. github设置添加SSH
  2. Android, BaseAdapter 处理大数据量时的优化
  3. corosync+pacemaker实现高可用(HA)集群(二)
  4. libklel 1.1.0 发布,表达式语言
  5. php 代码线程,php实现多线程代码
  6. Android学习笔记之SoftReference软引用,弱引用WeakReference
  7. Python中实现ASCII码与字符相互转换
  8. [2019.3.4]BZOJ1213 [HNOI2004]高精度开根
  9. 我是如何学习写一个操作系统(九):文件系统
  10. vue --- 从模块从父元素获取数据
  11. java中如何使用反射调用方法以及获得类中的属性
  12. 哇!单细胞测序-配体受体互作分析原来可以这么简单又高大上!
  13. (四)为深度伪造预处理数据集
  14. BZOJ 1801: [Ahoi2009]chess 中国象棋( dp )
  15. LAMP YUM安装配置实战
  16. 实战tkinter图形界面开发_Tkinter python(图形开发界面)
  17. openstack中虚拟机CPU与内存布局设计(三)
  18. 如何评价光伏电站的运维能力
  19. 服务器和网站域名,网站服务器和域名的区别
  20. Pytorch问题及解决:‘lengths‘ argument should be a 1D CPU int64 tensor, but got 1D cuda:0 Long tensor

热门文章

  1. db2 linux 64位下载,Redhat6.2 64位 安装DB2V10.5
  2. win7ie11调用java失败,Win7 更新IE11 一直失败,请求
  3. 黑苹果声卡、显卡、网卡驱动教程
  4. ppt设置外观样式_ppt如何设置幻灯片的样式
  5. python快速查城市的地理坐标
  6. 华为hwics格式产品文档打开方式
  7. 工厂流水线数据采集方案
  8. 开源多语言商城 CMS 企业建站系统,MyCms v3.9 发布
  9. 【MM32F5270开发板试用】基于MindSDK对接雨滴传感器
  10. 通过python获取Arduino雨滴传感器模块的数据(PyMata3+MySQL)