嵌入式linux学习记录七
Linux 中断的两半机制中断处理有个核心矛盾需要快速响应但有些处理很耗时。Linux 的解决方案上半部 下半部中断触发 │ ▼ ┌─────────────────────────────┐ │ 上半部Top Half │ ← 必须快关中断执行 │ · 读取硬件数据 │ │ · 清除中断标志 │ │ · 登记下半部任务 │ └──────────────┬──────────────┘ │ 立即返回重新开中断 ▼ ┌─────────────────────────────┐ │ 下半部Bottom Half │ ← 稍后执行可被中断 │ · 数据处理 │ │ · 协议栈处理 │ │ · 唤醒等待进程 │ └─────────────────────────────┘下半部的三种实现方式1. softirq软中断· 编译时静态定义数量固定32个 · 同一 softirq 可在多个 CPU 并发执行 · 优先级最高用于网络收发、块设备等 · 在 ksoftirqd 内核线程或中断返回时执行2. tasklet· 基于 softirq 实现 · 同一 tasklet 同一时刻只在一个 CPU 执行更安全 · 动态创建驱动开发常用3. workqueue工作队列· 在内核线程worker thread中执行 · 可以睡眠、阻塞前两种不能睡眠 · 适合耗时操作softirqtaskletworkqueue执行上下文中断上下文中断上下文进程上下文可以睡眠❌❌✅并发多CPU并发串行多线程使用难度复杂简单简单驱动中如何使用中断#include linux/interrupt.h // 1. 定义中断处理函数 static irqreturn_t my_irq_handler(int irq, void *dev_id) { // 上半部快速处理 // 清中断标志、读数据... // 登记下半部 tasklet_schedule(my_tasklet); return IRQ_HANDLED; } // 2. 注册中断 int irq gpio_to_irq(gpio_num); // 获取IRQ号 request_irq(irq, // IRQ号 my_irq_handler, // 处理函数 IRQF_TRIGGER_RISING, // 触发方式 my_device, // 名字 dev); // 传给handler的参数 // 3. 释放中断 free_irq(irq, dev);下半部用 tasklet// 定义 tasklet static void my_tasklet_func(unsigned long data) { // 下半部处理耗时操作 } DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0); // 在上半部中触发 tasklet_schedule(my_tasklet);下半部用 workqueue// 定义 work static void my_work_func(struct work_struct *work) { // 可以睡眠可以阻塞 msleep(100); // workqueue 中可以这样用 } DECLARE_WORK(my_work, my_work_func); // 触发 schedule_work(my_work);中断上下文的限制在中断处理程序上半部、softirq、tasklet中❌ 不能睡眠 // 没有进程上下文无法调度 ❌ 不能调用可能睡眠的函数 // mutex_lock、kmalloc(GFP_KERNEL)... ❌ 不能访问用户空间内存 ✅ 可以用 spinlock ✅ 可以用 kmalloc(GFP_ATOMIC) ✅ 可以读写硬件寄存器中断屏蔽// 关闭本地CPU所有中断 local_irq_disable(); // ... 临界区 ... local_irq_enable(); // 更安全的方式保存中断状态 unsigned long flags; local_irq_save(flags); // ... 临界区 ... local_irq_restore(flags); // 只禁止某个IRQ disable_irq(irq); enable_irq(irq);完整流程总结硬件触发中断 │ ▼ GIC 仲裁通知 CPU │ ▼ CPU 切换到内核态保存现场切换到中断栈 │ ▼ 查向量表调用 handle_irq │ ▼ 执行上半部irq_handler── 快速返回 │ ▼ 触发下半部softirq / tasklet / workqueue │ ▼ 恢复现场返回被中断的代码中断上半部分和下半部分的关系下半部类型能否被硬中断打断能否被同类打断softirq✅ 能❌ 不能同一CPUtasklet✅ 能❌ 不能workqueue✅ 能✅ 能硬中断可以打断下半部时间轴 ──────────────────────────────→ ┌──────────┐ │ 下半部 │ │ 执行中 │ └────┬─────┘ │ 新的硬中断来了 ▼ ┌──────────┐ │ 上半部 │ ← 打断下半部优先处理 └────┬─────┘ │ 上半部结束 ▼ ┌──────────┐ │ 下半部 │ ← 继续执行 │ 继续 │ └──────────┘因为下半部执行时中断是开着的所以硬中断随时可以进来。但 softirq 不能被同类打断// softirq 执行时会设置标志位 // 同一个 CPU 上同种 softirq 不会重入 CPU0 正在执行 NET_RX_SOFTIRQ │ │ 又来了网络包触发 NET_RX_SOFTIRQ │ └──→ 不会立即执行等当前 softirq 结束再说但不同 CPU 可以同时执行同一种 softirq所以 softirq 处理函数要考虑并发。tasklet 更严格同一个 tasklet任意时刻只在一个 CPU 上执行 │ ├── CPU0 执行 tasklet A ──→ CPU1 想执行 tasklet A等着 └── CPU0 执行 tasklet A ──→ CPU1 执行 tasklet B可以所以 tasklet 比 softirq 更安全驱动开发更常用。workqueue 最自由workqueue 跑在内核线程里是完整的进程上下文能被硬中断打断 ✅ 能被其他线程抢占 ✅ 能睡眠 ✅ 能被调度器调度 ✅跟普通内核线程没有区别。一句话总结下半部对硬中断都是敞开的硬中断随时能打断它。但下半部内部有自己的互斥机制防止自己被同类重入。