内中断

产生

中断源

  1. 除法错误
  2. 单步执行
  3. 执行 into 指令
  4. 执行 int 指令

    中断类型码

    中断类型码表示几号中断源

  5. 除法错误:0

  6. 单步执行:1
  7. into 指令:4
  8. int 指令:int n(n为中断类型码)

    向量表

    根据 8 位中断类型码,在中断向量表中查找中断处理程序的入口地址
    8086CPU 最多支持256个中断源
    中断向量表存放在内存地址0000:0000~0000:03FF处,一个表项包含中断处理程序的段地址和偏移地址,因此占用4个字节
    高地址存放段地址,低地址存放偏移地址
    中断源从0号开始
    image.png

    过程

  9. 获取中断类型N

  10. pushf标志寄存器入栈
  11. TF=0``IF=0(TF=1就会产生单步中断,因此处理中断的时候要将TF=0避免中断程序执行时陷入无限中断中)
  12. push CS
  13. push IP
  14. 获取中断处理程序内存地址([N×4+2]:[N×4])
  15. 执行中断处理程序

    iret 指令

    用于恢复中断处理程序前的状态,等价于
    1. pop IP
    2. pop CS
    3. popf

    除法中断处理

    ``` assume cs:code

code segment start:mov ax,1000h mov bl,1 div bl

  1. mov ax,4c00h
  2. int 21h

code ends

end start

  1. 上面的程序会产生除法溢出,因为除数是 8 位的,因此结果保存在`ax`里,其中商`1000h`保存在`al`里,出现溢出,余数`0`保存在`ah`里,无溢出
  2. <a name="iXZuI"></a>
  3. ### 处理 0 号中断程序

assume cs:code

code segment

start:mov ax,cs mov ds,ax mov si,offset do0

  1. mov ax,0
  2. mov es,ax
  3. mov di,200h
  4. mov cx,offset do0end - offset do0 ; 获取 do0 程序的字节数
  5. cld
  6. rep movsb ; do0 程序送入内存地址 0000:0200
  7. ; 设置中断向量表连接到 0000:0200
  8. mov ax,0
  9. mov es,ax
  10. mov word ptr es:[0*4],200h
  11. mov word ptr es:[0*4+2],0
  12. mov ax,4c00h
  13. int 21h
  14. ; 以下都是 do0 程序
  15. do0:jmp short do0start
  16. db 'overflow!'

do0start:mov ax,cs mov ds,ax mov si,202h ; jmp 占两个字节, 202h 链接到字符串的地址

  1. mov ax,0b800h
  2. mov es,ax
  3. mov di,12*160+36*2 ; es:di 指向显存空间的中间位置
  4. ; 把字符串一个个取出来放到显存空间
  5. mov cx,9
  6. s:mov al,[si]
  7. mov es:[di],al
  8. inc si
  9. add di,2
  10. loop s
  11. mov ax,4c00h
  12. int 21h

do0end:nop

code ends

end start

  1. 上述程序解析:
  2. 1. 处理程序`do0`
  3. 1. `do0`送入`0:200`
  4. 1. 将中断向量表`0`号中断源的处理程序地址改为`0:200`
  5. 1. 将字符串`overflow`输入到显存上
  6. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2327383/1642689268161-d76248b4-5dd9-44f3-b778-bafdd799e5bb.png#clientId=ubcf4126b-6445-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=293&id=u5fa8eea3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=644&originalType=binary&ratio=1&rotation=0&showTitle=true&size=10382&status=done&style=none&taskId=u2b4264bb-34dd-4e4d-a3a4-05d940968bc&title=%E7%BB%93%E6%9E%9C%E4%BC%9A%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%98%BE%E7%A4%BA%20overflow&width=429.3333333333333 "结果会在中间显示 overflow")
  7. <a name="KBD5z"></a>
  8. ## 单步中断
  9. 执行完一条指令后,寄存器`TF`被设置成`1`产生单步中断(`1`号中断源),并将寄存器的状态显示在屏幕上,等待下一条指令<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2327383/1642686958381-af7eec27-0591-45a5-86d4-434610f432f7.png#clientId=u9910ed90-4d2b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u1fcea68b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=396&originWidth=2063&originalType=binary&ratio=1&rotation=0&showTitle=true&size=20714&status=done&style=none&taskId=u61ccf450-21b9-4d3f-aa03-c1fc8d8fdfd&title=%E5%8D%95%E6%AD%A5%E4%B8%AD%E6%96%AD%E6%A1%88%E4%BE%8B&width=579.3333740234375 "单步中断案例")
  10. <a name="JBNOq"></a>
  11. ## 特殊情况
  12. 设置`ss``sp`的时候不产生单步中断

mov ax,2000 mov ss,ax mov sp,10 mov bx,1000

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2327383/1642687743202-e87603b9-75d7-4913-ba5a-e28d1890f18c.png#clientId=u9910ed90-4d2b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=231&id=u254b0bbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=539&originWidth=1599&originalType=binary&ratio=1&rotation=0&showTitle=false&size=185709&status=done&style=none&taskId=u73bd5556-6ac8-473a-9d17-2899855ac74&title=&width=685.3333740234375)<br />因此设置`ss`和`sp`应该是连续的指令
  2. <a name="oDfUK"></a>
  3. # int 指令
  4. <a name="dcsh6"></a>
  5. ## 求平方中断程序

assume cs:code

code segment start:mov ax,3456 ; 0D80H int 7ch ; 产生中断, 去获取 ax 的平方, 并且低位存在 ax 里, 高位存在 dx 里 add ax,ax adc dx,dx ; 最后结果 016C8000H mov ax,4c00h int 21h code ends end start

  1. ```
  2. ;中断安装程序
  3. assume cs:code
  4. code segment
  5. start:mov ax,cs
  6. mov ds,ax
  7. mov si,offset sqr
  8. ; 安装程序
  9. mov ax,0
  10. mov es,ax
  11. mov di,200h
  12. mov cx,offset sqrend - offset sqr
  13. cld
  14. rep movsb
  15. ; 修改向量表
  16. mov ax,0
  17. mov es,ax
  18. mov word ptr es:[7ch*4],200h
  19. mov word ptr es:[7ch*4+2],0
  20. mov ax,4c00h
  21. int 21h
  22. sqr:mul ax
  23. iret
  24. sqrend:nop
  25. code ends
  26. end start

