Asm - HsuJv/Note GitHub Wiki

基础知识

  • 机器语言

    • 机器语言是机器的指令集合, 由CPU直接执行
  • 汇编语言

    • 汇编指令将繁琐的机器码通过可读性高的助记符代替
    • 由汇编指令, 伪指令, 其它符号
      • 汇编指令被汇编器直接翻译成机器码
      • 伪指令(如assume等), 其它符合(如+-*/等)由汇编器识别和执行
  • 存储器

    • 即通常所说的内存
    • 显卡, 声卡, 键盘等外设中的内存等
  • 指令与数据

    • 指令和数据无明显区分, 由指向相应内容的寄存器告诉CPU该二进制信息是指令还是数据
  • 存储单元

    • 存储器被划分为多个存储单元, 从0开始编号
    • 单位: Img crashed
  • CPU对存储器的读和写

    • 通过 地址总线(Data Bus) 定位地址
      • n根地址线传递n位地址, 最大寻址空间是 Img crashed
    • 通过 控制总线(Control Bus) 得到指令
      • 有多少根控制总线, 就意味着CPU提供了多少种对外部器件的控制
    • 通过 数据总线(Data Bus) 传递数据
      • 数据总线的宽度直接决定CPU和外界的数据传送速度
  • 主板和接口卡

    • 主板上有通过总线相连的pc核心, 主要器件
    • CPU通过接口实现和外部设备的通信

寄存器

  • 通用寄存器

    • ax, bx, cx, dx通常用来存放一般性数据
    • 可以分别为分8位的ah, al, bh, bl, etc.以兼容8位程序
    • 32位CPU中每个寄存器名前面加e(eax等),64位中为r
  • 字的在寄存器中的存储

    • 字(Word) = 2 Bytes
    • 高8位存放在ah中, 低8位在al中
  • 物理地址

    • CPU访问内存单元时要给出内存单元的地址, 所有内存单元一维线性分布
    • 它们的唯一的地址编号即为物理地址
    • 80x86CPU的物理地址 = 段地址 * 16 + 偏移地址(用内部16位的数据线充分利用外部20位的地址总线进行寻址)
  • 段(Segment) 的概念

    • 段的划分来自CPU, 因为偏移寻址的存在, 给出段的概念方便管理内存
    • 一个段可以定位ffff = 64K个内存单元
    • 根据需要, 将地址联系, 起始地址为16的倍数的一组内存单元定义为一个段
  • 段寄存器

    • 80x86CPU有四个段寄存器: CS(Code Segment), (Data Segment), SS(Stack Segment), ES(Extra Segment)
    • 32, 64位CPU中的拓展同通用寄存器
  • CS:IP

    • CS: 代码段寄存器
    • IP: 指令指针寄存器
    • 任意时刻, CPU总在执行CS:IP指向的内存单元的指令
    • 80x86CPU加点启动或复位的时候, CS = ffffH, IP = 0000H
    • CS, IP无法通过mov指令改变, 可以通过转移指令jmp的相关指令改变

内存访问

  • DS和[address]

    • 80x86CPU中的DS段寄存器同常用来存放要访问的数据的段地址
    • 索引时默认使用DS中的段地址, 和指令给出的偏移地址进行寻址
    • 当然, 也可以显示的给出段寄存器
  • mov, add, sub

    • mov 可以:
      • 立即数(idata) 送入通用寄存器
      • 立即数送入内存单元
      • 寄存器和寄存器间相互传送
      • 内存单元和寄存器间相互传送
      • 不能直接将数据送入段寄存器
    • add, sub:
      • 格式: add r1(register)/m1(memory), r2/m2
      • 返回: r1/m1
    • 同高级语言相关概念
    • 栈寄存器SS(Stack Segment)和栈顶指针SP(Stack Point)时刻指向栈最顶端的数据
    • 栈空时, 栈顶指针指向最高地址的下一位地址
  • 栈顶越界

    • 当栈空间满, 再执行push指令, 就会超出我们分配的栈空间, 产生溢出, 覆盖掉不应该被覆盖的数据
    • 当栈空, 再指向pop指令, 同理也产生溢出
    • 若栈空间占据了一整个栈段, 则会覆盖掉最底/顶的数据
  • push, pop

    • 80x86CPU中, push指令的实现为:
      sub     sp, 2  
      mov     ss:[sp], r/m
      
    • pop指令的实现为:
      mov     r/m, ss:[sp]  
      add     sp, 2
      

