学习笔记——按键驱动中的中断下半部机制

按键驱动中的中断下半部机制

一、中断处理的分层结构

1. 中断处理流程

复制代码
中断源 → CPU → 中断顶(上)半部【中断服务程序】 → 调度 → 中断底(下)半部

2. 中断上下文的区别

特性 中断上下文 进程上下文
定义 中断处理相关代码执行环境 进程相关代码执行环境
包含内容 中断服务程序、软中断、tasklet open、read、write等文件操作
能否休眠 不能休眠、不能阻塞 可以休眠、可以阻塞
调度 不能被调度 可以被调度
执行速度 要求快进快出 可以执行耗时操作

二、工作队列机制 (key_workqueue.c)

1. 工作队列的作用

  • 工作队列:用来处理中断的普通线程

  • 特点:可以处理不紧急任务,可以阻塞,可以休眠

  • 适用场景:需要执行耗时操作的中断处理

2. 关键代码分析

(1) 工作结构体定义
复制代码
static struct work_struct work;  // 定义工作结构体
(2) 工作处理函数
复制代码
static void key_work_func(struct work_struct *work)
{
    ssleep(1);  // 休眠1秒(在进程上下文中可以休眠)
    condition = 1;
    wake_up_interruptible(&wq);
    printk("key_work_func ..\n");
}

特点

  • 可以调用 ssleep(1) 进行休眠

  • 可以执行耗时操作

  • 在进程上下文中执行

(3) 中断处理函数中的调度
复制代码
static irqreturn_t key_irq_handler(int irq, void * dev)
{
    int arg = *(int *)dev;
    if(100 != arg)
        return IRQ_NONE;
    
    schedule_work(&work);  // 调度工作队列执行
    printk("irq = %d dev = %d\n", irq, arg);
    return IRQ_HANDLED;
}

schedule_work():将工作加入到系统工作队列中调度执行

(4) 初始化工作队列
复制代码
INIT_WORK(&work, key_work_func);  // 初始化工作,绑定处理函数
  • probe 函数中初始化

  • 指定工作处理函数为 key_work_func

3. 执行流程

复制代码
1. 按键按下触发中断
2. 执行中断顶半部(key_irq_handler)
   - 快速处理(打印信息)
   - 调度工作队列(schedule_work)
   - 立即返回
3. 系统工作队列调度执行底半部
   - 执行 key_work_func
   - 可以休眠(ssleep)
   - 设置条件变量
   - 唤醒等待进程

三、Tasklet机制 (key_tasklet.c)

1. Tasklet的作用

  • Tasklet:基于软中断实现的机制

  • 特点:处理紧急任务,要求快进快出,不能休眠,不能阻塞

  • 适用场景:需要快速处理的中断下半部

2. 关键代码分析

(1) Tasklet结构体定义
复制代码
static struct tasklet_struct tsk;  // 定义tasklet结构体

// tasklet结构体定义(简化的内核结构)
struct tasklet_struct
{
    struct tasklet_struct *next;   // 下一个tasklet
    unsigned long state;          // 状态
    atomic_t count;               // 引用计数
    void (*func)(unsigned long);  // 处理函数
    unsigned long data;           // 传递给处理函数的数据
};
(2) Tasklet处理函数
复制代码
static void key_tasklet_handler(unsigned long arg)
{
    condition = 1;
    wake_up_interruptible(&wq);
    printk("key_tasklet_handler arg = %ld\n", arg);
}