转换大写

  1. assume cs:code
  2. data segment
  3. db "conversation",0
  4. data ends
  5. code segment
  6. start:mov ax,data
  7. mov ds,ax
  8. mov si,0
  9. int 7ch
  10. mov ax,4c00h
  11. int 21h
  12. code ends
  13. end start
  1. ;中断安装程序
  2. assume cs:code
  3. code segment
  4. start:mov ax,cs
  5. mov ds,ax
  6. mov si,offset capital
  7. ; 安装程序
  8. mov ax,0
  9. mov es,ax
  10. mov di,200h
  11. mov cx,offset capitalend - offset capital
  12. cld
  13. rep movsb
  14. ; 修改向量表
  15. mov ax,0
  16. mov es,ax
  17. mov word ptr es:[7ch*4],200h
  18. mov word ptr es:[7ch*4+2],0
  19. mov ax,4c00h
  20. int 21h
  21. capital:push si
  22. push cs
  23. change:mov cl,[si]
  24. mov ch,0
  25. jcxz ok
  26. and byte ptr [si],11011111B
  27. inc si
  28. loop change
  29. ok:pop si
  30. pop cx
  31. iret
  32. capitalend:nop
  33. code ends
  34. end start

模拟 loop 指令

image.png

  1. assume cs:code
  2. code segment
  3. start:mov ax,0b800h
  4. mov es,ax
  5. mov di,160*12
  6. mov bx,offset s - offset se ; 注意此时的偏移差是反向偏移
  7. mov cx,80
  8. s:mov byte ptr es:[di],'!' ; ! 送入显存空间
  9. add di,2 ; 地址加一个字
  10. int 7ch ; 跳转到中断程序, 此时入栈的 IP se 的偏移地址
  11. se:nop
  12. mov ax,4c00h
  13. int 21h
  14. code ends
  15. end start
  1. ;中断安装程序
  2. assume cs:code
  3. code segment
  4. start:mov ax,cs
  5. mov ds,ax
  6. mov si,offset lp
  7. ; 安装程序
  8. mov ax,0
  9. mov es,ax
  10. mov di,200h
  11. mov cx,offset lpend - offset lp
  12. cld
  13. rep movsb
  14. ; 修改向量表
  15. mov ax,0
  16. mov es,ax
  17. mov word ptr es:[7ch*4],200h
  18. mov word ptr es:[7ch*4+2],0
  19. mov ax,4c00h
  20. int 21h
  21. lp:push bp
  22. mov bp,sp ; 获取栈顶偏移地址
  23. dec cx ; cx -1
  24. jcxz lpret ; 再判断 cx 是否为 0, 0 则直接跳出中断程序
  25. add [bp+2],bx ; 不为 0 则将 se 的偏移地址 + 之前的偏移差获得 s 的偏移地址
  26. ; [bp] 是原 bp [bp+2] se IP, [bp+4] se CS
  27. lpret:pop bp
  28. iret ; 如果前面的 add 执行了, cs:ip 就指向 s, 实现循环
  29. lpend:nop
  30. code ends
  31. end start

BIOS 中的中断程序

int 10h是包含多个与屏幕输出相关子程序的中断程序

  1. assume cs:code
  2. code segment
  3. mov ah,2 ; 2 号子程序, 设置光标位置
  4. mov bh,0 ; 0
  5. mov dh,5 ; 5
  6. mov dl,12 ; 12
  7. int 10h
  8. mov ah,9 ; 9 号子程序, 在光标位置显示字符
  9. mov al,'a' ; 显示的字符
  10. mov bl,11001010B ; BL(闪烁) RGB(背景) I(高亮) RGB(前景)
  11. mov bh,0 ; 0
  12. mov cx,3 ; 字符重复 3
  13. int 10h
  14. mov ax,4c00h
  15. int 21h
  16. code ends
  17. end

