一、中断概念与历史:从轮询到事件驱动的进化

大家好,我是你们的嵌入式系统讲师。今天咱们聊聊中断——这个让计算机真正“活”起来的技术。

说实话,我刚入行那会儿,对中断的理解也就停留在“按个键CPU会停一下”这种程度。直到有一次,我在做一个工业控制项目,CPU要同时处理温度采集、电机控制和串口通信……用轮询的方式,程序写得像一团乱麻,还经常丢数据。后来改用中断,整个世界清静了。

1.1 中断的起源:一个偶然的“打断”

中断这个概念,最早可以追溯到20世纪50年代。那时候的计算机,工作方式特别“死板”——程序一条接一条地执行,外部设备想跟CPU说句话?对不起,请排队等着。

我记得有个经典故事:早期计算机的输入输出,靠的是CPU不断去“问”外设——“你准备好了吗?”“数据来了吗?”这种模式叫轮询(Polling)。你想想看,CPU大部分时间都在空转,效率低得可怜。

1956年,IBM的工程师们在设计IBM 704计算机时,遇到了一个实际问题:磁带机读取数据的速度远慢于CPU,CPU如果一直等着,太浪费了。于是他们想了个办法——让磁带机在准备好数据后,主动“通知”CPU。这个“通知”机制,就是中断的雏形。

核心要点:中断的诞生,本质上是为了解决CPU与外设之间的速度不匹配问题。CPU跑得飞快,外设慢吞吞,与其让CPU干等,不如让它去干别的活,等外设准备好了再回来处理。

1.2 中断的定义:到底什么是“中断”?

用大白话说,中断就是:CPU正在执行程序时,突然收到一个“紧急信号”,于是暂停当前工作,转去处理这个信号对应的任务,处理完后再回来接着干。

嗯,这里要注意几个关键点:

  • 暂停当前工作:CPU必须保存当前的状态(比如程序计数器、寄存器内容),否则回来就找不到北了。
  • 转去处理紧急任务:这个任务叫“中断服务程序”(ISR, Interrupt Service Routine)。
  • 返回继续执行:处理完后,CPU恢复之前的状态,从暂停的地方继续执行。

说白了,中断就像你正在写代码,突然电话响了。你记下当前写到哪一行(保存现场),接电话(处理中断),挂电话后继续写(恢复现场)。

个人经验:我在调试一个STM32项目时,曾经犯过一个低级错误——在中断服务程序里写了延时函数。结果中断一触发,CPU卡在ISR里出不来,整个系统像死机一样。后来才明白:中断服务程序要“短平快”,千万别在里面做耗时操作。

1.3 中断在计算机系统中的作用:为什么它如此重要?

中断的作用,我总结为以下四点。这四点是我在实际项目中反复体会到的:

作用 说明 实际案例
提高CPU利用率 CPU不用一直等外设,可以干别的活 串口接收数据时,CPU可以继续算数,数据到了再处理
实时响应 紧急事件能立刻得到处理 按键按下、电机过流保护,必须立即响应
多任务支持 操作系统靠中断实现任务切换 FreeRTOS的SysTick中断,用来产生时间片
错误处理 硬件异常时能及时捕获并处理 除零错误、内存访问越界,触发异常中断

你想想看,如果没有中断,CPU只能轮询。轮询的坏处是什么?

  • CPU被占满,啥也干不了
  • 响应延迟不确定,轮询间隔设短了浪费CPU,设长了丢数据
  • 程序结构复杂,每个外设都要写一个轮询循环

我曾经在一个项目中,用轮询方式同时处理4个串口。程序写出来像意大利面条,调试时想死的心都有。后来全部改成中断驱动,代码清晰了,CPU负载从95%降到了30%。

1.4 中断的两种基本类型

中断按触发方式,主要分两类:

  • 硬件中断:由外部硬件设备触发,比如按键、定时器、串口接收等。这是嵌入式系统中最常用的。
  • 软件中断:由程序主动触发,比如ARM的SWI指令、x86的INT指令。操作系统常用它来实现系统调用。

嗯,这里有个容易混淆的点:异常(Exception)和中断有什么区别?

简单说:异常是CPU内部产生的“意外”,比如除零、非法指令;中断是外部设备发起的“请求”。但在很多嵌入式处理器(比如ARM Cortex-M)中,它们统一用“异常向量表”来处理,所以有时候会混着叫。

