Skip to content

02_中断与系统调用

在计算机体系结构中,中断是 CPU 打破当前正常执行流,转而处理突发事件或请求的一种核心机制。按触发源和存在形式,中断严格划分为两大类:软件中断与硬件中断。

1. 软件中断 (Software Interrupt)

由程序员在代码中主动发起,指令本身真实存在于程序的内存空间中。

  • 典型场景: 应用程序向操作系统请求特权服务(系统调用)。

  • 示例: INT 21H(DOS 系统调用)、INT 10H(BIOS 中断)。程序执行到这些明确写在内存中的指令时,主动触发中断流程。

2. 硬件中断 (Hardware Interrupt)

由外部硬件事件异步触发,由 CPU 在两条指令执行的间隙主动发起,中断指令本身并不存在于当前执行流的内存空间中。

  • 典型场景: 外设状态变化(如键盘敲击、定时器到期、网络数据包到达)。

  • 示例:

    • INT 8H:时钟中断(由可编程定时器 PIT 触发,典型频率为每秒 18.2 次)。

    • INT 9H:键盘中断(用户每次按下或松开按键时,由键盘控制器触发)。

硬件中断隐式执行示例:

mov ax, 0      ; 假定这条指令消耗了 1/18 秒
               ; ---> CPU 会在此处隐式插入并调用 INT 8H 时钟中断处理程序。
               ; 注意:调用 INT 8H 的指令并不在用户程序的代码内存中。
mov bx, 1

again:
add ax, bx
               ; ---> 假设用户此时敲击了某个键。
               ; ---> CPU 会在此处隐式插入并调用 INT 9H 键盘中断处理程序。
cmp ax, 100
jb again

二、 中断的底层执行机制 (INT 与 IRET)

无论是软件中断还是硬件中断,CPU 在响应中断时,都会执行一套标准且不可打断的硬件级上下文切换流程。在 16 位实模式下,以 INT n 指令为例,它的本质是一次受控的特权级跳转。

1. INT 指令的核心流程

当 CPU 决定执行 INT n(n 为 0~255 的中断号)时,硬件自动完成以下动作:

  1. 保存标志寄存器: pushf(将标志寄存器 FL 压入堆栈)。

  2. 保存代码段: push cs。

  3. 保存指令指针: push ip(压入中断返回后下一条指令的偏移地址)。

  4. 跳转执行: jmp dword ptr 0:[n*4]。CPU 从固定在物理内存 0x00000 处的中断向量表 (IVT) 中查找到目标地址。中断 n 的向量(即处理程序的 CS:IP)固定保存在 0:[n*4] 的双字内存单元中。

2. IRET 中断返回流程

中断处理程序执行完毕后,必须以 IRET (Interrupt Return) 指令结尾。CPU 在执行 IRET 时自动完成出栈恢复动作,顺序与压栈完全相反:

  1. 恢复指令指针: pop ip。

  2. 恢复代码段: pop cs。

  3. 恢复标志寄存器: popf。

完整机制代码演示:

; --------- 调用方上下文 ---------
mov ah, 2
mov dl, 'A'

; 当 CPU 执行下面的 INT 21H 指令时,自动执行:
; 1. pushf          (保存标志位)
; 2. push cs        (保存当前代码段)
; 3. push offset back (保存下一条指令标号 back 的偏移地址 IP)
; 4. jmp dword ptr 0:[84h]  (21H * 4 = 84H,即跳转到向量表中的目标地址)
int 21h

back:
mov ah, 4Ch
mov al, 0
int 21h

; --------- 中断处理程序上下文 ---------
; 假设 offset int_21h 已保存到 word ptr 0:[84h] 中
; 且 seg int_21h 已保存到 word ptr 0:[86h] 中
int_21h:
    ... ; 执行具体的 21H 中断逻辑

    ; 处理完毕,准备返回
    ; CPU 执行 IRET 时,自动执行:
    ; 1. pop ip   (出栈赋给 IP,即回到 back 标号处)
    ; 2. pop cs   (恢复原 CS)
    ; 3. popf     (恢复原标志寄存器 FL)
    iret

