linux中断和响应

1. 驱动中断函数基础

中断分为向量中断和非向量中断。向量中断为不同的中断分配不同的中断号,中断到来后自动跳转到地址执行,是硬件提供中断服务程序入口地址;非向量中断是多个中断程序共享一个入口地址,进入入口后再根据标志判断是那个中断。

1.1 request_irq

申请IRQ函数

/*
 * irq:中断号
 * handler:回调函数,发生中断时,系统调用该函数
 * flags:中断处理属性1.3
 * devname:用于在/proc/interrupts标记,便于调试
 * dev_id:私有指针,内核不直接使用,但在中断发生时会原样传递给处理函数。在共享中断中,此参数是唯一能区分不同设备的手段,一般传入本设备结构体或NULL
* 成功返回0
*/ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id); /* * int irq:触发的中断号(同一个处理函数可以处理多个中断线时有用) * void *dev_id:设备标识符,即 request_irq 传入的 dev_id 参数 * 返回值 irqreturn_t: IRQ_NONE:中断不是本设备产生的(用于共享中断时判断) IRQ_HANDLED:中断已被本设备处理 IRQ_WAKE_THREAD:仅在线程化中断中使用,表示需要唤醒中断线程 */ typedef irqreturn_t (*irq_handler_t)(int, void*);

中断标志 flags 用于控制中断的行为,可组合使用(位或),常用标志包括:

标志含义
IRQF_SHARED 允许多个设备共享同一中断线。所有共享该中断的驱动必须设置此标志,并且处理函数必须能通过 dev_id 区分自己设备的中断。
IRQF_ONESHOT 用于线程化中断。确保中断处理线程执行期间,该中断线被屏蔽,线程执行完后再自动重新使能。通常与线程化中断配合使用。
IRQF_TRIGGER_RISING / FALLING / HIGH / LOW 指定中断触发电平(上升沿、下降沿、高电平、低电平)。这些标志通常由平台代码或设备树解析后自动设置,但驱动也可以显式指定。
IRQF_DISABLED (过时)旧内核用于要求在关中断上下文中执行处理函数。现代内核中,中断处理函数默认在关中断(本地 CPU 关中断)下执行,此标志已无意义。
IRQF_NO_SUSPEND 系统休眠时不禁用该中断。
IRQF_FORCE_RESUME 系统唤醒时强制恢复该中断。
IRQF_NO_THREAD 禁止将该中断强制线程化(即使系统开启 force_irqthreads)。
IRQF_EARLY_RESUME 在早期恢复阶段(syscore 阶段)恢复中断。

1.2 free_irq

释放IRQ函数

void free_irq(unsigned int irq, void* dev_id);

1.3 使能和屏蔽中断

释放IRQ函数

void disable_irq(int irq);          // 等待目前的中断处理完成后返回,由于该函数等待指定中断处理完,如果在中断的顶半段调用该函数,会引起系统死锁
void disable_irq_nosync(int irq);   // 立即返回
void enable_irq(int irq);

1.4 DECLARE_TASKLET

该函数是 Linux 内核中用于静态定义并初始化一个 tasklet 的宏。Tasklet 是一种常用的软中断机制,用于实现中断处理的下半部(bottom half),允许在稍后的安全时间执行延迟的工作,且保证在中断上下文中运行,不会睡眠

#include <linux/interrupt.h>

/*
 * name:要定义的 tasklet 变量名
 * func:tasklet 处理函数,类型为 void (*func)(unsigned long)
 * data:传递给 func 的参数,类型为 unsigned long,通常用于传递设备结构体指针或标志
 */
DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);  // 初始为禁用状态

// 该宏展开后类似如下
struct tasklet_struct name = {
    .func = func,
    .data = data,
    .state = 0,                    // 对于 DECLARE_TASKLET
    // 其他成员由内核初始化
};

最后通过 tasklet_schedule(&xxx_tasklet) 运行

/*
 * 该模块模拟一个简单的设备驱动:使用内核定时器周期性地触发“中断”,
 * 中断上半部:调度一个 tasklet
 * 中断下半部:tasklet 执行实际工作(增加计数器并打印)
 */

#include <linux/module.h>
#include <linux/interrupt.h>   // tasklet 相关
#include <linux/timer.h>       // 定时器模拟硬件中断
#include <linux/slab.h>           // kzalloc
#include <linux/spinlock.h>    // 自旋锁

