寄存器
通用寄存器有:AX,BX,CX,DX 。通常用来存放普通数据
8086CPU的寄存器为16位寄存器,16位寄存器可分为高位和地位,如AX寄存器可以分为:AH,AL 两个8位寄存器。也完全可以看成两个8位寄存器来使用,但如果看成8位寄存器来使用的话,当进行计算时如果结果超出8位,如在AL中计算时,结果如果为1F8,那么最高位将舍去(准确来说那一位并没有舍去,而是存放在了一个特殊的寄存器中),而不会进位到AH寄存器中,而如果是直接用AX寄存器来计算,结果就会进位。
CPU的工作方式:寄存器CS:IP指向的地址中的数据将当作指令来处理。先通过地址加法器算出CS:IP指向的物理地址,然后通过地址总线找到那个地址,然后通过数据总线将地址中的数据传送到指令缓冲器,这个时候寄存器IP中的数据根据之前的指令增加,使得CS:IP指向下一条指令,然后CPU执行指令。
段的概念:8086CPU中的寻址方式为段地址加上偏移地址的方式。因为偏移地址为16位,所以一个段的最大寻址能力为64KB。
8086CPU不支持直接将一个数据送入段寄存器,可以通过另一个寄存器将数据送入段寄存器。
mov ax,[0]。此命令将偏移地址为0的数据送入AX中,而段地址则是寄存器DX中的数据。此操作将会传送一个字的数据到AX中,如果将AX换成AH或者AL。则将传送一个字节的数据。同样也可以进行mov [0],ax这样的操作。上诉说到不能将一个数据直接送入段寄存器,但是可以将内存中的数据送入段寄存器:mov DS,[0]。
栈
一段内存空间,遵循先进后出的原则。push 入栈 pop 出栈 都是以字为单位,低地址空间存放低位字节,高地址空间存放高位字节。段寄存器SS 和寄存器SP指向的地址永远为栈顶,SS:SP指向栈顶。
当使用push向栈中压入数据时,SP将先减2,然后此时SS:SP指向新的栈顶,然后再将数据压入栈。
而使用POP将栈中的数据弹出时,将先将数据弹出,然后再将SP加2。
栈空时的状态:
栈空时,不存在栈顶栈底,此时SS:SP指向栈中最后一个元素的下一个存储单元,当要向栈中压入数据时,SP加2,这时SS:SP就正好指向压入数据后的栈顶。
push指令 pop指令后面可以接寄存器也可以接内存单元,操作都是以字为单位的操作。
因为push,pop都只改变SP,也就是都只改变偏移地址,所以栈顶的最大变化范围为0~FFFFH,一个栈段的最大空间就为64KB。
汇编程序
汇编源程序有固定的格式,也有伪指令和汇编指令,伪指令由编译器来编译,汇编指令由计算机来编译。
一般格式都为:
assume 寄存器名:段名
段名 segment
汇编代码......
.......
mov ax,4c00h
int 21h
段名 ends
end
每一段都有个段名,然后后面要有结束标志,整个程序最后也要有一个结束标志,而代码段的最后部分有一个固定的结束标志,是用来返回之前的程序的。
要运行我们的程序,那么在CPU中就得有一个在运行的程序将我们的程序加载进去,然后将CPU控制权交给我们的程序,然后等我们的程序运行完之后要返回原来的程序,将CPU的控制权交还回去,所以在最后有一个固定的返回格式。
程序被加载进CPU的过程:
loop指令
格式:
段名:代码......
loop 段名
每次执行loop指令时,会先检查寄存器CX中的数据是否为0,如果不为0,则先将CX中的数据减一,然后再跳转到loop后面的段名处。如果为0,则不执行loop指令,直接往下执行。
start
可以在某个代码段的某行代码前加上 start ,这样就指明了程序的入口,那么程序将会从这里开始执行,同样在程序的最末尾的end要变成end start .
其实是end start 指明了程序的入口,后面的start也可以换成其他字符。
dw 定义字型数据
dd 定义双字型数据
db 定义字节型数据
如果在[]中使用的是寄存器BP 那么段地址默认为段寄存器SS中的数据。
用[]寻址时,[]里面只能用 DI,SI,BP,BX 这几个寄存器还有立即数,并且搭配的时候只能是BX和SI,DI,BP和SI,DI。
div 除法指令
mul 乘法指令
dup
操作符,使用方法:要重复定义一段数据时 如:db 2 dup ('abc') 等同于 db 'abc','abc' db 2 dup('abc','def') 等同于 db 'abc','def','abc','def'
offset :获得标号的偏移地址
seg : 获得标号的偏移地址
jump 无条件转移语句
jump short 后面接标号,当程序运行到这将会直接转移到标号处运行(短转移,将short改成near ptr就是近转移,范围更大)。短转移近转移都是段内转移,其原理就是改变寄存器IP。
当CPU读取了这一条指令,并且这条指令进入了指令缓冲器,这时IP中的数据增加,CS:IP指向下一条指令,然后JUMP指令执行,它将会把IP中的数据加上 JUMP后面标号的偏移地址减去JUMP指令下一条指令的偏移地址的值,这样操作后,IP中的值就正好是标号的偏移地址。这两种转移的机器码都包含了那个差值而不是标号的地址,差值是在编译的时候算出来的
jump far ptr 长转移,它的机制与短转移近转移不同,它的机器码中直接包含了标号的段地址的偏移地址,不用计算。
JUMP 寄存器 :寄存器中存放要转移的地址的偏移地址,指令执行后IP中的数据将会变成寄存器中的数据。
JUMP 后接内存单元
第一种,段内转移,JUMP word ptr 内存单元:指令执行后将会将寄存器IP中的数据设置为从这个内存单元开始读取一个字的数据。
第二种,段间转移,JUMP dword ptr 内存单元:指令执行后将会读取从这个内存开始两个字的内容,高位字节的数据将设置为CS中的数据,低位字节数据将设置为IP中的数据
jcxz 条件转移指令 loop 循环指令
jcxz指令为条件转移指令,且为短转移,jcxz后面接标号,当寄存器CX中的值等于0时,执行转移指令,转移的原理和 jump short的原理一样。当CX不等于0时什么也不做。
loop循环指令的转移原理也和 jump short 的原理一样。并且也是短转移。
ret和retf
ret 为近转移指令,它后面什么都不用接,执行这个指令就等于:
pop ip 把栈顶数据弹出到寄存器IP中来改变IP的值。
retf 为远转移指令,后面也不用接任何东西,执行这个指令相当于:
pop ip
pop cs 先弹出栈顶的数据到IP中,然后再弹一个到CS中。这样就修改了CS:IP的指向。
上诉两个指令执行后指向栈顶的指针SS:SP都会发生变化,就像是用了 pop指令一样。
call
call指令后面可以接标号和寄存器以及内存地址
接标号时,call 标号:这样是近转移,执行指令将会先把IP中的值压入栈,然后再把IP中的值设置为标号的偏移地址,原理和jump的近转移一样,是通过增加位移来实现的。
call far ptr 标号:这样是实现段间转移,既然是段间转移,那就肯定要同时修改CS和IP,它就是先将CS压入栈,再把IP压入栈,然后再直接将CS和IP设置为标号的段地址和偏移地址。
call 寄存器:8086寄存器只有16位,所以后面接寄存器的只能实现近转移,就是先将IP压入栈,然后再将IP修改为寄存器中的值。
call加内存地址也有两种,一种是只修改IP的近转移,一种是修改CS和IP的段间转移。
call word ptr 内存地址:先将IP压入栈,然后从内存地址读取一个字的内容设置为IP的值。
call dword ptr内存地址:先将CS压入栈,然后将IP压入栈,然后从内存地址读取两个字的内容,高位地址的数据作为CS中的数值,低位地址作为IP中的数值。
可以注意到近转移时 call 和ret可以配合使用,段间转移时 call可以和 retf 配合使用。通常 call 用来调用函数。
标志寄存器
zf 位,记录计算的结果是否为0,如果为0,则这个标志位的数据为1,如果不为0,则这个标志位的数据为0。只会记录计算的结果,mov ,push ,pop这种指令是没用的
pf 查看计算结果的二进制的所有比特位中1的个数是否为偶数,如果为偶数则这个标志位记为1,如果不是记为0
sf 查看计算结果是否为负,为负就记为1,不为负就记为0。
cf 查看计算是否有进位或者借位的情况,有就记为1,没有就记为0。
of 查看计算是否有溢出,有就记为1,没有就记为0。
该指令也会影响CF进位值
也会影响CF借位值
cmp 指令
cmp 操作数1,操作数2 相当于一个减法指令,其实也可以看成一个比较指令,它执行将会将前面的操作数减后面的操作数,但不保留结果,只是会影响标志寄存器。
因此可以根据这个指令和标志位来一些条件转移指令
这样每次转移一个字节,把movsb 改成movsw 就是每次转移一个字
当然这种转移指令肯定有一个指令和它配套使用
rep movsb或者rep movsw
相当于s:movsb
loop s
rep指令也是会根据CX中的数据是否为0来转移的,原理和loop指令一样。
内中断
CPU的中断处理机制,当CPU执行完当前的一条指令后,因为收到了CPU内部的中断信息转而去处理其他指令,这样的机制就是内中断。
内中断的产生,由中断信息源发出的中断信息,CPU接收到后去执行中断程序。内中断源,比如除法溢出,单步执行。每一种不同的中断信息对应不同的中断源,而中断信息中又包含有标识中断源的中断类型码。当CPU接收到了中断信息后,就开始执行中断过程,这个过程由硬件执行,执行完中断过程后就开始执行对应的中断程序。
那么,中断类型码有什么用?前面说到CPU在执行完中断过程后要去执行中断处理程序,CPU就是根据中断类型码在中断向量表中找到对应的中断处理程序的地址,中断向量表是一个在内存中有着固定地址的用来存放中断处理程序地址的一个表,里面每个中断处理程序的地址占两个字的内存,高地址存放段地址,低地址存放偏移地址。这个表占了1024个字节,8086CPU的中断类型码为一个字节的大小,能表示256中不同的中断源,比如说中断类型码为1的中断源,它的中断处理程序在中断向量表中对应的地址就是第一个。我们来看看中断处理程序:第一步,从中断信息中提取出中断类型码。第二步,标志寄存器的值入栈(因为中断处理程序可能会改变这些值)。第三步,将标志寄存器的IF和TF标志位置为0(因为TF的标志位为1就是单步中断这个中断源,如果这个标志位为1,那么执行完一条指令后将会一直循环执行单步中断程序,将IF标志位置为1是禁止可屏蔽中断:IF位为1则执行可屏蔽中断,为0则不执行)。第四步,将CS,IP入栈。第五步,从中断向量表中读取对应的CS,IP并设置好。
执行完中断过程后,因为CS:IP这时指向中断处理程序的入口,所以就会去执行中断处理程序了。
那么,执行完中断处理程序后应该再返回原来的指令执行的地方,有一个指令正好与之前的中断过程相对:iret
它等同于:pop ip
pop cs
popf
但是有些指令执行后就算发出中断信息也不会有响应,比如改变SS中的值
因为如果我们在执行完对SS的设置的指令后响应中断,注意:这个时候我们只设置了SS就转而去执行处理中断程序了,而我们还没有设置SP的值,而我们的中断处理过程以及程序中,都要将一些东西比如标志寄存器,CS,IP什么的压入栈,而这个时候我我们的SS是改变了的,而SP还没有改变,所以这个时候我们的SS:SP指向的是一个错误的栈,这个内存里面的数据是不确定的,可能会引发一些不好的后果。
int
int指令可以用来调用中断例程,如 int n 。n为中断类型码。
BIOS基本输入输出系统
当CPU一加电,就会开始执行指令,最先执行的就是BIOS中的指令
之前的汇编源程序结束部分的 mov ax,4c00h int 21h 就是调用了DOS的一个中断例程中的子程序,是一个返回程序。4C是中断例程中的子程序编号,BIOS和DOS都是向AH中传递子程序编号的。
端口
与CPU总线相连的除了各种存储器外,还有主板上的接口卡上的接口芯片,还有主板上的接口芯片,还有一些其他的芯片。这些芯片都是用来控制对应的东西的(接口卡芯片控制接口卡,主板上的接口芯片CPU通过它们控制一些外设)。这些芯片上面都有一组寄存器,可以让CPU进行读写,这些寄存器在CPU看来也是内存,是在逻辑内存中有自己的地址的。CPU把这些寄存器当做端口。
CPU可以读写自己的寄存器,还有内存,还有端口。
CPU对端口的读写只有两个命令: in和out 且只能用寄存器AX来进行数据写入和读出,比如 in al,60h:从60号端口读取一个字节的数据。
shl 左移指令和 shr 右移指令,比如 shl ax,1就是将寄存器AX中的数据左移一位,出来的数字将放入CF标志位,低位用0来填充,如果左移或右移的位数大于一位,那么要将移动的位数放入CL中,CF标志位记录的是最后一个移出的数字。
外中断
在CPU内部产生的中断叫做内中断,在外部产生的就叫外中断。CPU对外设的输出和外设对CPU的输入都不是直接的,都要先将数据存入对应端口中,也就是说CPU通过端口与外设进行交流。
外设产生的中断类型码放在端口中,通过总线送入CPU。
外中断分为可屏蔽中断和不可屏蔽中断
大部分外设产生的中断都是可屏蔽中断,可屏蔽中断的中断过程和内中断的中断过程差不多,除了第一步内中断是从中断信息中取得中断类型码,外中断是从端口中取得中断类型码。其他都一样,而不可屏蔽中断的中断类型码是固定的为2,所以中断过程没有第一步。
可屏蔽中断的执行与否是根据标志寄存器的IF标志位是否为1来决定的。STI命令将IF为设置为1,CLI将IF为设置为0。
BIOS提供的访问磁盘的中断例程
int 13h 13h号中断例程,可以对扇区进行读写,向AH传递2为读,3为写