Debug相关指令

  • R: 查看, 改变CPU寄存器的内容
  • D: 查看内存中的内容
  • E: 改写内存中的内容
  • U: 将内存中的机器指令翻译成汇编指令
  • T: 以单步步入方式执行一条机器指令
  • A: 以汇编指令的格式向内存中写入机器指令
  • P: 以单步步过方式执行一条指令
  • G: 执行到(CS:)指定地址处停止

第一个程序

  • 一个程序从写出到执行

    • 由汇编编译器(MASM.exe)对源程序进行编译, 产生目标文件.obj
    • 再用连接程序(LINK.exe)对目标文件进行连接, 生成可执行文件
    masm    *.asm   ; 生成*.obj
    link    *.obj   ; 生成*.exe
    

    或直接 ml *.asm 也可以生成*.exe

  • 可执行文件

    • 可执行文件包含:
      • 程序和数据(机器码, 源程序中定义的数据)
      • 相关的描述信息(程序大小等)
    • 执行时, 操作系统依照描述信息, 将机器码和数据载入内存, 并进行相关的初始化
  • 源程序组成

    • 汇编指令: 有对应机器码的指令
    • 伪指令: 没有对应机器码的指令, 编译器根据伪指令进行相关操作
      • segment和ends是一对成对使用的伪指令
      • 它们定义一个段, segment说明开始, ends说明结束
      • 格式:
      xxx segment
      ...
      xxx ends
      
    • 一个汇编程序由一至多个段组成
    • 伪指令end是一个汇编程序的结束标记, end后面给出的标号是程序的入口
    • 伪指令assume假定某一用segment...ends定义的段和某一段寄存器相关联, 编译器通过这种关联在编译时计算相关的 偏移(offset)
    • 标号: 一个标号指代了一个地址
  • DOS中的程序运行

    • DOS是一个单任务操作系统
    • 一个程序必须由另一个程序加载进内存, 转交CPU控制权, 才能得以运行
    • 运行后, 原程序暂停运行
    • 结束后, CPU控制权被交还给加载它的程序, 原程序继续
    • 结束后交还的过程被称为 返回(return)
      • 应该在程序结束添加代码:
        mov     ax, 4c00h
        int     21h
        
  • 编译和链接的作用

    • 编译:
      • 把汇编指令转换为机器码
      • 处理伪指令
    • 链接:
      • 将一个源程序编译后得到的目标文件中一些不能被直接执行的内容处理成为最终的可执行信息
      • 当源程序很大的时候, 分为多个编译, 再连接
      • 程序中调用某个库文件的子程序, 则将这个库文件和该程序生成的目标文件连接到一起, 生成可执行文件
  • PSP

    • exe文件加载之后, 内存的前256字节会被创建为一个程序的前缀(PSP), DOS要利用PSP来和被加载程序进行通信

[BX]和Loop

  • [bx]

    • 对于编译器来说, mov ax, [0]等价于mov ax, 0
    • 所以偏移地址不由立即数给出, 而是存在[bx]中
    • 将指令改写为
    mov     bx, 0
    mov     ax, [bx]
    
    • 所以bx又叫基址寄存器
    • 偏移地址只能存储在基址寄存器(bx), 或基址指针寄存器(bp), 或变址寄存器(si/di)中
  • Loop

    • loop的格式是: loop 标号
    • loop指令执行两步操作:
    dec     cx  ; 计数寄存器cx自减
    jnz     标号; 如果cx != 0 则跳转到标号中
    
    • loop指令类似于高级语言中的do - while循环
  • Debug和Masm对指令的不同处理

    • 在汇编源程序中, 数据不能以字母开头
    • 在汇编源程序中, 偏移地址不能由立即数给出, 但是在显示指明段寄存器的时候, 可以使用立即数
    • 在汇编源程序中, 立即数默认为十进制, 而Debug中为十六进制
  • 一段安全的空间

    • 80x86CPU中, 地址0:200到0:2ff共256B的内存单元是肯定不会有任何数据
    • 所以可以认为这是一段安全的空间
    • 在这一段空间内存储数据不会对操作系统产生影响

包含多个段的程序

  • 在代码段中使用数据

    • db: define byte , 定义字节型数据
    • dw: define word
    • dd: define dword(double word)
  • 在代码段中使用栈

  • 把数据, 代码, 栈放入不同的段

    • 把数据, 代码, 栈都放在代码段中显得很混乱
    • 80x86CPU中, 一个段的容量最大为64KB
    • 用类似定义代码段的方法定义栈段和数据段, 将它们放入不同的段中