#define DRIVER_NAME "tasklet_second_dev"

/* 设备结构体,包含所有相关资源 */
struct my_device {
    struct tasklet_struct tasklet;   // tasklet 对象
    struct timer_list timer;         // 用于模拟硬件中断的定时器
    spinlock_t lock;                 // 保护共享数据 counter
    unsigned long counter;             // 统计 tasklet 执行次数
};

static struct my_device *my_dev;     // 全局设备指针

/* tasklet 处理函数(下半部) */
static void my_tasklet_handler(unsigned long data)
{
    struct my_device *dev = (struct my_device *)data;

    /* tasklet 运行在软中断上下文,不能睡眠,只能使用原子操作或自旋锁 */
    spin_lock(&dev->lock);
    dev->counter++;
    spin_unlock(&dev->lock);

    pr_info("Tasklet executed, counter = %lu\n", dev->counter);

    /* 此处不能调用可能睡眠的函数(如 kmalloc(GFP_KERNEL)) */
}

/* 中断上半部处理程序(由定时器回调调用) */
static void simulated_interrupt(struct timer_list *t)
{
    struct my_device *dev = container_of(t, struct my_device, timer);

    pr_info("Simulated interrupt, scheduling tasklet\n");

    /* 调度 tasklet:将 tasklet 挂入队列,稍后在软中断中执行 */
    tasklet_schedule(&dev->tasklet);

    /* 硬件中断处理还需要:
     * 1. 读取中断状态寄存器,确认是否是自己的设备产生的中断
     * 2. 清除硬件中断标志
     * 3. 如果是共享中断,返回 IRQ_NONE 表示不是自己的中断
     */

    /* 重新启动定时器,实现周期性中断(如每秒一次) */
    mod_timer(&dev->timer, jiffies + HZ);
}

/* 模块初始化 */
static int __init tasklet_second_init(void)
{
    int ret;

    /* 1. 分配设备结构体内存 */
    my_dev = kzalloc(sizeof(struct my_device), GFP_KERNEL);
    if (!my_dev)
        return -ENOMEM;

    /* 2. 初始化自旋锁 */
    spin_lock_init(&my_dev->lock);

    /* 3. 初始化 tasklet:绑定处理函数,传入设备指针作为参数 */
    tasklet_init(&my_dev->tasklet, my_tasklet_handler, (unsigned long)my_dev);

    /* 4. 初始化定时器,用于模拟硬件中断 */
    timer_setup(&my_dev->timer, simulated_interrupt, 0);
    mod_timer(&my_dev->timer, jiffies + HZ);  // 1 秒后触发第一次

    pr_info("Tasklet sample module loaded, timer started.\n");

    return 0;
}

/* 模块退出 */
static void __exit tasklet_second_exit(void)
{
    if (!my_dev)
        return;

    /* 1. 删除定时器,确保不会再调用模拟中断 */
    del_timer_sync(&my_dev->timer);

    /* 2. 杀死 tasklet:确保 tasklet 不会再次被调度,并等待正在运行的实例完成 */
    tasklet_kill(&my_dev->tasklet);

    /* 此时可以安全地释放资源,因为不会有任何 tasklet 在运行 */
    pr_info("Tasklet killed, final counter = %lu\n", my_dev->counter);

    /* 3. 释放设备结构体 */
    kfree(my_dev);
    pr_info("Tasklet sample module unloaded\n");
}

