一、中断上下半部
1. 为什么需要上下半部
中断处理函数不能做耗时操作,否则会阻塞其他中断和进程。所以 Linux 把中断处理分成两部分:
| 部分 | 作用 | 特点 |
|---|---|---|
| 上半部(顶半部) | 紧急的硬件操作 | 不可被打断,要快 |
| 下半部(底半部) | 耗时的操作 | 可被打断,可以慢 |
上半部:在中断处理函数里,只做最紧急的事(比如记录中断、唤醒进程)。
下半部:把耗时的操作放到下半部执行。
2. 下半部的实现方式
Linux 有 4 种下半部实现方式:
| 方式 | 执行上下文 | 能否睡眠 |
|---|---|---|
| 软中断 | 中断上下文 | ❌ |
| tasklet | 中断上下文 | ❌ |
| 工作队列 | 进程上下文 | ✅ |
| 线程化 IRQ | 进程上下文 | ✅ |
tasklet 和 工作队列最常用。
二、tasklet
1. 特点
基于软中断实现。
执行在中断上下文,不能睡眠。
适合短小、不睡眠的耗时操作。
2. 代码示例
c
#include <linux/interrupt.h> static void mytasklet_func(unsigned long data) { pr_info("tasklet running\n"); } static DECLARE_TASKLET(mytasklet, mytasklet_func, 0); static irqreturn_t key_isr(int irqno, void *pdev) { tasklet_schedule(&mytasklet); // 调度 tasklet return IRQ_HANDLED; }| 函数/宏 | 作用 |
|---|---|
DECLARE_TASKLET | 定义并初始化 tasklet |
tasklet_schedule | 调度 tasklet(在中断里调用) |
mytasklet_func | tasklet 执行函数 |
中断触发 →tasklet_schedule调度 → 内核在合适时机执行mytasklet_func。
三、工作队列
1. 特点
执行在进程上下文,可以睡眠。
适合耗时、可能睡眠的操作(比如
msleep、I2C 读写)。
2. 代码示例
c
#include <linux/workqueue.h> static struct work_struct workqueue; static void key_workqueue_func(struct work_struct *work) { msleep(10); // 可以睡眠 pr_info("key pressed\n"); } static irqreturn_t key_handler(int irqno, void *pdev) { schedule_work(&workqueue); // 调度工作队列 return IRQ_HANDLED; } // probe 里初始化 INIT_WORK(&workqueue, key_workqueue_func);| 函数/宏 | 作用 |
|---|---|
INIT_WORK | 初始化工作队列 |
schedule_work | 调度工作队列(在中断里调用) |
key_workqueue_func | 工作队列执行函数 |
中断触发 →schedule_work调度 → 内核线程执行key_workqueue_func。
3. tasklet vs 工作队列
| 项目 | tasklet | 工作队列 |
|---|---|---|
| 执行上下文 | 中断上下文 | 进程上下文 |
| 能否睡眠 | ❌ | ✅ |
| 适合 | 短小操作 | 耗时操作 |
| 调度函数 | tasklet_schedule | schedule_work |
关键:需要在中断下半部睡眠(比如msleep),必须用工作队列。
四、input 子系统
1. 什么是 input 子系统
Linux 把输入设备(键盘、鼠标、触摸屏、按键)统一管理,称为input 子系统。
应用程序通过/dev/input/eventX读取输入事件,不用自己写驱动接口。
2. input 驱动的流程
c
// 1. 申请 input 设备 pkey_dev = input_allocate_device(); // 2. 说明设备能力(支持什么事件) input_set_capability(pkey_dev, EV_KEY, KEY_0); // 3. 注册 input 设备 input_register_device(pkey_dev); // 4. 上报事件 input_event(pkey_dev, EV_KEY, KEY_0, 1); // 按下 input_sync(pkey_dev); input_event(pkey_dev, EV_KEY, KEY_0, 0); // 松开 input_sync(pkey_dev);
| 函数 | 作用 |
|---|---|
input_allocate_device | 申请 input 设备 |
input_set_capability | 说明设备支持的事件类型 |
input_register_device | 注册 input 设备 |
input_event | 上报事件 |
input_sync | 同步事件 |
3. 事件类型
| 类型 | 含义 |
|---|---|
EV_KEY | 按键事件 |
EV_REL | 相对坐标(鼠标) |
EV_ABS | 绝对坐标(触摸屏) |
4. input 驱动 + tasklet
c
static void mytasklet_func(unsigned long data) { input_event(pkey_dev, EV_KEY, KEY_0, 1); // 按下 input_sync(pkey_dev); input_event(pkey_dev, EV_KEY, KEY_0, 0); // 松开 input_sync(pkey_dev); } static irqreturn_t key_isr(int irqno, void *pdev) { tasklet_schedule(&mytasklet); // 中断里调度 tasklet return IRQ_HANDLED; }中断触发 → tasklet 上报按键事件 → 应用程序从/dev/input/eventX读到事件。
5. 应用程序读取 input 事件
c
#include <linux/input.h> int fd = open("/dev/input/event1", O_RDONLY); struct input_event ev; while (1) { read(fd, &ev, sizeof(ev)); if (ev.type == EV_KEY && ev.code == KEY_0) { if (ev.value == 1) { printf("key pressed\n"); } else if (ev.value == 0) { printf("key released\n"); } } }/dev/input/eventX由 input 子系统自动创建,应用程序直接读。
五、内核定时器
1. 概念
内核定时器:在指定的时间之后,执行一个函数(超时函数)。
节拍(jiffies):内核的系统时钟,每毫秒增加一次(
HZ = 100)。定时时间:
jiffies + msecs_to_jiffies(100)表示 100ms 后。
2. 代码示例
c
#include <linux/timer.h> static void timeout(unsigned long data) { pr_info("timeout ok!\n"); } static DEFINE_TIMER(mtimer, timeout, 0, 0); static int __init kernel_timer_init(void) { init_timer(&mtimer); // 初始化定时器 add_timer(&mtimer); // 添加定时器 mod_timer(&mtimer, jiffies + msecs_to_jiffies(100)); // 设置超时时间 return 0; } static void __exit kernel_timer_exit(void) { del_timer(&mtimer); // 删除定时器 return 0; }3. 关键函数
| 函数 | 作用 |
|---|---|
DEFINE_TIMER | 定义并初始化定时器 |
init_timer | 初始化定时器 |
add_timer | 激活定时器 |
mod_timer | 修改超时时间 |
del_timer | 删除定时器 |
4. 时间转换
| 函数 | 作用 |
|---|---|
msecs_to_jiffies | 毫秒转节拍 |
usecs_to_jiffies | 微秒转节拍 |
jiffies_to_msecs | 节拍转毫秒 |
5. 用法
c
mod_timer(&mtimer, jiffies + msecs_to_jiffies(100)); // 100ms 后超时
超时后,内核自动调用timeout函数。
六、三种下半部方式对比
| 方式 | 上下文 | 能否睡眠 | 适合场景 |
|---|---|---|---|
| tasklet | 中断上下文 | ❌ | 短小操作 |
| 工作队列 | 进程上下文 | ✅ | 耗时操作 |
| 内核定时器 | 中断上下文 | ❌ | 延时执行 |
七、总结
| 知识点 | 内容 |
|---|---|
| 上下半部 | 上半部快,下半部慢 |
| tasklet | 中断上下文,不能睡眠 |
| 工作队列 | 进程上下文,可以睡眠 |
| input 子系统 | 统一管理输入设备,上报事件 |
| 内核定时器 | 延时执行函数 |
核心思想:中断处理函数要快,耗时操作放到下半部。tasklet 用于短小操作,工作队列用于耗时操作,input 子系统用于输入设备,内核定时器用于延时执行。