实验:

> 程序如下, 编写code段中的代码, 将a段和b段的数据依次相加, 将结果存到d段中
assume cs : code

a segment
    db 1, 2, 3, 4, 5, 6, 7, 8
a ends

b segment
    db 1, 2, 3, 4, 5, 6, 7, 8
b ends

d segment
    db 0, 0, 0, 0, 0, 0, 0, 0
d ends

code segment
start:
...
code ends
end start

Think And Solve

更灵活定位内存地址

  • and和or

    • and(按位与): 同真为真, 其余为假
    • 通常用于将某个数据的某一位置0
    • 例如 and al, 10111111b, 即可将al的第六位置0
    • or(按位或): 同假为假, 其余为真
    • 通常用于将某个数据的某一位置1
    • 例略
  • 以字符形式给出的数据

    • 用'....'表示字符串
    • 例如: db 'unIX' , 相当于 db 75h, 6eh, 49h, 58h
  • 大小写转换

    • 大写字母恒比小写字母的ASCII码小20h
    • 体现在二进制代码中, 大写字母的第5位恒为0, 小写字母恒为1
    • 则大写字母转换成小写只需 or [bx], 00100000b
    • 小写字母转换为大写 and [bx], 11011111b
    • 大小写互相转换 xor [bx], 20h
  • [bx+idata]

    • 还可以表述为[idata+bx], idata[bx], [bx].idata三种形式
    • idata[bx]形式中, idata可以表述为数组首地址
    • [bx].idata形式中, idata可以表述为结构体内数据的offset
  • si和di

    • 源变址寄存器si(source index) , 目的变址寄存器di(destination index) 常用来进行复制功能, ds:si指向被复制内存, ds:di指向目的内存, 然后使用循环完成复制(或其它操作)
    • 可以利用[bx(si/di)+idata]使程序更加简洁
  • [bx+si]和[bx+di]

    • 还可以表述为[bx][si]
    • 用于二维数组
  • [bx+si+idata]和[bx+di+idata]

    • 还可以表述为[bx+idata+si], [idata+bx+si], idata[bx][si], [bx].idata[si], [bx][si].idata
    • 用于表格中的数据项: [bx].idata[si]可以表述为表.行[属性], idata[bx][si]可以表述为表[行][属性]; idata也可以表述为某行某属性中某成员变量的offset
  • 灵活的寻址方式

    • [idata], 立即寻址, 汇编中等效于idata
    • ds:/es:[idata], 直接寻址
    • [bx], [si], 寄存器间接寻址
    • [bx+idata], [si+idata], 寄存器相对寻址
    • [bx+si], 基址变址寻址
    • [bx+si+idata] , 相对基址变址寻址
    • bx和bp可以相互替换, si和di同样也可以

数据处理的两个基本问题

  • bx, si, di, bp

    • 80x86CPU中, 有且只有四个寄存器(bx, si, di, bp)可以用在'[...]'进行内存单元的寻址
    • bx和bp不能同时出现在一个'[ ]'中
    • si和di不能同时出现在一个'[ ]'中
    • 只要没有显式指明段地址, 若bp出现, 则默认为ss段, 否则为ds段
  • 机器指令处理的数据所在位置

    • 绝大部分机器指令都是进行数据处理(读取, 写入, 运算)的指令
    • 指令所要处理的数据可以位于CPU内部, 内存, 端口
  • 指令要处理的数据有多长

    • 通过寄存器名指明要处理的数据长度
    • 没有寄存器名的情况下, 用操作符X ptr(X = byte/word/dword/qword)指明内存单元的长度
    • 有些指令默认了数据长度, 例如: push, pop
  • div

    • 除法指令div(division) : div r/m
    • 除数: 8-bits/16-bits, 在寄存器或内存单元中
    • 被除数: (默认)放在ax(8-bits)或dx(high)和ax(low)(16-bits)中
    • 商: al(8-bits)或ax(16-bits)
    • 余数: ah(8-bits)或dx(16-bits)
  • dup

    • 伪指令 dup(duplication)
    • 配合db, dw, dd等伪指令使用
    • db x dup(y) ;重复定义x次数据y

实验:

