• 存储器包括:

    随即存储器(RAM) - 用于存放供CPU使用的绝大部分程序和数据,主随机存储器一般由两个位置上的RAM组成,装在主板上的RAM和插在扩展插槽上的RAM(指内存条)。

    装有BIOS的ROM - BIOS是有主板和各类接口卡(网卡显卡等)厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入输出。主板和接口卡上插有存储相应BIOS的ROM。主板上的ROM有主板的BIOS,显卡上的ROM有显卡的BIOS。网卡上如果有ROM,也会有网卡的BIOS。

    接口卡上的RAM - 用于对大批量输入输出数据进行缓存,如显存。显卡随时将显存中的数据显示在显示器上。

    内存是有以上所有类型存储器的总和,CPU需要和以上所有的存储器进行读写工作。存储器以byte为最小单位作为一个存储单元,把存储单元编号,即为内存地址。这些存储器都是通过总线和CPU相连,CPU通过控制总线对其发出读写命名。虽然各个存储器物理上是独立的器件,但是CPU操作时并不会区分哪个是哪个,而是都当作由若干存储单元组成的逻辑存储器对待,每个存储器在地址空间中占有一小段,然后根据地址到指定的地方读写数据而已。8086地址总线宽度为20,所以其寻址空间为2的20次方,即1M, 而80386地址总线宽度为32,则内存地址空间最大为4G。

    CPU的读写需要和外部器件进行三方面的信息交互:

    • 存储器单元的地址(地址信息)
    • 器件的选择,读或写的命令(控制信息)
    • 读或写的数据(数据信息)

    CPU通过总线与外部器件连接,相应的有地址总线,控制总线和数据总线。地址总线给出操作的地址,控制总线给出操作的类型读或写,数据总线读或写出具体的信息。

    一个CPU有N根地址线,则可以说地址总线宽度为N。寻址范围则为2的N次方。数据总线的宽度决定了CPU和外界数据传送的速度。 8根总线则一次可以传送一个byte, 16根总线则一次可以传送两个byte。8088CPU总线宽度为8,8086CPU总线宽度为16. 控制总线宽度反应了CPU的控制能力,其中一根称为读信号输出的控制线负责cpu向外传送读信号,cpu向该控制线上输出低电平表示要读取数据,另一根写信号输出控制线负责传送写信号。

    CPU不能对外部设备进行直接控制,而是通过外部接口卡来控制,如显卡控制显示器,声卡控制音箱等。

    汇编语言笔记-寄存器

    一个典型的CPU由运算器、控制器、寄存器构成。这些器件靠内部总线(地址总线、控制总线、数据总线都是外部总线)相连。运算器进行信息处理,寄存器进行信息存储,控制器控制各器件进行工作,内部总线在各器件间进行数据传总。

    不同的CPU,寄存器的个数不同,每个寄存器的容量也不同,8086CPU有14个寄存器,每个寄存器都是16位的,即可以存放2byte。其中AX(Accumulator累加寄存器, 可用于乘、 除、输入/输出等操作,使用频率很高)、BX(Base Register基址寄存器, 可作为存储器指针来使用)、CX(Count Register计数寄存器 在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数)、DX(Data Register,在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址)用来存放一般性数据,叫通用寄存器,为了兼容8位寄存器,可以把16位寄存器分成2个8位寄存器用,即AX可以分为AH、AL,其他类同。在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,在32位CPU中,其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性.

    8086CPU作为16位结构的CPU,运算器一次最多可以处理16位的数据,寄存器最大宽度为16位,可以一次性处理、传输、暂存的地址也为16为,但是8086却有20位地址总线。所以8086内部用两个16位地址通过地址加法器来合成一个20位物理地址,此两个16位地址一个为段地址,一个为偏移地址,物理地址=段地址*16 (即左移4位)+ 偏移地址。段只是CPU管理内存的一种方式,内存本身并没有分段。段地址*16 称为基础地址,也成为段起始地址,可见此地址一定是16的倍数。

    8086CPU中CS和IP(Instruction Pointer)是两个重要的寄存器,CS为代码段寄存器,IP为指令指针寄存器,专门用于存储段地址和偏移地址。即CS:IP指向的内容永远是CPU下一条要执行的指令。

    程序员不能用mov来修改CS和IP的值,可以用转移指令如jmp来修改。jmp有两种格式,一种“jmp 段地址:偏移地址" 用于直接跳转到指定地址,另一种"jmp 某一合法寄存器"如jmp ax, 表示仅用寄存器的值修改IP即偏移地址的值,段地址不变.

    DS寄存器通常用来存放要访问的数据的段地址,mov al,[0] 指令表示把偏移地址为"0", 段地址不变(即存在DS寄存器中的值)的数据存入al寄存器中。8086CPU不支持把数据直接送入段寄存器的操作, 所以要改变段地址必须借助寄存器,如mov bx, 1000H; mov cs bx。mov指令和add指令sub指令一样都是双操作数,可以有的形式为add 寄存器,数据;add 寄存器,寄存器;add 寄存器,内存单元;add 内存单元,寄存器; add 寄存器,数据[add ax,8]mov 段寄存器,寄存器[mov ds, ax]; mov 寄存器, 数据[mov ax,8]; mov 寄存器, 寄存器[mov ax,bx];mov 寄存器,内存单元[mov ax,[0]];mov 内存单元,寄存器[mov [0],ax]

    段寄存器SS和寄存器SP用来存储栈顶的地址,SS:SP永远指向栈顶元素。入栈时,栈顶从高地址向低地址方向增长。在指令中"[ax]"表示寄存器ax中的内容,[1111h]就表示内存单元的偏移地址是0x1111. 所以"mov ax, bx"的意思就是bx寄存器中存放的内容作为偏移地址, 段地址就是ds中的内容,然后把此地址中的内容放入ax中。即(ax)=((ds)*16+bx), 描述语言中加()表示其内容。而[bx+200]表示bx的内容加上200,即"mov ax,[bx+200]"的含义是"(ax) = ((ds)*16+(bx)+200)", 汇编语言中BX寄存器常用来和立即数一起构成内存单元的地址,即形如[bx+20]的形式si和di寄存器也可以和bx一样这样使用,但是si和di不能分成8位寄存器来使用, SI和DI被称为变址寄存器(Index Register, 包括32位的ESI和EDI),主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便,另外ESI和EDI低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响高16位的数据,SI和DI与串操作指令REP/ STOS/ MOVS/ CMPS/ SCAS/ LODS甚至可以用[bx+si+200h]或[bx+di+0h]等更灵活的方式来访问内存。另外bp寄存器也可以放在[]中来使用。即可以放在[]使用的寄存器只有"bx,si,di,bp"四个,这四个可以单独使用或组合使用或和立即数一起使用, 但是[bx+bp],[si+di]的组合是错误的。另外,bp作为堆栈段的栈基址寄存器,在有bp出现而没有指明段地址的情况下,缺省段地址是[ss], 而不是[ds],如[bp+si+200h]的段地址就是[ss],[bx+di+39h]的段地址在[ds],BP后SP作为指针寄存器(base pointer, stack pointer)相应的32位版本EBP和ESP,对低16位数据的存取,不影响高16位的数据,指针寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果.

    汇编程序中标号代表一个地址,用于实现跳转,如"loop 标号"指令,就是要跳转到标号的位置去执行以实现循环,循环控制用cx中的值来计算。loop的执行先要把cx中的内容减1,如果大于0则跳转到标号位置,否则继续执行

    在汇编程序中和debug不同的一点是,debug认为"mov ax,0"和"mov ax,[0]"不一样,前者表示将0存入ax,后者[0]表示偏移地址,但是汇编程序中,二者含义一样。汇编程序中要用立即数来指明偏移地址,一种办法是借助寄存器,如"mov bx,0  mov ax,bx". 另一种办法是直接给出段地址,如"mov ax, [ds]:0", [bx]也是间接给出了偏移地址,默认段地址在ds中。

    32位段寄存器包括 ECS——代码段寄存器(Code Segment Register),其值为代码段的段值;EDS——数据段寄存器(Data Segment Register),其值为数据段的段值;EES——附加段寄存器(Extra Segment Register),其值为附加数据段的段值; ESS——堆栈段寄存器(Stack Segment Register),其值为堆栈段的段值;
    EFS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;EGS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值
    。所以,在32位环境下开发的程序最多可同时访问6个段。32位CPU有两个不同的工作方式:实方式和保护方式。实模式下前4个段寄存器CS、DS、ES和SS与先前CPU中的所对应的段寄存器的含义完全一致,内存单元的逻辑地址仍为“段值:偏移量”的形式。为访问某内存段内的数据,必须使用该段寄存器和存储单元的偏移量。 保护模式下,情况要复杂得多,装入段寄存器的不再是段值,而是称为“选择子”(Selector)的某个值

    debug的使用简介

    R命令查看、改变寄存器的内容,D命令查看内存中的内容,E命令改写内存的内容。U命令将内存中的机器指令翻译为汇编指令,T命令执行一条机器指令。A命令以汇编指令的格式在内存中写入一条机器指令。

    输入"r 寄存器名字"回车,则出现-符,然后输入要修改成的内容,及可以修改指定寄存器的内容。用"e 起始地址 数据 数据 数据"的格式可以改写内存中的内容。

    一个完整的程序

    汇编语言中包括两种指令,汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。伪指令没有对应的机器指令,最终不会被CPU执行,伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。比如assume就是一条伪指令,用来指定某一个段的代号,如"assume cs:codeseg",表示用codeseg来表示代码段。然后就可用用"codeseg segment .... codeseg ends"来开始和结束一个代码段了。在汇编程序里用"段名 ends"表示一个段的结束,用"end"表示汇编程序的结束,用"mov ax,4c00h int 21h"表示程序返回,交出cpu控制权。

    dw用来定义一个字,如"dw 0123h",数据类型为word。当在代码段中定义数据时,数据从代码段的起始位置开始存放,也就是说cs:[0]处存放的是第一个变量。为了明确指明程序的起始位置,可以在程序的起始位置加标号,如"start",并用"end start"来取代原先的"end"来结束程序,这样就明确标明了程序的起始位置。我们也可以把栈放在代码段里,只需要定义一块连续的空间(如"dw 0,0,0,0,0"),并把SS:SP指向起始空间的开始位置,就可做堆栈操作。我们也可以定义专门的堆栈段和数据段,如"assume cs:code, ss:stack, ds:data", 然后用"stack segment"开始一个堆栈段并用"end stack"来结束一个堆栈段。数据段也一样,都和前面提到的代码段一样。需要注意的是"标号就代表了段的起始地址",所以可以做如下操作"mov ax, data",用以把数据段中定一个的第一个"字"变量存到ax寄存器里。

    数据后边加H表示数据是16进制表示的,加B表示是二进制表示的,如"101110B"。用''方式表示一个字符串,如"db 'unIX'"就定义了一个字符串,相当于"db 75H,6EH,49H,58H"。dd用于定义一个32位数。

    在汇编语言中可以有多种方式来指明内存单元的长度。其一是利用寄存器,如ax表示16bit,al表示8bit。其二是"X ptr"方式,由于8086CPU只有两种类型"字节和字",所以X表示word或者byte,如"mov word ptr ds:[0],1", "inc byte ptr [bx]"。其三有些指令本身已经表明了。如push和pop只对字操作。

    div用于除法操作。除数有8位和16为两种放在一个寄存器或存储单元里,被除数有8位16为32位三种。如果是8或16位,则放在AX中,如果是32位,则高位放DX中,低位放AX中,如果除数是8位,则AL存商,AH存余数,如果除数为16位,则AX存商,DX存余数。mul是乘法指令。乘数要么都是8位要么都是16位,如果都是8位一个放在al中。另一个放在8位寄存器或内存单元中,如果是16为,则一个乘数放在ax中。另一个放在16为寄存器或内存单元中。如果是8位相乘,则乘积放在ax中,如果是16位相乘,乘积高位放在dx中,低位放在ax中。

    dup用于数据定义,表示重复。格式为"db/dw/dd 重复次数 dup (重复的字节/字/双字数据)" 如"db 3 dup(0,2,3)" 等同于"db 0,2,3,0,2,3,0,2,3"。

    转移指令指改变CS:IP的指令。包括段间转移如"jmp 1000:0",和段内转移如"jmp ax"。由于转移指令对IP修改范围不同,段内转移又分为短转移(ip修改范围在-128 - 127)和长转移(-32768 - 23767)。8086的转移指令包括: 无条件转移指令如jmp;条件转移指令;循环指令如loop;过程;中断

    offset是编译器处理的符号,用于取得标号的偏移地址如 "s: mov ax,offset s" 用于取得标号s的偏移地址,从而不用去计算s处的偏移地址,编译器自己可以算出。jmp作为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。其一格式为"jmp short 标号", 用于段内短转移到标号处执行指令,IP的修改范围为"-128 - 127",编译器处理后并不给出新的CS:IP,而是给出一个位移(即向前还是向后移动多少字节),因为系统不能确定目的地址,利用这种方式实现跳转,所以实际上内短转移指令"short 标号"等同于"(IP)=(IP)+8位位移[所以IP改变的范围为-128 - 127]"。而段内近转移"near ptr 标号"相当于"(IP)=(IP)+16位位移[范围为-32768 - 32767]" 而段内长转移"far ptr 标号" 实现的是段间转移。其二格式为"jmp 寄存器(16位)", 如"jmp ax". 其三为"jmp word ptr 内存单元地址(段内转移)[内存单元中存放的字是偏移地址]" 或 "jmp dword ptr 内存单元地址(段间转移)"[内存单元中有两个字,高地址的字中是目的段地址,低地址的字中是目的偏移地址]

    所有条件转移指令都是短转移,即IP修改范围在-128 - 127之间。如"jcxz 标号"表示"如果(cx)=0 转移到标号处执行, (cx)<>0则顺序执行"。loop作为循环指令也是短转移。指令格式为"loop 标号[(cx)=(cx) - 1 如果(cx<>0) 则转移到标号处执行]"

    ret和retf指令用栈中的数据修改CS和IP实现转移,其中ret实现近转移。等同如下2步操作"1. (IP) = ((SS)*16 + (SP)) 2. (SP) = (SP) + 2",相当于"POP IP"。而retf指令等同于如下4步操作"(IP) = ((SS) * 16 + (SP)) 2. (SP) = (SP) +2 3. (CS) = ((SS)*16 + (SP)) 4. (SP) = (SP) +2",相当于"POP IP  POP CS"。

    Call指令不能实现短转移,其转移实现的原理和jmp相同。Call指令分两步进行,第一步"将当前的IP或CS和IP压栈",第二步转移格式"call 标号"或"call 16为寄存器"或"call word ptr 内存单元地址"为段内转移,格式"call far ptr 标号"或"call dword ptr 内存单元地址"是段见转移

    shl逻辑左移指令,功能为将寄存器或内存单元中的数据向左移位,将最后移出的一位写入CF中。最低位用0补充。shr是逻辑右移指令。

    标志寄存器(Flag register), VC中用EFL(标志寄存器)表示

    标志寄存器的作用是 1. 存储相关指令的某些执行结果。2. 为CPU执行相关指令提供依据。3. 用来控制CPU的相关工作方式

    8086CPU的标志寄存器使用了16位中的9位。第6位ZF表示指令执行的结果是否为0. 第二位PF为奇偶标志位,表示指令执行后,结果中所有的bit位中1的个数是偶数还是奇数。第7位SF表示符号位,表示执行结果的正负。第0位CF是进位标志位。表示在无符号运算是结果的最高有效位是否向更高位借位或进位。第11位OF(Overflow Flag)是溢出位,表示结果是否溢出,仅对有符号数运算有效。第10位为方向标志位DF。串处理指令中控制每次操作后si,di的递增还是递减。pushf和popf分别是对标志寄存器压栈和出栈。辅助进位标志AF(Auxiliary Carry Flag)在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:(1)、在字操作时,发生低字节向高字节进位或借位时;(2)、在字节操作时,发生低4位向高4位进位或借位时。

    状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。
    1、追踪标志TF(Trap Flag)
    当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。指令系统中没有专门的指令来改变标志位TF的值,但程序员可用其它办法来改变其值。
    2、中断允许标志IF(Interrupt-enable Flag)
    中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下:(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。CPU的指令系统中也有专门的指令来改变标志位IF的值

    adc是带进位加法指令,利用了CF位来进行加法计算。adc ax,cx 表示(ax) = (ax) + (cx) + CF. SBB是带借位减法指令。sbb ax,cx 表示(ax) = (ax) - (cx) -CF. cmp指令是比较指令,相当于不保存结果的减法指令,然后通过标志位来判断比较的结果。可以根据CF和ZF的值判断比较的结果。

    其他条件转移指令包括: je等于则转移(zf=1), jne不等于则转移(zf=0), jb低于则转移(cf=1), jnb不低于则转移(cf=0), ja高于则转移(cf=0或zf=0), jna不高于则转移(cf=1或zf=1)。其中e = equal, a = above, b = below。

    32位标志寄存器增加的标志位
    1、I/O特权标志IOPL(I/O Privilege Level)
    I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。
    2、嵌套任务标志NT(Nested Task)
    嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:(1)、当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;(2)、当NT=1,通过任务转换实现中断返回。
    3、重启动标志RF(Restart Flag)
    重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。
    4、虚拟8086方式标志VM(Virtual 8086 Mode)
    如果该标志的值为1,则表示处理机处于虚拟的8086方式下的工作状态,否则,处理机处于一般保护方式下的工作状态。

    中断

    由于CPU内部情况而发生的中断叫内中断,如除法错误(0号中断),单步执行(1号中断),执行into指令(4号中断),执行int指令(包括各种中断号 int n, n为byte型)。中断即指CPU停止当前的操作,而去相应另一个操作。CPU用8位的中断类型码,查找中断向量表来找到需要跳往的地址。执行中断前也需要保存现场包括IP,CS,标志寄存器值等,然后执行完毕后用iret而不是ret指令返回。

    两个常数的减法可以用-完成,由编译器负责解析。

    外中断源有可屏蔽中断和不可屏蔽中断两大类。可屏蔽中断是cpu可以不响应的中断,cpu依据IF寄存标志判断是否响应,IF=1则CPU执行完当前命令后响应中断,如果IF=0则不响应中断,所以IF置0的意义是在进入中断处理程序后,禁止其他的可屏蔽中断。不可屏蔽中断是cpu必须响应的中断,执行完当前指令后马上响应,不可屏蔽中断的类型码固定为2。

    端口

    CPU可以对其内部的寄存器,内存单元以及端口直接进行读写。PC中和CPU通过总线相连的除了各种存储器外,还有3种芯片,其一各种接口卡上的芯片(如网卡显卡等),其二主板上的接口芯片,主板通过他们对部分外设进行访问,其三其他用来存储相关系统信息或输入输出的芯片。这些芯片中也都有寄存器通过总线和CPU相连,虽然这些寄存器物理上在不同芯片中,但都被cpu通过统一编址而进行统一管理。CPU将这些寄存器都当作端口对端口的读写只能用int和out命令,而不能用move,push等命令。8086cpu最多可以定位64K个端口。in al, 60h表示从60号端口读一个byte数据到al中.