跳转至

中断处理

概述

中断是嵌入式系统响应外部事件的核心机制,正确的中断处理对系统实时性和稳定性至关重要。

中断基本概念

中断类型

类型 说明 示例
外部中断 外设触发的中断 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
1
2
3
4
5
6
7
8
9
// 设置优先级分组
// 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
1
2
3
4
5
6
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
1
2
3
4
// 使用中断聚合
// 例如:网络驱动的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
1
2
3
4
# 设置中断在哪个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
1
2
3
4
5
# 查看中断计数
cat /proc/interrupts

# 查看每个CPU的中断
cat /proc/irq/*/smp_affinity

追踪中断

Bash
1
2
3
4
# 使用ftrace
echo function > /sys/kernel/tracing/current_tracer
echo irq_handler_entry > /sys/kernel/tracing/set_event
cat /sys/kernel/tracing/trace

参考资料