> Power idea 公司从1975年成立一直到1995年的基本情况如下
> 
> 年份    收入(千美元)     雇佣(人)       人均收入(千美元)
> 1975      16              3                   ?
> 1976      22              7                   ?
> 1977      38              9                   ?
> ...
> 1995      5937000         17800               ?
> 
> 下面的程序中, 已经定义好了这些数据
assume cs:codesg, ds:datasg, ss:stacksg

datasg segment
    db     '1975','1976','1977','1978','1979','1980','1981','1982','1983'
    db     '1984','1985','1986','1987','1988','1989','1990','1991','1992'
    db     '1993','1994','1995'    ; 21年
    dd      16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065 
    dd      97479, 140417, 197514, 345980, 590827, 803530, 1183000, 1843000, 2759000
    dd      3753000, 4649000, 5937000    ; 21年内的总收入
    dw      3, 7, 9, 12, 28, 38, 130, 220, 476
    dw      778, 1001, 1442, 2258, 2793, 4037, 5635, 8226, 11542
    dw      14430, 15257, 17800  ; 21年雇佣员工数
datasg ends

table segment
    db      21  dup('year summ ne ?? ')
table ends
> 编程, 将data段中的数据按如下格式写入到table段中, 并计算21年中的人均收入(
> 取整), 结果也按照下面的格式保存在table段中

>            |  年份(4B) | 空| 收入(4B) | 空|雇员数(2B)|空|人均收入(2B)| 空
> 行内地址   |  0 1 2 3  | 4 |  5 6 7 8 |  9|  A    B  | C|   D    E   | F
> 行首地址   |           |   |          |   |          |  |            |   
> table:0    | '1 9 7 5' |   |     16   |   |    3     |  |     ??     |
> table:10H  | '1 9 7 6' |   |     22   |   |    7     |  |     ??     | 

Think And Solve

转移指令的原理

  • 80x86CPU中转移指令的分类

    • 无条件转移(如: jmp)
    • 条件转移(如: ja, jz, jb等)
    • 循环指令
    • 过程
    • 中断
  • offset

    • 伪指令 offset 取得一个标号的偏移地址
    • 例如 mov ax, offset 标号
    • 可以通过 offset 标号end - 标号start 计算某一段数据的长度
  • jmp

    • 无条件转移指令 jmp(jump)
    • 可以只修改ip, 也可以同时修改cs:ip
    • 需要给出转移的目的地址, 转移的距离(short(-128~127), near(-32769~32767), far(between segments))
    • 除远转移外, 其余转移通过计算相对当前ip的偏移量修改ip, 从而实现跳转
    • 当转移地址在寄存器中, 相当于短转移或近转移
    • 当转移地址在内存中, 通过word ptr实现段内转移, dword ptr实现段间转移, 其中低地址存放ip, 高地址存放cs
  • jcxz

    • jcxz(jump if cx is zero) 是有条件转移指令
    • 所有有条件转移指令都是短转移
    • 格式: jcxz 标号
  • loop

    • loop指令为循环指令, 所有的循环指令都是短转移

实验1:

> 分析下面程序, 在运行前思考, 这个程序可以正确返回吗?
> 运行后思考, 为什么会是这种结果?
assume cs:codesg
codesg segment
    mov     ax, 4c00h
    int     21h
start:
s:
    nop
    nop
    mov di, offset s
    mov si, offset s2
    mov ax, cs:[si]
    mov cs:[di], ax     

s0:
    jmp     short   s

s1:
    mov     ax, 0
    int     21h
    mov     ax, 0

s2:
    jmp     short   s1
    nop
codesg ends
end start

Think And Solve

实验2:

> 编程。在屏幕中间分别显示绿色, 绿底红色, 白底蓝色的字符串'Welcome to 
> masm!'
> 
> 编程所需的知识通过阅读, 分析下面的材料获得
> 80x25彩色字符模式显示缓冲区的结构: 
> 
> 内存地址空间中, b8000h-bffffh共32KB的空间, 为80x25彩色字符模式显示 缓冲区
> 向这个地址空间写入数据, 写入的数据, 写入的内容会立即出现在显示器上
> 
> 在80x25彩色字符模式下, 显示器可以显示25行, 每行80个字符, 每个字符可以有
> 256种属性(背景色, 前景色, 闪烁, 高亮等组合信息)
> 
> 这样, 一个字符在显示缓冲区中要占两个字节, 分别存放字符的ASCII码(低地址)
> 和属性(高地址), 80x25模式下, 一屏的内容在显示缓冲区共占4000个字节
> 
> 显示缓冲区分为8页, 每页4KB≈4000B。显示器可以显示任意一页的内容, 一般情况
> 下, 显示第0页(b8000h-b8f9f)的内容
> 
> 属性字节的格式: 
>           7   6   5   4   3   2   1   0
> 含义     BL   R   G   B   I   R   G   B
>          闪烁    背景    高亮    前景