(注:在 32 位/64 位保护模式下,查表机制变为中断描述符表 IDT,压栈内容变为 EFLAGS、CS、EIP,且涉及 Ring 3 到 Ring 0 的权限校验与内核栈切换。)

三、 异常与专用硬件/系统中断

部分中断号被 CPU 硬件架构严格保留,用于处理执行流异常、硬件事件或系统调试。

中断号 名称 触发机制 / 作用
INT 0 除零异常 当执行 DIV 或 IDIV 指令且除数为 0(或商溢出)时,CPU 硬件主动触发。
INT 1 单步/陷阱中断 当标志寄存器中的 TF (Trap Flag) = 1 时,CPU 每执行完一条普通指令,就会自动触发一次 INT 1 中断。专门用于调试器(如 DOS 下的 debug)实现单步跟踪。
INT 3 断点陷阱 唯一拥有单字节机器码 (CC) 的中断。专供调试器使用,用于在程序指令流中安全地插入软件断点,替换原指令以劫持执行流。
INT 4 溢出中断 与特定指令 INTO 配合。若溢出标志位 OF = 1,执行 INTO 会触发 INT 4;若 OF = 0 则该指令无任何效果。
INT 8 时钟中断 核心硬件中断。由主板上的定时器芯片(PIT)高频周期性触发。操作系统利用此中断进行系统计时、更新系统时间以及进行多任务的时间片轮转调度。
INT 9 键盘中断 核心硬件中断。键盘控制器在按键被按下或松开时向 CPU 发送信号触发。处理程序会读取键盘端口数据,将其转换为扫描码/ASCII 码并存入键盘缓冲区。

四、 实模式经典软件中断生态

在 16 位实模式时代,软件中断是应用程序请求系统服务的唯一标准接口。通过向特定寄存器(如 AH)赋值功能号来实现路由。

1. INT 21H:MS-DOS 系统调用核心

进程与内存控制 (31H, 4CH, 62H)

AH 功能 传参 / 前置要求 返回值 / 结果 备注
4CH 安全终止程序 AL = 返回码 (Exit Code) 结束程序,释放内存 任何 .EXE/.COM 程序的标准退出方式,控制权交还 command.com。
31H 驻留退出 (TSR) AL = 返回码



DX = 需保留的内存大小 (以节 Paragraph,即 16 字节为单位)
程序退出,控制权交还 DOS,但保留占用内存 Terminate and Stay Resident。该程序在内存中不会被覆盖,常用于编写后台常驻程序。
62H 获取 PSP 地址 无 BX = PSP 的段地址 程序段前缀 (PSP) 是 DOS 加载程序时在程序首部创建的 256 字节数据区,包含重要的命令行参数、环境变量指针等运行时信息。

字符级 I/O 控制 (01H, 02H, 08H)

AH 功能 传参 返回值 备注
01H 键盘输入(带回显) 无 AL = 字符 ASCII 码 程序会阻塞等待,按下的字符直接显示在屏幕上。
08H 键盘输入(无回显) 无 AL = 字符 ASCII 码 同 01H,但屏幕不显示按键。常用于捕获控制键或编写密码掩码(如捕获输入后手动调用 02H 打印 *)。
02H 屏幕输出单字符 DL = 字符 ASCII 码 光标后移 若输出控制字符(如 0DH 回车,0AH 换行),屏幕物理光标会执行对应动作。

字符串级 I/O 控制 (09H, 0AH)

AH 功能 传参 行为说明
09H 打印字符串 DX = 字符串首地址 DOS 字符串打印机制不认 \0 (00H),只认 $ (24H)。遇到 $ 立刻停止打印。若需在屏幕上显示真实的 $ 符号,必须改用 02H 循环打印。
0AH 缓冲输入字符串 DX = 缓冲区首地址 DX 指向的内存必须严格遵循三段式缓冲结构(见下文详述)。