避坑指南:我曾经在移植uC/OS-II时,发现任务切换总是不正常。查了两天才发现,是中断优先级配置错了——一个低优先级的中断把高优先级的中断给屏蔽了。记住:中断优先级不是随便设的,要仔细看芯片手册的优先级分组规则。

1.5 中断处理的核心流程

为了让大家更直观地理解,我画了一张流程图:

中断处理核心流程 主程序正常执行 中断事件发生 保存现场(压栈) 查找中断向量表 执行中断服务程序(ISR) 恢复现场(出栈)→ 返回主程序 关键说明 1. 中断由硬件自动触发 2. 保存现场由CPU自动完成 3. 向量表地址由芯片决定 4. ISR要短小精悍 5. 恢复现场后继续执行 常见错误: - ISR里用延时 - 忘记清除中断标志 - 优先级配置混乱

这张图展示了中断处理的完整生命周期。从主程序执行,到中断触发,再到保存现场、查向量表、执行ISR,最后恢复现场返回。每一步都是硬件和软件协同完成的。

1.6 一个简单的代码示例

以STM32的外部中断为例,看看实际代码怎么写:

// 外部中断初始化
void EXTI0_Init(void)
{
    // 1. 使能GPIO时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    
    // 2. 配置PA0为输入模式
    GPIOA->CRL &= ~(0x0F << 0);  // 清除配置
    GPIOA->CRL |= (0x08 << 0);   // 上拉/下拉输入
    
    // 3. 使能AFIO时钟
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
    
    // 4. 配置EXTI0中断线
    AFIO->EXTICR[0] &= ~AFIO_EXTICR1_EXTI0;
    AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0_PA;
    
    // 5. 配置EXTI触发方式(下降沿触发)
    EXTI->FTSR |= EXTI_FTSR_TR0;
    
    // 6. 使能EXTI中断
    EXTI->IMR |= EXTI_IMR_MR0;
    
    // 7. 配置NVIC优先级
    NVIC_SetPriority(EXTI0_IRQn, 2);
    NVIC_EnableIRQ(EXTI0_IRQn);
}

// 中断服务程序
void EXTI0_IRQHandler(void)
{
    if(EXTI->PR & EXTI_PR_PR0)
    {
        // 处理中断事件
        // ... 你的业务逻辑 ...
        
        // 清除中断标志位(这一步千万别忘!)
        EXTI->PR = EXTI_PR_PR0;
    }
}

个人经验:看到那个清除中断标志位的操作了吗?我曾经在这个地方栽过跟头。有一次调试,中断只触发了一次,之后就再也不进了。查了半天,发现是忘了清标志位。不清标志位,CPU会以为中断一直存在,但又不重新触发,结果就是“死锁”。记住:进中断第一件事是清标志,或者出中断前清标志,看具体芯片要求。

1.7 中断的优先级与嵌套

实际系统中,中断不是孤立存在的。多个中断同时发生时,谁先处理?这就引出了中断优先级的概念。

大多数嵌入式处理器支持中断嵌套:高优先级的中断可以打断低优先级的中断服务程序。比如:

  • 定时器中断(优先级高)正在执行
  • 串口中断(优先级低)来了,先等着
  • 定时器中断处理完,再处理串口中断

但要注意:同优先级的中断不能互相打断。如果两个同优先级中断同时到达,按硬件向量号顺序处理。

避坑指南:我曾经在一个项目中,把所有的中断都设成了相同的优先级。结果两个外设同时触发中断时,系统行为变得不可预测。后来才意识到:关键中断(比如电机过流保护)必须设最高优先级,普通中断(比如按键扫描)设低优先级。优先级分配是系统设计的重要一环。

1.8 小结

中断这个概念,从1956年诞生到现在,已经成了计算机系统的基石。它让CPU从“傻等”变成了“高效响应”,让嵌入式系统能够实时处理各种事件。

我个人觉得,理解中断的关键在于三点:

  1. 中断是异步的——你不知道它什么时候来,但来了必须能处理
  2. 中断是高效的——不用轮询,CPU利用率大幅提升
  3. 中断是有代价的——保存/恢复现场需要时间,ISR要写得精简

嗯,这一章的内容就到这里。中断的历史和基本概念,是后续所有章节的基础。下一章我们会深入中断向量表的具体实现,看看硬件层面是怎么找到中断服务程序的。


可可精品资料库