DOS 中的中断程序

  1. ;mov ax,4c00h
  2. mov ah,4ch ; 4c 号子程序
  3. mov al,0 ; 返回值
  4. int 21h

光标处显示字符串

  1. assume cs:code
  2. data segment
  3. db 'Welcome to masm','$'
  4. data ends
  5. code segment
  6. start:mov ah,2
  7. mov bh,0
  8. mov dh,5
  9. mov dl,12
  10. int 10h ; 这里设置光标
  11. mov ax,data
  12. mov ds,ax
  13. mov dx,0 ; ds:dx 指向字符串
  14. mov ah,9 ; 21h 的第 9 号子程序
  15. int 21h
  16. mov ax,4c00h
  17. int 21h
  18. code ends
  19. end start

外中断

外设的输入到达后,相关芯片将向CPU发出相应的中断信息,CPU执行完当前指令后就会检测到发送过来的中断信息

可屏蔽中断

IF=1CPU会在执行完当前指令后响应中断,IF=0则不会响应,因此内中断过程中需要将IF设置成0

  1. sti ; 设置 IF=1
  1. cli ; 设置 IF=0

几乎所有的外设中断都是可屏蔽的

键盘输入

按下一个键,开关接通,芯片产生一个扫描码,说明了该键在键盘上的位置,称为通码
松开一个键,也会产生扫描码,称为断码
通码和断码都是一个字节
通码最高位为0断码最高位为1,即通码+80h=断码
主板上相关接口端口地址60h

中断执行过程

  1. 读出60h端口的扫描码
  2. 字符按键,将对应的ASCII码送入BIOS的键盘缓冲区,控制按键(ctrl)或切换按键(CapsLk),转成状态字节,写入内存中存储状态字节的单元(0040:17)

    BIOS键盘缓冲区可存储15个键盘输入 一个键盘输入使用一个存放,高位扫描码,低位字符码(ASCII码)

位置 按键
0 右 shift 1按下
1 左 shift
2 ctrl
3 alt
4 ScrollLock 1指示灯亮
5 NumLock 1键盘输入数字
6 CapsLock 1大写
7 Insert 1删除状态

int9 中断程序

  1. assume cs:code,ss:stack
  2. stack segment
  3. db 128 dup (0)
  4. stack ends
  5. data segment
  6. dw 0,0 ; 这里要保存系统自带 int9 程序的内存地址
  7. data ends
  8. code segment
  9. start: mov ax,stack
  10. mov ss,ax
  11. mov sp,128
  12. mov ax,data
  13. mov ds,ax
  14. mov ax,0
  15. mov es,ax
  16. ; 将自带的 int9 程序内存地址保存起来
  17. push es:[9*4]
  18. pop ds:[0]
  19. push es:[9*4+2]
  20. pop ds:[2]
  21. ; 中断向量表中设置新的 int9 程序
  22. mov word ptr es:[9*4],offset int9
  23. mov es:[9*4+2],cs
  24. mov ax,0b800h
  25. mov es,ax
  26. mov ah,'a'
  27. s: mov es:[160*12+40*2],ah
  28. call delay
  29. inc ah
  30. cmp ah,'z'
  31. jna s ; 如果不等于'z'就继续
  32. mov ax,0
  33. mov es,ax
  34. ; 中断向量表恢复成原来的
  35. ; 不恢复别的程序无法使用键盘
  36. ; 这里也可以使用下面这种写法
  37. ; mov ax,ds:[0]
  38. ; mov es:[9*4],ax
  39. push ds:[0]
  40. pop es:[9*4]
  41. push ds:[2]
  42. pop es:[9*4+2]
  43. mov ax,4c00h
  44. int 21h
  45. delay:push ax
  46. push dx
  47. mov dx,10h ; 循环 10/0000h
  48. mov ax,0
  49. s1: sub ax,1
  50. sbb dx,0
  51. cmp ax,0
  52. jne s1
  53. cmp dx,0 ; 必须 dx ax 都为 0 才退出
  54. jne s1
  55. pop dx
  56. pop ax
  57. ret
  58. int9:push ax
  59. push bx
  60. push es
  61. in al,60h ; 读取60h端口的扫描码
  62. ; 模拟调用原来的 int9 程序
  63. ; 不能直接使用 int9 因为程序入口已经改成新的 int9 程序了
  64. pushf ; 这里要用两次 pushf 因为 pushf 操作一个字节, pop bx 操作两个字节
  65. pushf
  66. pop bx
  67. and bx,11111100B ; 设置 TF=0 IF=0
  68. push bx
  69. popf
  70. call dword ptr ds:[0]
  71. ; 这里其实可以不需要设置 TF=0 IF=0
  72. ; 因为能进入新的 int9 程序, 说明当前处于中断中, TF/IF 已经被设置成 0
  73. cmp al,1 ; esc 扫描码 01
  74. jne int9ret
  75. mov ax,0b800h
  76. mov es,ax
  77. inc byte ptr es:[160*12+40*2+1] ; 属性+1,改变颜色
  78. int9ret:pop es
  79. pop bx
  80. pop ax
  81. iret
  82. code ends
  83. end start

不可屏蔽中断

中断类型码为2,因此CS=0AH``IP=8H