# 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://legacy.cookielau.com/archives/5-junior2/5-x86programming/6-interrupt.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