Think And Solve

Call和Ret指令

  • ret和retf

    • ret指令利用栈中的数据修改ip, 实现近转移
    • 实现:
      pop     ip
      
    • retf则实现远转移
    • 实现:
      pop     ip
      pop     cs
      
  • call

    • call指令通常跟ret配合
    • CPU进行call指令进行两步操作:
      1. 将当前的ip或cs:ip入栈
      2. 转移(jmp)
    • call指令不能实现短转移, 除此外, call实现转移的方法和jmp的原理相同
    • 格式:
        call    标号  ; 16位位移  = 标号处地址减去call指令后第一个字节的地址
        call    far ptr     标号 
        call    r
        call    word ptr    m
        call    dword ptr   m
    
  • call和ret配合使用

    • 通过call和ret配合使用, 实现子程序的功能, 完成子程序的封装
    • 使程序更为结构化
  • mul

    • 乘法指令mul(multiply) : mul r/m
    • 相乘的两个数, 要么都是8位(al和8-bits r/m), 要么都是16位(ax和16-bits r/m)
    • 结果: ax(8-bits), dx(高位)和ax(低位)(16-bits)
  • 模块化程序设计

    • 由call和ret支持
    • 同高级语言中相关概念

实验1:

> 编写一个通用的子程序来实现显示字符串的功能
> 子程序描述: 
> 功能: 在指定的位置, 用指定的颜色, 显示一个用0结束的字符串
> 参数: (dh) = 行号, (dl) = 列号
>       (cl) = 颜色, ds:si指向字符串的首地址
> 返回: 无

Think And Solve

实验2:

> 编写一个通用的子程序来实现不会溢出的除法运算, 被除数为dword型, 除数为word
> 型, 结果为dword型
> 参数: (ax) = dword型数据的低16位
>       (dx) = dword型数据的高16位
>       (cx) = 除数
> 返回: (dx) = 商的高16位
>       (ax) = 商的低16位
>       (cx) = 余数

Think And Solve

标志寄存器

  • 80x86CPU中的PSW结构
 15  14  13  12  11  10  9   8   7   6   5   4   3   2   1   0
                 OF  DF  IF  TF  SF  ZF      AF      PF      CF
  • ZF

    • ZF(Zero Flag)
    • 当相关指令执行后
      • 结果为0, ZF = 1
      • 结果为非0, ZF = 0
    • 80x86CPU中有的指令会影响标志寄存器, 它们大多是运算指令
    • 有些指令不会影响, 它们多是传送指令
  • PF

    • PF(Parity Flag)
    • 当相关指令执行后, 结果的所有二进制中1的个数
      • 为偶数, PF = 1
      • 为奇数, PF = 0
  • SF

    • SF(Sign Flag)
    • 它记录指令执行后, 结果的最高位
    • SF仅对有符号数起作用
  • CF

    • CF(Carry Flag)
    • 记录 无符号 运算中, 运算结果最高有效位向更高位的进位或借位
      • 有进位或借位, CF = 1
      • 无进位或借位, CF = 0
  • OF

    • OF(Overflow Flag)
    • 记录 有符号 运算时是否发生溢出
      • 有溢出, OF = 1
      • 无溢出, OF = 0
  • adc

    • 带进位加法
    • adc ax, bx 等效于 (ax) = (ax) + (bx) + CF
    • 利用adc指令可以对任意大数据进行相加运算
  • sbb

    • 带借位减法
    • sbb ax, bx 等效于 (ax) = (ax) - (bx) - CF
    • 利用sbb指令可以对任意大数据进行相减运算
  • cmp

    • 功能相当于减法指令
    • cmp r1/m1, r2/m2
    • 执行后仅影响PSW, 不保存结果
    • cmp对无符号数的比较
      • r1 = r2, ZF = 1
      • r1 != r2, ZF = 0
      • r1 < r2, CF = 1
      • r1 >= r2, CF = 0
      • r1 <= r2, CF | ZF = 1
      • r1 > r2, CF & ZF = 0
    • cmp对有符号数的比较
      • r1 = r2, ZF = 1
      • r1 != r2, ZF = 0
      • r1 < r2, (SF = 1 & OF = 0) | (SF = 0 & OF = 1)
      • r1 >= r2, (SF = 0 & OF = 0) | (SF = 1 & OF = 1)
  • 检测比较结果的条件转移指令

