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

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

一、中断处理的分层结构

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. 选择依据:根据任务是否需要休眠来选择合适的下半部机制

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

相关推荐
Sinclair1 小时前
内网服务器离线安装 Nginx+PHP+MySQL 的方法
运维
叶落阁主1 小时前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
0xDevNull8 小时前
Linux切换JDK版本详细教程
linux
进击的丸子8 小时前
虹软人脸服务器版SDK(Linux/ARM Pro)多线程调用及性能优化
linux·数据库·后端
甲鱼9291 天前
MySQL 实战手记:日志管理与主从复制搭建全指南
运维
Johny_Zhao2 天前
OpenClaw安装部署教程
linux·人工智能·ai·云计算·系统运维·openclaw
chlk1233 天前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
舒一笑3 天前
Ubuntu系统安装CodeX出现问题
linux·后端
改一下配置文件3 天前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
碳基沙盒3 天前
OpenClaw 多 Agent 配置实战指南
运维