1. 中断基础概念
大家好,我是老李。做嵌入式Linux开发这么多年,中断这个话题我几乎天天都在打交道。今天咱们就来聊聊中断的那些基础概念。别小看这些基础,我见过不少工程师在这上面栽跟头。
1.1 中断的定义
中断,说白了就是CPU正在干一件事,突然被另一件事打断,先处理完紧急的事,再回来继续干原来的活。
举个例子。你正在写代码,突然电话响了。你放下键盘接电话,接完再继续写。这就是中断。电话铃声就是中断信号,接电话就是中断处理程序。
在计算机里,中断是硬件通知CPU的一种机制。外设(比如网卡、键盘、定时器)通过中断告诉CPU:我有事找你,快来处理一下。
核心要点:中断是一种异步事件处理机制。CPU不需要一直轮询外设状态,外设主动通知CPU。
1.2 中断与轮询的区别
很多初学者会问:为什么非要用中断?轮询不行吗?
轮询就是CPU不停地问外设:你好了没?你好了没?你好了没?
中断是外设主动喊CPU:我好了,快来!
我做过一个项目,用轮询方式读取传感器数据。CPU占用率高达80%,系统几乎干不了别的。后来改成中断驱动,CPU占用率降到5%。差距就这么大。
| 对比项 | 中断 | 轮询 |
|---|---|---|
| CPU利用率 | 高(只在需要时处理) | 低(一直占用CPU) |
| 响应延迟 | 快(毫秒级甚至微秒级) | 取决于轮询间隔 |
| 实现复杂度 | 较高(需要配置中断控制器) | 简单(循环读取状态) |
| 适用场景 | 事件频率低、实时性要求高 | 事件频率高、实时性要求低 |
我的经验:如果外设事件频率超过每秒1000次,可以考虑用轮询或混合模式。中断太频繁反而会拖垮系统。
1.3 中断上下文
这个概念很重要。Linux内核把执行环境分成两种:进程上下文和中断上下文。
进程上下文:可以睡眠、可以调度、可以访问用户空间。说白了就是正常的代码执行环境。
中断上下文:不能睡眠、不能调度、不能访问用户空间。为什么?因为中断处理程序不是以进程为单位运行的,它没有进程控制块(PCB)。
我曾经犯过一个错误:在中断处理函数里调用了kmalloc(GFP_KERNEL)。这个函数可能会睡眠,结果系统直接死锁了。排查了整整两天才找到原因。
避坑指南:中断上下文里绝对不要调用可能睡眠的函数。比如:
- kmalloc(GFP_KERNEL) → 应该用GFP_ATOMIC
- mutex_lock() → 应该用spin_lock()
- copy_from_user() → 这个根本不能用
中断上下文又分为上半部和下半部。上半部处理紧急的硬件操作,下半部处理耗时的逻辑。这个后面会详细讲。
1.4 中断号与中断向量
这两个概念容易混淆。我简单解释一下。
中断号:硬件给每个中断源分配的唯一编号。比如网卡的中断号是17,键盘是1。在Linux里,中断号是软件层面的概念,用int类型表示。
中断向量:CPU硬件层面的概念。它是一个地址表,每个表项指向一个中断处理程序的入口地址。x86架构的中断向量表有256个条目。
举个例子。你打电话给客服,先按1(中断号),然后系统根据1找到对应的客服人员(中断向量)。
在ARM架构里,中断向量表通常放在0x00000000或0xFFFF0000地址。Linux内核启动时会重新设置中断向量表。
// Linux内核中注册中断的典型代码
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
// 处理中断
printk("Interrupt %d occurred\n", irq);
return IRQ_HANDLED;
}
// 注册中断
int ret = request_irq(IRQ_NUMBER, my_irq_handler,
IRQF_TRIGGER_RISING,
"my_device", dev_id);
if (ret) {
printk("Failed to request IRQ %d\n", IRQ_NUMBER);
return ret;
}
关键区别:
- 中断号是软件标识,由内核管理
- 中断向量是硬件机制,由CPU架构定义
- Linux通过irq_desc数组管理所有中断号
- 中断号到中断向量的映射由中断控制器完成
知识体系总览
下面这张图展示了中断基础概念的核心关系。我画了好几个版本,这个最清晰。
嗯,以上就是中断基础概念的核心内容。我在实际项目中反复验证过这些知识点。记住一句话:中断是嵌入式系统的灵魂,理解中断就是理解系统如何响应外部世界。
个人建议:刚开始学中断时,不要急着看复杂的中断控制器驱动。先搞懂这几个基础概念,然后用一个简单的GPIO中断实验练手。我当年就是这么过来的。