指令    含义              检测标志位
je    jump if equal       ZF = 1
jne   jump if not equal   ZF = 0
jb    jump if below       CF = 1
jnb   jump if not below   CF = 0
ja    jump if above       CF = 0 and ZF = 0
jna   jump if not above   CF or ZF = 0
  • DF标志和串传送

    • DF(Direction flag)
    • 在串处理指令中, 控制si, di的增减
    • DF = 0 (CLD): 每次串处理指令操作后si, di递增
    • DF = 1 (STD): 每次串处理指令操作后si, di递减
    • 串处理指令有movsb, movsw等, 一般和rep配合使用
    • rep指令, 根据cx的值, 重复执行后面的串传送指令
  • pushf和popf

    • pushf: 将标志寄存器的值压入栈
    • popf: 从栈中弹出数据, 送入标志寄存器中
  • 标志寄存器在Debug中的显示

标志位   =1  =0
  ZF     ZR  NZ
  PF     PE  PO
  SF     NG  PL
  OF     OV  NV
  AF     AC  NA
  DF     DN  UP 
  IF     EI  DI
  TF     --  --

内中断

  • 中断

    • 中断是CPU处理外部突发时间的一个重要技术
    • 根据引起中断的原因(中断源), 分为硬件中断和软件中断, 而硬件中断又分为外部中断和内部中断
    • 外部中断一般是由外设发出的中断请求, 大多是可屏蔽中断
    • 内部中断指因硬件出错或运算出错引起的中断, 大多是不可屏蔽中断
    • 软件中断不是真正的中断, 只是可被调用执行的一般程序以及DOS的系统功能调用
    • 中断优先权:
      • 除法错 > 软件中断(Int n) > 溢出中断(IntO)
      • 不可屏蔽中断
      • 可屏蔽中断
      • 单步中断
  • 中断向量表

    • 中断向量表就是中断向量的列表
    • 对于80x86CPU, 中断向量表位于0000:0000-0000:03ff的1024个内存单元
    • CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序入口地址
  • 中断过程

    • 根据中断类型在中断向量表中找到中断例程的入口
    • 设置cs:ip, 使CPU处理中断程序
    • 这个过程由CPU的硬件自动完成, 被称为中断过程
    • 80x80CPU的中断过程:
      • 取得中断类型码
      • 标志寄存器的值入栈(保护标志位)
      • 设置TF = 0, IF = 0
      • cs入栈
      • ip入栈
      • 从内存地址为类型码*4和*4+2的两个字单元中读取中断例程的入口地址设置ip, cs
  • 单步中断

    • CPU执行完一条指令之后, 如果检测到标志寄存器的TF位为1, 则产生单步中断, 引发中断过程
    • Debug提供了单步中断的中断例程, 功能为显示所有寄存器的内容后等待输入命令
    • 使用T命令时, Debug将TF设置为1, 使得CPU在执行这条指令后引发单步中断,执行转去执行上述中断例程

int指令

  • int

    • int n
    • 引发中断过程, 中断类型码为n
    • int指令和call指令类似, 都是调用一段程序
  • BIOS和DOS中断例程的安装过程

    • CPU加电后初始化(cs) = 0ffffh, (ip) = 0
    • ffff:0处有一条跳转指令, CPU执行该指令后, 转去执行BIOS中的硬件系统检测和初始化程序
    • 初始化程序将建立BIOS所支持的中断向量
    • 硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导
    • DOS启动后, 除完成其它工作外, 还将它提供的中断例程装入内存, 并建立相应的中断向量

端口

  • 端口的读写

    • 端口不能用mov, push, pop等内存读写指令
    • 端口的读写指令只有两条, in和out分别用于从端口读取和往端口写入
    • in和out指令中, 只能用ax或al来存放从端口读入的数据或要发送到端口的数据
    • 255号及以下的端口读写, 端口号可以由立即数给出
    • 对256-65535的端口读写时, 用dx存放端口号
  • shl和shr

    • shl(shift left)shr(shift right)
    • 将一个r/m的数据向左/右移位
    • 将最后移出的一位写入CF中
    • 最低/最高位用0补充
    • 如果移动位数大于1时, 必须将移动位数放在cl中