module_init(tasklet_second_init);
module_exit(tasklet_second_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cear");

 

1.5 INIT_WORK

该函数是 Linux 内核中用于动态初始化一个工作项(work_struct)的宏。工作项是工作队列机制的核心,允许将任务延迟到进程上下文中执行,从而可以睡眠、持有互斥锁等,适用于处理较复杂或可能阻塞的操作(如大量的数据拷贝、文件系统操作等)

#include <linux/workqueue.h>

/*
 * work:指向要初始化的 struct work_struct 变量的指针
 * func:工作处理函数,将在工作项被调度时执行
 */ 
INIT_WORK(struct work_struct *work, work_func_t func);

// 宏展开后类似如下
struct work_struct {
    atomic_long_t data;          // 内部状态和标志
    struct list_head entry;      // 用于挂入队列的链表节点
    work_func_t func;            // 工作处理函数
};

最后通过 schedule_work(&my_work)调度工作队列执行

/*
 * - 动态初始化工作项(INIT_WORK)
 * - 使用系统默认工作队列调度工作(schedule_work)
 * - 在模拟中断(定时器回调)中调度工作
 * - 工作函数在进程上下文中执行,可以睡眠
 * - 模块卸载时确保工作项被取消(cancel_work_sync)
 */

#include <linux/module.h>
#include <linux/workqueue.h>   // work_struct 相关
#include <linux/timer.h>       // 定时器模拟中断
#include <linux/slab.h>        // kmalloc
#include <linux/spinlock.h>    // 自旋锁
#include <linux/delay.h>       // msleep (仅用于演示可睡眠)

#define DRIVER_NAME "workqueue_sample"

/* 设备结构体,包含工作项、定时器、计数器及锁 */
struct my_device {
    struct work_struct work;        // 工作项
    struct timer_list timer;        // 模拟中断的定时器
    spinlock_t lock;                // 保护 counter
    unsigned long counter;          // 统计工作项执行次数
    bool stop;                      // 停止标志,用于退出循环(可选)
};

static struct my_device *my_dev;    // 全局设备指针

/* 中断下半部:工作队列处理函数(运行在进程上下文,可以睡眠) */
static void my_work_handler(struct work_struct *work)
{
    struct my_device *dev = container_of(work, struct my_device, work);
    unsigned long flags;

    /* 可以进行需要睡眠的操作(例如长时间计算、I2C 读写等) */
    msleep(10); 

    /* 更新计数器(需要保护,因为可能与模块卸载路径并发) */
    spin_lock_irqsave(&dev->lock, flags);
    dev->counter++;
    spin_unlock_irqrestore(&dev->lock, flags);

    pr_info("Work handler executed, counter = %lu\n", dev->counter);
}

/* 中断上半部:定时器回调(软中断上下文,不能睡眠) */
static void simulated_interrupt(struct timer_list *t)
{
    struct my_device *dev = from_timer(dev, t, timer);

    pr_info("Simulated interrupt, scheduling work\n");

    /* 调度工作项到系统默认工作队列 */
    schedule_work(&dev->work);

    /* 重新启动定时器,模拟周期性中断(每2秒一次) */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(2000));
}

/* 模块初始化 */
static int __init workqueue_sample_init(void)
{
    int ret;

    /* 1. 分配设备结构体内存 */
    my_dev = kzalloc(sizeof(struct my_device), GFP_KERNEL);
    if (!my_dev)
        return -ENOMEM;

    /* 2. 初始化自旋锁 */
    spin_lock_init(&my_dev->lock);

    /* 3. 初始化工作项:绑定处理函数,注意处理函数参数是 work_struct 指针 */
    INIT_WORK(&my_dev->work, my_work_handler);

    /* 4. 初始化定时器,用于模拟中断 */
    timer_setup(&my_dev->timer, simulated_interrupt, 0);
    mod_timer(&my_dev->timer, jiffies + msecs_to_jiffies(2000)); // 2秒后首次触发

    pr_info("Workqueue sample module loaded, timer started (period 2s)\n");
    return 0;
}

/* 模块退出 */
static void __exit workqueue_sample_exit(void)
{
    if (!my_dev)
        return;

    pr_info("Unloading module, stopping timer and cancel work...\n");

    /* 1. 删除定时器,确保不再触发新的工作调度 */
    del_timer_sync(&my_dev->timer);

    /* 2. 取消可能正在执行或待处理的工作项
     *    cancel_work_sync 会等待正在工作项完成
     *    并确保之后不会再有该工作项被调度
     *    返回 true 表示工作项在取消前尚未开始执行
     *    false 表示工作项已经开始执行或已经完成
     */
    if (cancel_work_sync(&my_dev->work))
        pr_info("Work was pending and has been cancelled.\n");
    else
        pr_info("Work was not pending or already executed.\n");

    /* 此时可以安全地访问 counter(无并发工作项) */
    pr_info("Final counter = %lu\n", my_dev->counter);

    /* 3. 释放设备结构体 */
    kfree(my_dev);
    pr_info("Workqueue sample module unloaded\n");
}

module_init(workqueue_sample_init);
module_exit(workqueue_sample_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cear");

 

posted @ 2026-02-16 19:52  cear  阅读(1)  评论(0)    收藏  举报