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

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

一、中断处理的分层结构

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

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

相关推荐
ZHOUPUYU5 小时前
PHP 8.3网关优化:我用JIT将QPS提升300%的真实踩坑录
开发语言·php
寻寻觅觅☆9 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
l1t10 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
小白同学_C10 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖10 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
2601_9491465310 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
儒雅的晴天10 小时前
大模型幻觉问题
运维·服务器
赶路人儿10 小时前
Jsoniter(java版本)使用介绍
java·开发语言
testpassportcn10 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
ceclar12311 小时前
C++使用format
开发语言·c++·算法