实验:

> 在CMOS RAM中, 存放着当前时间:
> 秒: 00h
> 分: 02h 
> 时: 04h
> 日: 07h
> 月: 08h
> 年: 09h
> 这6个信息的长度都为1个字节
> 这些数据以BCD码的方式存放, 高4位存放十位上的数值, 低4位存放个位
> 70h为地址端口, 存放要访问的CMOS RAM单元的地址
> 71h为数据端口, 存放从选定的CMOS RAM单元中读取的数据或要写入的数据

Think And Solve

外中断

  • 接口芯片和端口

    • 在PC系统的接口卡和主板上, 装有各种接口芯片
    • 这些外设接口芯片内部有若干寄存器, CPU将这些寄存器当做端口来访问
    • 外设的输入不直接送入内存和CPU, 而是送入相关的接口芯片的端口中
    • 输出, 控制指令同理
    • CPU通过端口和外部设备联系
    • CPU执行完当前指令后, 可以检测到发送过来的中断信息, 引发中断过程
  • 外中断信息

    • 可屏蔽外中断:
      • 由数据总线将中断类型码送入CPU(不同于内中断的CPU直接产生)
      • 其余过程同内中断
    • 不可屏蔽外中断:
      • 所有不可屏蔽中断的中断类型码固定为2
      • 标志寄存器的值入栈, IF = 0, TF = 0
      • cs入栈, ip入栈
      • (ip) = (8), (cs) = (0ah)
  • PC机键盘的处理过程

    • 键盘上的每一个键相当于一个开关, 键盘中有一个芯片对每一个开关进行扫描
    • 每按下一个键的时候, 开关接通, 产生一个扫描码(通码), 送入相关接口芯片的寄存器中, 该寄存器的端口地址为60h
    • 松开键时也产生一个扫描码(断码), 也会送入60h
    • 通码与断码均一字节, 其中通码第7位为0, 断码为1
    • 当键盘的输入到达60h时, 会产生int 9号中断, 主要工作有:
      • 读出60h端口的扫描码
      • 如果是字符键, 则将改扫描码和对应ASCII送入BIOS的键盘缓冲区
      • 如果是控制键或切换键, 则将其转变为状态字节写入内存中存储状态字节的单元
      • 对键盘系统进行相关的控制
    • 0040:17单元存储键盘状态字节, 各位记录信息如下:
      • 0: 右shift状态, 置1表示按下
      • 1: 左shift状态, 置1表示按下
      • 2: Ctrl状态, 置1表示按下
      • 3: Alt状态, 置1表示按下
      • 4: ScrollLock状态, 置1表示指示灯亮
      • 5: NumLock状态, 置1表示小键盘输入数字
      • 6: CapsLock状态, 置1表示输入大写字母
      • 7: Insert状态, 置1表示处于删除状态

实验:

> 在屏幕中依次显示'a' - 'z', 并可以让人看清
> 在显示过程中, 按下Esc键后改变颜色

Think And Solve

直接定址表

  • 描述了单元长度的标号

    • 之前的程序中, 用到的标号(如code, start, s等)都仅表示了内存单元的地址
    • 还有一种标号, 不但能表示内存单元的地址, 还表示了内存单元的长度
    • 标号后不加':'即可, 例如: a db 1, 2, 3
    • 此时, 标号a描述了该数据所在地址的同时, 还描述了这个地址以后的内存单元都是字节型
    • 因为这种标号包含了对长度的描述, 所以在指令中它可以代表一个段中的内存单元
    • 如: mov al, a , 即将字节型数据1送入al
    • 同时, 它仍然还作为偏移地址使用
    • 如: mov al, a[2] , 即将字节型数据3送入al
    • 这种标号被称为数据标号
  • 在其它段中使用数据标号

    • 一般将数据标号放在data段中使用, 使用assume伪指令将data段与ds寄存器相关联
    • 后面带有':'的标号(即单纯的地址标号), 仅能在代码段中使用
  • 直接定址表

    • 利用数据标号和多种寻址方法相组合, 可以完成"查表"操作
    • 具体概念与实现类似于高级语言
⚠️ **GitHub.com Fallback** ⚠️