一口Linux 发表于 2025-3-31 10:52:00

操作系统是如何一步步发明中断机制的?

1960年代初期,你正在开发一个批处理系统,用于自动化处理大量的数据任务。

系统需要频繁地与磁带机和打印机等外部设备交互。然而,这些设备的响应速度远远低于CPU的处理速度。例如,磁带机读取一个数据块需要约100毫秒,而打印机打印一行数据更是需要超过600毫秒。
在等待设备响应的过程中,CPU只能不断地查询设备状态,就像这样:
int poll_count = 0;
// 轮询等待打印机就绪
while (1) {
    poll_count++;
    if (check_printer_status() == PRINTER_READY) {
      send_to_printer(print_data);
      break;
    }
}
这就是所谓轮询,这个示例程序通过不断轮询打印机状态来等待设备就绪,只要打印机不READY你就没有办法跳出这个while循环,这导致大量的计算资源被浪费。
灵感时刻1954年IBM 704的出现给了你灵感,因为这台机器上出现了一种有趣的特性。

IBM 704 具有一个溢出标志位(Overflow Flag, OV),它会在某些算术运算(如加法、乘法等)导致溢出时被设置,程序员可以手动检查这个标志位,并根据需要进行错误处理:
   ADD MQ      // AC = AC + MQ,可能导致溢出
   TOV ERROR   // 如果 OV 标志为 1,则跳转到 ERROR 处理异常
   TRA CONTINUE// 否则继续执行程序
ERROR
   // 错误处理指令
CONTINUE
   // 继续执行其他指令
你看到后想了一下,为什么要程序员自己手写汇编来检查异常呢,实现在CPU硬件层面就好了,出现A错误就跳转到X代码,出现B错误就跳转到Y代码等等,这样程序员只需要编写正常的处理逻辑就好。

以程序除0错误为例:
void test_division() {
    int a = 10;
    int b = 0;// 除数为零
    int result = a / b;// CPU立即触发异常处理
    // 这行代码永远不会执行
    printf("结果是: %d
", result);
}
当CPU执行到除法操作时,它能够立即检测到除数为零的情况,并自动跳转到异常处理程序(提前定义好的),而不是等待程序员自己检查除数是否为零。
中断的发明这种机制给你带了新的启示:实际上这相当于软件出现异常后可以通知CPU去执行一段异常处理逻辑,而且整个过程非常丝滑,因为异常处理逻辑是提前定义好的,CPU能根据异常类型去执行不同的异常处理逻辑。
到这里你灵光乍现,既然软件能通知CPU那么外部设备显然也可以通知CPU。

可以把上述机制应用在外部设备上,为此你进行了如下设计:
[*]硬件层面:外部设备通过特定的信号线连接到CPU[*]信号触发:设备就绪时产生电平变化[*]CPU响应:检测到信号后立即切换到处理程序[*]任务恢复:处理完成后返回原程序继续执行这种设计可以让CPU不再需要主动查询设备状态,而是由设备在就绪时主动通知CPU,从而大大减少了CPU资源的浪费,到这里你发明了中断机制。

中断的实现现在CPU不但能响应软件异常也能响应外部设备,这些统统被称为中断。
只不过来自软件的就被称为软中断,比如除零错误、内存访问违规、系统调用等;来自硬件的就被称之为硬中断,比如I/O设备中断(如打印机、磁盘完成操作)、时钟中断等。
你在自己实现的内核中定义了这些中断类型:
// 中断类型定义
typedefenum {
    // 硬件中断
    INT_PRINTER = 0,    // 打印机中断
    INT_DISK = 1,       // 磁盘中断
    INT_TIMER = 2,      // 时钟中断
    INT_KEYBOARD = 3,   // 键盘中断
   
    // 软件中断
    INT_DIVIDE_BY_ZERO = 4,    // 除零错误
    INT_PAGE_FAULT = 5,      // 页面错误
    INT_SYSTEM_CALL = 6,       // 系统调用
   
    MAX_INTERRUPT_TYPE = 7
} InterruptType;
除此之外你还需要实现中断处理函数,中断处理函数应该能处理所有类型的中断,其本质就是一个函数数组,你将其命名为中断向量表:
// 中断处理函数的类型定义
typedef void (*InterruptHandler)(void);
// 中断向量表结构
typedef struct {
    InterruptHandler handlers;
    bool enabled;      // 中断使能状态
} InterruptVectorTable;
从其定义可以看到:
[*]中断向量表是一个存储中断号与对应中断处理程序入口地址映射的表格。
[*]每个中断号对应一个特定的事件(如硬件中断、系统调用、异常等),中断向量表中的每个条目通常包含:中断处理程序的入口地址、可能还包括其他信息(如中断优先级、状态标志等)。
当发生中断时,CPU使用中断号作为索引,查找中断向量表中的对应条目,从而获取中断处理程序的入口地址,其本质就是:
void handle_interrupt(InterruptVectorTable* ivt, InterruptType type) {
...
ivt->handlers();
...
}
现在CPU不再需要一遍遍检查设备状态而是可以专注于执行正常任务的机器指令,当外部设备需要CPU关注时发起中断信号,然后CPU将跳转到提前定义好的中断处理函数去执行。现在你应该对操作系统的中断机制有所了解了吧。最后推荐一下我写的专栏《深入理解操作系统》,第二版焕新升级,600+精美手绘图、87节精讲,如果你喜欢《计算机底层的秘密》这本书的风格,那么这个专栏不能错过:
页: [1]
查看完整版本: 操作系统是如何一步步发明中断机制的?