> For the complete documentation index, see [llms.txt](https://legacy.cookielau.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://legacy.cookielau.com/archives/5-junior2/5-x86programming/6-interrupt.md).

# System Interruption

## BIOS 调用

* **在主板内存较高地址区内的ROM**(只读存储器)中,固化了PC机的基本输入/输出系统BIOS(Basic Input/Output System)。 BIOS 提供了系统加电自检、引导并装人操作系统、主要I/O设备的控制等功能。主要的外部设备,如键盘、显示系统、磁盘(软盘和硬盘)、打印机、系统时钟等,都有相应的中断调用。主要的BIOS中断类型如下表所示。

| 中断号 |     功能     | 中断号 |    功能   |
| :-: | :--------: | :-: | :-----: |
|  0H |    除法出错    | 10H |   显示器   |
|  1H |    单步中断    | 11H |   设备校验  |
|  2H |   不可屏蔽中断   | 12H |  内存大小检查 |
|  3H |    断点中断    | 13H |    磁盘   |
|  4H |    溢出中断    | 14H |   异步通信  |
|  5H |   打印屏幕中断   | 15H | I/O系统扩充 |
|  8H | 8254系统定时中断 | 16H |    键盘   |
|  9H |    键盘中断    | 17H |   打印机   |
|  BH |  异步通信串口1中断 | 18H |  驻留BIOS |
|  CH |  异步通信串口0中断 | 19H |    引导   |
|  DH |    硬件中断    | 1AH |    时钟   |
|  EH |    软盘中断    | 1BH | 键盘Break |
|  FH |   并行打印机中断  | 1CH |   定时器   |

* 左侧栏中的中断号都小于10H，为系统的**硬中断**，所谓硬中断,是由I/O硬件事件触发的,一般不由程序调用(尽管可以调用它们)。这类中断提供的是真正面向I/O硬件的中断例程。 &#x20;
* 右侧栏中的中断号大于等于10H的，为**软中断**,它不由I/O硬件事件触发,是供系统软件和用户程序调用的一组功能。 &#x20;
* 一些主要的I/O设备，如键盘、显示器、打印机、磁盘、异步通信口、时钟等都**拥有这两种中断**。 &#x20;

## DOS中断调用

* DOS是磁盘操作系统(Disk Operating System)的简称.DOS是由BIOS在开机后装入内存的,它提供了任务管理、设备管理、用户界面管理、文件管理等各种服务。
* 本来DOS是一套标准的软件,不会产生什么中断。但DOS借用了BIOS用软中断提供功能调用的方法,将它为编程者准备的API(应用程序编程接口)都以中断调用方式来提供。这些功能调用也不仅仅是1/O设备的功能调用。
* **大多数为用户提供的功能调用都位于软中断21H中**,但还有其他一些DOS中断调用。主要的DOS中断调用如下表所示。

|    中断号   |       功能       |
| :------: | :------------: |
|    20H   |      程序终止      |
|    21H   |   主要的DOS功能调用   |
|    22H   |      结束地址      |
|    23H   | Ctrl+Break出错地址 |
|    24H   |     严重出错处理     |
|    25H   |      绝对磁盘读     |
|    26H   |      绝对磁盘写     |
|    27H   |     终止并驻留内存    |
| 28H\~3EH |   DOS内部使用的中断   |
|    2FH   |    补充的DOS中断    |
| 30H\~3FH |     保留给DOS     |

* 对编程者来说，使用的主要是21H中断调用。
* DOS在21H中断调用中给出了绝大多数用户编程所需的功能,所以有时也将对21H的调用称为DOS功能调用。
* 对DOS21H功能的调用,只需将**AH**置成功能号,其他寄存器置上该功能号所规定的参数,即可完成所需的功能。
* 例如,要从应用程序中返回DOS的功能调用方法是:

  ```
  MOV AH, 4CH
  MOV AL, 返回码
  INT 21H
  ```

  平时我们直接使用 `MOV AX, 4C00H` 就是返回码为0的返回到DOS。 &#x20;

## 键盘I/O调用

* 这里大部分使用栗子进行说明。

### IBM键盘的扫描码表

![IBM键盘的扫描码表](https://tva1.sinaimg.cn/large/007S8ZIlgy1gfta1dy9f2j30zq0qgalj.jpg)\
可以看出该表和我们的键盘是对应的（标准Windows的键盘)，从最左上角的ESC开始编号直到最后的Shift(右)都是可以根据键盘的位置直接读数确定的。

### 键盘缓冲区

* 用户每按下一个键，都会产生一个**键盘中断**(如果允许中断的话)。
* 键盘的中断处理程序会根据用户按下的键,决定是否求出所按键的ASCII码,否则,将只给出该键的扫描码。
* 由于所按键并不会马上被用户程序读取,所以在BIOS的参数区中,定义了一个键盘输人缓冲区KB\_ BUFFER。它的位置在内存0040: 001A处,结构如下:

|     地址    |        属性       |     空间定义     |    说明    |
| :-------: | :-------------: | :----------: | :------: |
| 0040:001A |    BUFF\_HEAD   |     DW ?     |    首指针   |
| 0040:001C |    BUFF\_TAIL   |     DW ?     |    尾指针   |
| 0040:001E |    KB\_BUFFER   | DW 16 DUP(?) | 16个字的缓冲区 |
| 0040:003E | KB\_BUFFER\_END |  LABEL, WORD |    尾地址   |

上述缓冲区是一个**先进先出的循环队列**,BUFF\_HEAD及BUFF\_TAIL是缓冲区的两个指针。这两个指针相等,缓冲区为空。程序调用BIOS获取键盘输人时,BIOS就会从缓冲区中取出内容,同时移动指针。缓冲区满,会响铃告警。

### 键盘状态及键盘状态字节

* 用户按下Shift ,Ctrl, Alt ,NumLock ,Scroll,Ins及CapsLock键时, BIOS并不返回扫描码, 而是置上一种状态。
* 程序可通过查询状态字节获知这些键的状态。
* 利用 INT 16H 的 AH=2 功能可以返回此状态字节。 &#x20;
* 下图为键盘状态对应的字节：

![键盘状态字节](https://tva1.sinaimg.cn/large/007S8ZIlgy1gftannusukj30qq0fw0w5.jpg)

#### BIOS键盘功能调用

BIOS提供给键盘的调用为16H，有三个功能，分别为：

| AH号 |    功能   |                                 结果                                |                       说明                       |
| :-: | :-----: | :---------------------------------------------------------------: | :--------------------------------------------: |
|  0  |   从键盘读  |            <p>AH为键盘扫描码<br>AL为对应的ASCII(如果可以转换为ASCII)</p>           |         <p>该功能为强制读<br>若用户没有按键则一直等待</p>         |
|  1  |  从缓冲区读  | <p>ZF=0时：AH=键盘扫描码，AL=对应的ASCII或0<br>ZF=1时：表示无键被按下，KB\_BUFFER为空</p> | <p>该功能为探测读<br>若用户已按键，则与0相同<br>若用户未按键，则直接返回</p> |
|  2  | 取键盘状态字节 |                             AL=键盘状态字节                             |                   参见上图的键盘状态字节                  |

实用栗子：

* 输入汉字：

  ```
    hanzibuff dw ?
    mov ah, 0
    int 16h
    cmp al, 0
    jz exit
    test ah, 80h
    jz exit ;输入后ah首位为0，al为0才是汉字
    mov byte ptr hanzibuff, ah
    mov ah, 0
    int 16h
    mov byte ptr hanzibuff+1, ah
    jmp exit
  ```
* 在程序运行过程中检测是否有键按下：

  ```
    ...
    mov ah, 1 ;从缓冲区读
    int 16h
    jz continue
    cmp ah, xx
    ; do something
  ```

  在一个长时间运行的程序中，可以加入上面的语句序列控制程序的运行，比如按下ESC时终止程序运行，则将 `cmp ah,xx` 中 xx 替换为 01 即可。 &#x20;

#### DOS键盘功能调用

BIOS使用的是16h进行调用，DOS的21h也为键盘提供了一些列的功能如下：

|  AH |            功能说明            |        输入参数       |                返回结果                |
| :-: | :------------------------: | :---------------: | :--------------------------------: |
|  1  |          从键盘读入并回显          |                   |                AL=字符               |
|  6  |            测试读键盘           |      DL=0FFH      |             AL=字符(如有输入)            |
|  7  |        从键盘输入一个字符并不回显       |                   |                AL=字符               |
|  8  | 从键盘输入一个字符并不回显，检测Ctrl+Break |                   |                                    |
|  A  |           读字符至缓冲区          |      DS:DX=缓冲     |             字符及计数已经设置好             |
|  B  |            读键盘状态           |                   | <p>AL=0FFH(有输入)<br>AL=00H(无输入)</p> |
|  C  |       清除键盘缓冲区并调用一种功能       | AL=功能号(1,6,7,8,A) |                                    |

下面以栗子来说明A和C功能：

* 清除键盘缓冲区：

  **注意是键盘的缓冲区，不是设置的DS:DX内存的缓冲区**

  ```
  mov ah, 0ch ; 清除缓冲区
  mov al, 08h ; 并再读一个字符
  int 21h
  ```
* 读入字符串，实现将其中的所有小写转大写的功能

  ```
    ------------------------------------------
    data:
    len equ 121
    in_buf db len-1 ; 第一个byte：最多能够输入的字符长度
           db ? ; 第二个byte：实际输入的字符长度(空格分割)
           db len dup(?) ; 缓冲区存储字符空间
    ------------------------------------------
    code: 
    buf_read:
        mov dx, offset in_buf
        mov ah, 0ah
        int 21h

        mov cl, in_buf+1 ; +1可以使得出循环的时候直接在'$'的位置
        xor ch, ch
        mov si, offset in_buf+2
    lp1:
        mov al, [si]
        cmp al, 'a' ; 小于'a'不是小写字母
        jb lp2
        cmp al, 'z' ; 大于'z'不是小写字母
        ja lp2
        add al, 'A'-'a' ; 小写转大写
        mov [si], al ; 存回内存

    lp2:
        inc si ; 字符串指针+1
        loop lp1

        mov byte ptr[si], '$' ; si指向末尾的下一个，直接写入字符串终止符

        mov ah, 2
        mov dl, 0dh ; 回车
        int 21h
        mov ah, 2
        mov dl, 0ah ; 换行
        int 21h

        mov dx, offset in_buf+2 ; in_buf+2 为字符串首地址
        mov ah, 09h ; 输出以'$'为结尾的字符串
        int 21h

        jmp exit
  ```

  * 运行实例：
    * in\_buf 位于 DS:46，输入 `huiH231-`
    * 第一个byte为78H，十进制为120&#x20;
    * 第二个byte为08H，为实际输入的字符个数
    * in\_buf+2 开始为实际存储，如果要直接输出需要在末尾加'$'

      ![image-20200615215940211](https://tva1.sinaimg.cn/large/007S8ZIlgy1gftbkwnzeoj30z60gsdgp.jpg)