0AH 输入缓冲区的 3 段式物理结构要求:

  1. 第 0 字节 (入参): 程序员必须预先写入允许输入的最大字符数(包含最后的回车符)。

  2. 第 1 字节 (出参): DOS 写入实际输入的字符数(不含最后的回车符)。

  3. 第 2 字节开始 (数据区): 存放用户输入的真实字符序列,以回车符 0DH 结尾。

    (注意:0AH 接收完成后,不会在末尾自动添加 $。若后续想用 09H 直接打印这段输入,程序员必须通过读取第 1 字节的长度,利用偏移量手动在 0DH 后写入一个 `$。)

2. INT 10H:BIOS 视频显存服务

直接绕过 DOS 与底层显卡硬件通讯,常用于编写无操作系统的引导扇区 (Bootloader) 或复古图形程序。

AH 功能 传参 / 前置要求 备注
00H 设置显示模式 AL = 模式号 屏幕会被清空。03H 为标准 80x25 文本模式;13H 为 320x200 256 色 VGA 图形模式。
02H 设置光标位置 BH = 页码



DH = 行(Y)



DL = 列(X)
文本模式下将物理光标定位于特定坐标,屏幕左上角为 (0,0)。
0EH 电传(TTY)输出 AL = ASCII 码



BH = 页码
Bootloader 打印字符串最常用的底层依赖。硬件会自动接管光标右移与屏幕底部的上滚逻辑。
13H 显示格式字符串 传参极多(涉及 AL, BH, BL, CX, DH, DL 以及 ES:BP 指向字符串首址) 支持输出带颜色属性的字符串。不依赖 $ 截断,而是通过 CX 明确指定长度,但由于占据大量寄存器,调用成本较高。

五、 系统调用的演进 (32/64位)

随着操作系统的发展和保护模式的普及,传统的通过 INT n 进行系统调用的方式由于性能开销过大被现代操作系统抛弃。

1. 传统 INT 调用的性能瓶颈

在保护模式下,执行 INT 指令触发特权级切换(从用户态 Ring 3 到内核态 Ring 0)需要极其繁琐的内存交互:

  1. CPU 必须去物理内存中读取并解析 IDT 表(中断描述符表)。

  2. 进行复杂的权限安全检查(对比 DPL 与 CPL)。

  3. 强制切换堆栈(从用户栈切换到内核栈),并在新栈中压入大量上下文状态数据。

    这种内存访问和权限校验消耗了数百个时钟周期,对于高频调用的接口(如网络收发、文件高频读写)构成了极大的性能瓶颈。

2. 过渡方案:Linux 32 位的 INT 80H

在早期的 32 位 Linux 中,全系统统一使用 INT 80H 作为系统调用唯一入口。

  • 机制: EAX 存放系统调用号(Syscall Number,如 4 代表 sys_write)。EBX, ECX, EDX, ESI, EDI, EBP 依次存放最多 6 个参数。

  • 现状: 目前仅为了兼容老旧的 32 位可执行程序而保留。

3. 现代方案:快速系统调用 (Fast System Call)

为了彻底绕过内存中的 IDT 表查表流程,Intel 和 AMD 在 CPU 硬件微架构内部引入了专用的 特定模块寄存器 (MSR, Model Specific Registers) 和全新的指令集:

  • x86 (32位) 方案:SYSENTER / SYSEXIT (Intel 提出)。

  • x64 (64位) 标准:SYSCALL / SYSRET (AMD 提出,现已成为 x86-64 的绝对通用标准)。

SYSCALL 的核心机制与优势:

  1. 绝对的零内存查找: 操作系统内核在引导启动时,预先将系统调用的 Ring 0 入口地址硬编码烧录到了 CPU 内部的高速专用寄存器(MSRs)中。

  2. 极速切换: 当用户态程序执行 SYSCALL 指令时,CPU 硬件直接从 MSR 寄存器中提取内核入口和目标代码段。不查 IDT 表,跳过繁琐的特权级安全比对流程,瞬间切入 Ring 0。

  3. 纯寄存器传参: x64 Linux 中,系统调用号存入 RAX,参数严格按照 RDI, RSI, RDX, R10, R8, R9 的顺序存放于通用寄存器中,避免了压栈传参的内存访问开销。

  4. 性能跃升: 单次系统调用的硬件时钟周期消耗,从 INT 80H 时代的数百个,暴降至几十个,极大地提升了现代操作系统的整体 I/O 吞吐能力。