特点

  • 不能调用休眠函数(如 ssleep

  • 必须快进快出

  • 在中断上下文中执行

(3) 中断处理函数中的调度
复制代码
static irqreturn_t key_irq_handler(int irq, void * dev)
{
    int arg = *(int *)dev;
    if(100 != arg)
        return IRQ_NONE;
    
    tasklet_schedule(&tsk);  // 调度tasklet执行
    printk("irq = %d dev = %d\n", irq, arg);
    return IRQ_HANDLED;
}

tasklet_schedule():将tasklet加入到调度队列中执行

(4) 初始化Tasklet
复制代码
tasklet_init(&tsk, key_tasklet_handler, 100);  // 初始化tasklet
  • probe 函数中初始化

  • 参数1:tasklet结构体

  • 参数2:处理函数

  • 参数3:传递给处理函数的数据(这里传递100)

3. 执行流程

复制代码
1. 按键按下触发中断
2. 执行中断顶半部(key_irq_handler)
   - 快速处理(打印信息)
   - 调度tasklet(tasklet_schedule)
   - 立即返回
3. 内核在适当的时机执行tasklet
   - 执行 key_tasklet_handler
   - 不能休眠(直接执行)
   - 设置条件变量
   - 唤醒等待进程

四、两种下半部机制的对比

1. 调度方式对比

特性 工作队列 (Workqueue) Tasklet
调度类型 同步调度 异步调度
执行上下文 进程上下文 中断上下文
能否休眠 可以休眠 不能休眠
执行时机 由内核线程调度 在软中断中立即执行
适用场景 耗时操作,需要阻塞 快速操作,不能阻塞

2. 同步 vs 异步调度示例

同步调度(工作队列)
复制代码
// 类似这样的执行流程
fun() {
    sleep(1);          // 可以休眠
    printf("world\n");
}

main() {
    fun();             // 调用函数,等待其执行完成
    printf("hello\n"); // 只有fun()执行完后才执行
}
// 输出顺序:world → hello
异步调度(Tasklet)
复制代码
// 类似这样的执行流程
fun() {
    printf("world\n"); // 快速执行,不能休眠
}

main() {
    schedule_fun();    // 调度函数异步执行
    printf("hello\n"); // 立即执行,不等待fun()
}
// 输出顺序可能是:hello → world 或 world → hello

3. 代码初始化对比

复制代码
// 工作队列初始化
INIT_WORK(&work, key_work_func);

// Tasklet初始化  
tasklet_init(&tsk, key_tasklet_handler, 100);

五、中断处理的完整流程

1. 中断顶半部(上半部)要求

  • 短小:执行时间要尽量短

  • 快进快出:尽快处理完返回

  • 置标志位:设置必要的标志,调度下半部处理

  • 不执行耗时操作:不能休眠、不能阻塞

2. 中断底半部(下半部)选择原则

复制代码
中断发生
    ↓
判断任务性质:
    ↓
如果任务紧急、简短 → 使用 Tasklet(不能休眠)
    ↓
如果任务耗时、需要阻塞 → 使用工作队列(可以休眠)
    ↓
执行相应的下半部处理

3. 实际应用场景

  • Tasklet适用场景

    • 简单的数据处理

    • 状态标志设置

    • 快速唤醒等待队列

  • 工作队列适用场景

    • 需要访问文件系统

    • 需要分配大量内存

    • 需要执行I/O操作

    • 需要休眠等待资源

六、驱动框架总结

1. 整体架构

复制代码
应用程序(read阻塞)
        ↓
驱动程序
├── 文件操作接口(open/read/close)
├── 中断顶半部(快速处理,调度下半部)
├── 中断底半部(Tasklet/工作队列)
│   ├── Tasklet:快速处理,不能休眠
│   └── 工作队列:耗时处理,可以休眠
└── 等待队列机制(同步应用程序和中断)

2. 核心机制

  • 等待队列:实现进程的阻塞和唤醒

  • 中断处理:快速响应硬件事件

  • 下半部机制:处理中断的后续工作

  • 设备树匹配:硬件与驱动的自动匹配

3. 使用建议

  1. 优先使用Tasklet:如果任务简单且不需要休眠

  2. 必要时使用工作队列:如果任务需要休眠或执行耗时操作

  3. 保持顶半部简短:中断处理函数中只做必要的最小工作

  4. 合理使用等待队列:同步用户空间和内核空间的操作

总结

  1. 中断分为上下两部分:顶半部(快速处理)和底半部(后续处理)

  2. Tasklet:基于软中断,不能休眠,适合快速处理

  3. 工作队列:基于内核线程,可以休眠,适合耗时操作

  4. 两种调度方式:同步调度(工作队列)和异步调度(Tasklet)

  5. 选择依据:根据任务是否需要休眠来选择合适的下半部机制

这些机制都是为了在保证中断响应速度的同时,能够处理复杂的后续工作,提高系统的整体性能和响应能力。

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
Yana.nice3 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
2601_960567966 小时前
电商套图自动化效率的工程量化分析——从逐张生成到批量套图的架构演进
运维·架构·自动化
a1117766 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao7 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
吳所畏惧7 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh