中断处理
概述
中断是嵌入式系统响应外部事件的核心机制,正确的中断处理对系统实时性和稳定性至关重要。
中断基本概念
中断类型
| 类型 |
说明 |
示例 |
| 外部中断 |
外设触发的中断 |
GPIO、UART |
| 内部中断 |
CPU内部事件 |
除零、非法访问 |
| 软中断 |
软件触发 |
系统调用 |
中断处理流程
| Text Only |
|---|
| 中断请求 -> 中断响应 -> 保护现场 -> 中断服务 -> 恢复现场 -> 返回
|
ARM Cortex-M中断
NVIC(嵌套向量中断控制器)
| C |
|---|
| #include "core_cm4.h"
// 使能中断
NVIC_EnableIRQ(USART1_IRQn);
// 禁能中断
NVIC_DisableIRQ(USART1_IRQn);
// 设置优先级
NVIC_SetPriority(USART1_IRQn, 5);
// 清除挂起标志
NVIC_ClearPendingIRQ(USART1_IRQn);
// 触发中断(软件触发)
NVIC_SetPendingIRQ(USART1_IRQn);
|
优先级分组
| C |
|---|
| // 设置优先级分组
// NVIC_Group: 0-4
// 主优先级位数: NVIC_Group
// 子优先级位数: 4 - NVIC_Group
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); // 0-15抢占优先级
// 设置中断优先级
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0); // 抢占优先级5, 子优先级0
|
STM32中断编程
外部中断(EXTI)
| C |
|---|
| #include "stm32f4xx_hal.h"
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
// 中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
// 回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0) {
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
// 上升沿
} else {
// 下降沿
}
}
}
|
定时器中断
| C |
|---|
| void TIM_Init(void)
{
TIM_HandleTypeDef htim;
htim.Instance = TIM2;
htim.Init.Prescaler = 8400 - 1; // 84MHz / 8400 = 10kHz
htim.Init.CounterMode = TIM_COUNTERMODE_UP;
htim.Init.Period = 10000 - 1; // 10kHz / 10000 = 1Hz
HAL_TIM_Base_Init(&htim);
HAL_TIM_Base_Start_IT(&htim);
HAL_NVIC_SetPriority(TIM2_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2) {
// 1秒定时到达
}
}
|
Linux中断编程
注册中断处理函数
| C |
|---|
| #include <linux/interrupt.h>
#include <linux/irq.h>
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
// 处理中断
// 注意:必须快速返回
return IRQ_HANDLED; // 中断已处理
// return IRQ_NONE; // 不是我们的中断
}
// 注册中断
int ret = request_irq(
irq, // 中断号
my_interrupt_handler, // 处理函数
IRQF_TRIGGER_RISING, // 标志
"my_device", // 名称
dev // 设备ID(传给处理函数)
);
// 释放中断
free_irq(irq, dev);
|
中断标志
| C |
|---|
| IRQF_TRIGGER_RISING // 上升沿触发
IRQF_TRIGGER_FALLING // 下降沿触发
IRQF_TRIGGER_HIGH // 高电平触发
IRQF_TRIGGER_LOW // 低电平触发
IRQF_SHARED // 共享中断
IRQF_ONESHOT // 硬件中断禁用直到线程处理完
|
线程化中断
| C |
|---|
| static irqreturn_t my_hard_isr(int irq, void *dev_id)
{
// 硬件中断上下文(快速处理)
return IRQ_WAKE_THREAD; // 唤醒线程
}
static irqreturn_t my_thread_fn(int irq, void *dev_id)
{
// 进程上下文(可以睡眠)
// 执行耗时操作
return IRQ_HANDLED;
}
// 注册线程化中断
ret = request_threaded_irq(irq, my_hard_isr, my_thread_fn,
IRQF_TRIGGER_RISING, "my_device", dev);
|
中断下半部
Tasklet
| C |
|---|
| #include <linux/interrupt.h>
void my_tasklet_func(unsigned long data)
{
// 处理数据
}
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
// 在中断处理函数中调度
static irqreturn_t my_handler(int irq, void *dev_id)
{
tasklet_schedule(&my_tasklet);
return IRQ_HANDLED;
}
// 禁用/启用
tasklet_disable(&my_tasklet);
tasklet_enable(&my_tasklet);
// 杀死(等待执行完成)
tasklet_kill(&my_tasklet);
|
Workqueue
| C |
|---|
| #include <linux/workqueue.h>
static struct work_struct my_work;
void my_work_func(struct work_struct *work)
{
// 在进程上下文执行
}
// 初始化
INIT_WORK(&my_work, my_work_func);
// 调度
schedule_work(&my_work);
// 延迟调度
static DECLARE_DELAYED_WORK(my_delayed_work, my_work_func);
schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(100));
// 等待完成
flush_work(&my_work);
flush_delayed_work(&my_delayed_work);
|
软中断
| C |
|---|
| #include <linux/interrupt.h>
void my_softirq_func(struct softirq_action *a)
{
// 处理
}
// 注册
open_softirq(MY_SOFTIRQ, my_softirq_func);
// 触发
raise_softirq(MY_SOFTIRQ);
// 在中断上下文中安全触发
raise_softirq_irqoff(MY_SOFTIRQ);
|
中断上下文注意事项
不能做的事
| C |
|---|
| // ❌ 不能睡眠
// msleep(100);
// wait_event(...);
// down(&sem);
// ❌ 不能调用可能睡眠的函数
// copy_to_user();
// kmalloc(size, GFP_KERNEL); // 用GFP_ATOMIC代替
// ❌ 不能长时间执行
// while (1); // 会阻塞其他中断
|
可以做的事
| C |
|---|
| // ✓ 原子内存分配
void *ptr = kmalloc(size, GFP_ATOMIC);
// ✓ 短时间自旋锁
spin_lock(&lock);
// 临界区
spin_unlock(&lock);
// ✓ 调度下半部
tasklet_schedule(&my_tasklet);
schedule_work(&my_work);
// ✓ 唤醒进程
wake_up(&wait_queue);
|
中断性能优化
减少中断频率
| C |
|---|
| // 使用中断聚合
// 例如:网络驱动的NAPI
// 使用DMA代替中断传输
|
快速处理
| C |
|---|
| // 中断处理函数尽量短
// 耗时操作放到下半部
static irqreturn_t fast_handler(int irq, void *dev_id)
{
// 读取状态寄存器
u32 status = readl(regs + STATUS_REG);
// 清除中断标志
writel(status, regs + CLEAR_REG);
// 调度下半部处理
schedule_work(&my_work);
return IRQ_HANDLED;
}
|
中断亲和性(多核)
| Bash |
|---|
| # 设置中断在哪个CPU上处理
echo 1 > /proc/irq/24/smp_affinity # CPU 0
echo 2 > /proc/irq/24/smp_affinity # CPU 1
echo 3 > /proc/irq/24/smp_affinity # CPU 0或1
|
中断调试
查看中断统计
| Bash |
|---|
| # 查看中断计数
cat /proc/interrupts
# 查看每个CPU的中断
cat /proc/irq/*/smp_affinity
|
追踪中断
| Bash |
|---|
| # 使用ftrace
echo function > /sys/kernel/tracing/current_tracer
echo irq_handler_entry > /sys/kernel/tracing/set_event
cat /sys/kernel/tracing/trace
|
参考资料