Appearance
中断处理机制
中断是计算机系统中一种重要的异步事件处理机制,它允许硬件设备在需要CPU关注时主动通知CPU。Linux内核提供了完善的中断处理框架,支持多种类型的中断处理方式。本章将深入解析Linux内核的中断处理机制。
中断基本概念
什么是中断
中断是指CPU在执行程序过程中,由于硬件或软件的请求而暂停当前程序的执行,转去执行处理该请求的程序,处理完后再回到原来被中断的位置继续执行的过程。
中断类型
- 硬件中断:由硬件设备产生的中断信号
- 软件中断:由软件指令触发的中断
- 异常:由CPU内部事件(如除零错误、缺页异常等)引起的中断
中断处理过程
中断处理通常分为两个阶段:
- 顶半部(Top Half):快速响应中断,执行时间敏感的操作
- 底半部(Bottom Half):执行耗时较长的操作,可以被其他中断打断
Linux中断处理框架
中断描述符
Linux内核使用irq_desc结构体来描述每个中断线:
c
struct irq_desc {
struct irq_data irq_data;
struct irq_chip *chip; // 中断控制器
struct irqaction *action; // 中断处理函数链表
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; // 中断禁用计数
unsigned int wake_depth; // 唤醒使能计数
unsigned int irq_count; // 中断计数
unsigned long last_unhandled; // 上次未处理时间
unsigned int irqs_unhandled; // 未处理中断计数
// ... 其他字段
};中断处理函数
中断处理函数通过irqaction结构体注册:
c
struct irqaction {
irq_handler_t handler; // 中断处理函数
void *dev_id; // 设备标识
struct irqaction *next; // 链表指针
irq_handler_t thread_fn; // 线程化处理函数
struct task_struct *thread; // 内核线程
unsigned long thread_flags; // 线程标志
unsigned long flags; // 中断标志
const char *name; // 设备名称
struct proc_dir_entry *dir; // /proc/irq/目录项
};中断注册与注销
注册中断处理函数
c
#include <linux/interrupt.h>
// 中断处理函数原型
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
// 处理中断
// 读取中断状态寄存器
u32 status = readl(dev->base + STATUS_REG);
// 清除中断标志
writel(status, dev->base + CLEAR_REG);
// 处理具体中断源
if (status & RX_COMPLETE) {
// 处理接收完成中断
// ...
}
if (status & TX_COMPLETE) {
// 处理发送完成中断
// ...
}
return IRQ_HANDLED;
}
// 注册中断
int my_device_init(struct my_device *dev)
{
int ret;
// 申请中断线
ret = request_irq(dev->irq, my_interrupt_handler,
IRQF_SHARED, "my_device", dev);
if (ret) {
pr_err("Failed to request IRQ %d\n", dev->irq);
return ret;
}
pr_info("IRQ %d registered for my_device\n", dev->irq);
return 0;
}注销中断处理函数
c
// 注销中断
void my_device_exit(struct my_device *dev)
{
free_irq(dev->irq, dev);
pr_info("IRQ %d freed for my_device\n", dev->irq);
}顶半部处理
顶半部设计原则
- 快速执行:尽量减少执行时间
- 原子操作:不能睡眠或执行可能引起阻塞的操作
- 临界区保护:必要时禁用中断
顶半部实现示例
c
static irqreturn_t uart_interrupt_handler(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
unsigned int status, pass_counter = 100;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
do {
status = readl(port->membase + UART_STATUS);
if (!(status & UART_IRQ_MASK))
break;
if (status & UART_RX_IRQ) {
// 读取接收数据
unsigned int ch = readl(port->membase + UART_RX);
// 将数据放入环形缓冲区
uart_insert_char(port, ch, TTY_NORMAL);
}
if (status & UART_TX_IRQ) {
// 检查发送缓冲区
if (!uart_tx_empty(port)) {
unsigned int ch = uart_tx_char(port);
writel(ch, port->membase + UART_TX);
} else {
// 禁用发送中断
writel(readl(port->membase + UART_CTRL) & ~UART_TX_IRQ_EN,
port->membase + UART_CTRL);
}
}
// 清除中断标志
writel(status, port->membase + UART_CLEAR);
} while (--pass_counter);
spin_unlock_irqrestore(&port->lock, flags);
// 通知底半部处理
if (status & (UART_RX_IRQ | UART_TX_IRQ))
return IRQ_WAKE_THREAD;
return IRQ_HANDLED;
}底半部处理机制
软中断(Softirq)
软中断是静态分配的底半部机制,具有最高的执行优先级:
c
// 软中断类型定义
enum {
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
// 软中断处理函数
void my_softirq_handler(struct softirq_action *action)
{
// 执行软中断处理
// ...
}
// 注册软中断
static struct softirq_action my_softirq = {
.action = my_softirq_handler,
};
// 在初始化时注册
open_softirq(MY_SOFTIRQ, my_softirq_handler);Tasklet
Tasklet是基于软中断实现的动态底半部机制:
c
// 定义tasklet
static struct tasklet_struct my_tasklet;
// tasklet处理函数
void my_tasklet_handler(unsigned long data)
{
struct my_device *dev = (struct my_device *)data;
// 处理耗时操作
process_received_data(dev);
// 更新统计信息
update_statistics(dev);
pr_debug("Tasklet handler executed\n");
}
// 初始化tasklet
static int __init my_module_init(void)
{
// 初始化tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, (unsigned long)my_dev);
return 0;
}
// 调度tasklet执行
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
// 顶半部处理...
// 调度tasklet在底半部执行
tasklet_schedule(&my_tasklet);
return IRQ_HANDLED;
}
// 清理tasklet
static void __exit my_module_exit(void)
{
// 确保tasklet完成执行
tasklet_kill(&my_tasklet);
}工作队列(Workqueue)
工作队列是最灵活的底半部机制,允许睡眠操作:
c
#include <linux/workqueue.h>
struct my_device {
struct work_struct work;
struct delayed_work dwork;
// 其他设备数据
};
// 工作处理函数
static void my_work_handler(struct work_struct *work)
{
struct my_device *dev = container_of(work, struct my_device, work);
// 可以执行可能睡眠的操作
mutex_lock(&dev->mutex);
// 处理复杂逻辑
process_complex_operation(dev);
// 访问用户空间
if (need_user_access())
access_user_space();
mutex_unlock(&dev->mutex);
pr_debug("Work handler executed\n");
}
// 延迟工作处理函数
static void my_delayed_work_handler(struct work_struct *work)
{
struct my_device *dev = container_of(to_delayed_work(work),
struct my_device, dwork);
// 延迟执行的操作
periodic_task(dev);
// 重新调度延迟工作
schedule_delayed_work(&dev->dwork, msecs_to_jiffies(1000));
}
// 初始化工作
static int my_device_init(struct my_device *dev)
{
// 初始化工作
INIT_WORK(&dev->work, my_work_handler);
INIT_DELAYED_WORK(&dev->dwork, my_delayed_work_handler);
return 0;
}
// 调度工作执行
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
// 顶半部处理...
// 调度工作在底半部执行
schedule_work(&dev->work);
return IRQ_HANDLED;
}
// 调度延迟工作
static void start_periodic_task(struct my_device *dev)
{
schedule_delayed_work(&dev->dwork, msecs_to_jiffies(1000));
}中断线程化
从Linux 2.6.30开始,内核支持中断线程化,允许中断处理函数在内核线程中执行:
c
// 线程化中断处理函数
static irqreturn_t my_threaded_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
// 可以执行耗时操作
// 可以睡眠
msleep(100);
// 处理复杂逻辑
process_data(dev);
return IRQ_HANDLED;
}
// 主中断处理函数(顶半部)
static irqreturn_t my_main_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
// 快速响应中断
// 清除中断标志
writel(0, dev->base + INT_CLEAR_REG);
// 返回IRQ_WAKE_THREAD唤醒线程处理函数
return IRQ_WAKE_THREAD;
}
// 注册线程化中断
int my_device_init(struct my_device *dev)
{
int ret;
// 注册线程化中断处理函数
ret = request_threaded_irq(dev->irq, my_main_handler, my_threaded_handler,
IRQF_SHARED, "my_device", dev);
if (ret) {
pr_err("Failed to request threaded IRQ\n");
return ret;
}
return 0;
}中断亲和性
Linux支持设置中断亲和性,将中断绑定到特定的CPU核心:
c
#include <linux/interrupt.h>
// 设置中断亲和性
int set_irq_affinity(unsigned int irq, unsigned long cpu_mask)
{
struct cpumask mask;
int ret;
cpumask_clear(&mask);
cpumask_set_cpu(cpu_mask, &mask);
ret = irq_set_affinity_hint(irq, &mask);
if (ret) {
pr_err("Failed to set IRQ affinity for IRQ %d\n", irq);
return ret;
}
pr_info("IRQ %d affinity set to CPU %lu\n", irq, cpu_mask);
return 0;
}
// 在设备初始化时设置中断亲和性
static int my_device_init(struct my_device *dev)
{
int ret;
// 注册中断...
// 将中断绑定到CPU 0
ret = set_irq_affinity(dev->irq, 0);
if (ret) {
pr_warn("Failed to set IRQ affinity, continuing anyway\n");
}
return 0;
}中断调试与性能分析
中断统计信息
bash
# 查看系统中断统计
cat /proc/interrupts
# 查看软中断统计
cat /proc/softirqs
# 查看特定中断的详细信息
ls /proc/irq/使用ftrace跟踪中断
bash
# 启用中断跟踪
echo 1 > /sys/kernel/debug/tracing/events/irq/enable
# 查看跟踪结果
cat /sys/kernel/debug/tracing/trace
# 禁用跟踪
echo 0 > /sys/kernel/debug/tracing/events/irq/enable中断性能监控
c
// 在驱动中添加性能统计
struct my_device_stats {
unsigned long interrupts;
unsigned long bytes_received;
unsigned long bytes_transmitted;
ktime_t last_interrupt_time;
};
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
ktime_t now = ktime_get();
// 更新统计信息
dev->stats.interrupts++;
// 计算中断间隔时间
if (dev->stats.last_interrupt_time.tv64) {
ktime_t delta = ktime_sub(now, dev->stats.last_interrupt_time);
// 处理时间间隔统计...
}
dev->stats.last_interrupt_time = now;
// 处理中断...
return IRQ_HANDLED;
}中断处理最佳实践
设计原则
- 最小化顶半部执行时间:将耗时操作移到底半部
- 合理选择底半部机制:
- 软中断:高频率、时间敏感的任务
- Tasklet:一般优先级的异步任务
- 工作队列:需要睡眠或执行复杂操作的任务
- 避免在中断上下文中睡眠:中断上下文不能睡眠
- 正确使用锁机制:在SMP系统中保护共享数据
代码示例
c
struct my_device {
spinlock_t lock; // 保护共享数据
struct mutex mutex; // 保护可以睡眠的操作
struct work_struct work; // 工作队列
struct tasklet_struct tasklet; // tasklet
struct my_device_stats stats; // 统计信息
// 其他设备数据
};
// 顶半部:快速响应中断
static irqreturn_t my_device_isr(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
unsigned long flags;
u32 status;
// 使用自旋锁保护临界区
spin_lock_irqsave(&dev->lock, flags);
// 读取中断状态
status = readl(dev->base + STATUS_REG);
// 清除中断标志
writel(status, dev->base + CLEAR_REG);
// 更新统计(原子操作)
dev->stats.interrupts++;
spin_unlock_irqrestore(&dev->lock, flags);
// 根据中断类型决定底半部处理方式
if (status & HIGH_PRIORITY_MASK) {
// 高优先级任务使用tasklet
tasklet_schedule(&dev->tasklet);
} else {
// 一般任务使用工作队列
schedule_work(&dev->work);
}
return IRQ_HANDLED;
}
// Tasklet处理函数:处理高优先级任务
static void my_device_tasklet(unsigned long data)
{
struct my_device *dev = (struct my_device *)data;
// 处理高优先级任务
handle_high_priority_tasks(dev);
}
// 工作队列处理函数:处理一般任务
static void my_device_work(struct work_struct *work)
{
struct my_device *dev = container_of(work, struct my_device, work);
// 可以执行可能睡眠的操作
mutex_lock(&dev->mutex);
// 处理复杂逻辑
handle_complex_operations(dev);
mutex_unlock(&dev->mutex);
}
// 设备初始化
static int my_device_init(struct my_device *dev)
{
int ret;
// 初始化锁和工作队列
spin_lock_init(&dev->lock);
mutex_init(&dev->mutex);
INIT_WORK(&dev->work, my_device_work);
tasklet_init(&dev->tasklet, my_device_tasklet, (unsigned long)dev);
// 注册中断处理函数
ret = request_irq(dev->irq, my_device_isr, IRQF_SHARED, "my_device", dev);
if (ret) {
pr_err("Failed to register IRQ handler\n");
return ret;
}
return 0;
}通过掌握Linux内核中断处理机制,您可以开发出响应迅速、性能优良的设备驱动程序。合理使用各种中断处理技术,能够有效提高系统的实时性和稳定性。