title: 中断下半部处理 tags: [Linux驱动, 中断, softirq, tasklet, workqueue, 嵌入式] created: 2026-09-17 updated: 2026-09-17
💡 关联知识: [[02-嵌入式Linux内核基础/04-进程调度与中断管理]] | [[FreeRTOS学习笔记/07-中断管理与临界段]]
Linux内核中的中断处理必须快速完成,因为中断会打断当前正在执行的代码。如果中断处理函数执行时间过长,会导致系统响应延迟,甚至丢失其他中断。因此,Linux内核将中断处理分为两部分:
graph TD
A[硬件中断触发] --> B[执行上半部]
B --> C{是否需要下半部?}
C -->|是| D[调度下半部]
C -->|否| E[中断处理完成]
D --> F[软中断/Tasklet]
D --> G[工作队列]
D --> H[线程化中断]
F --> I[执行下半部处理]
G --> I
H --> I
I --> J[中断处理完成]
| 机制 | 执行上下文 | 可睡眠 | 适用场景 |
|---|---|---|---|
| 软中断(Softirq) | 软中断上下文 | 否 | 高频事件、网络收发 |
| Tasklet | 软中断上下文 | 否 | 快速处理、简单任务 |
| 工作队列(Workqueue) | 进程上下文 | 是 | 耗时操作、需要睡眠 |
| 线程化中断 | 内核线程 | 是 | 复杂处理、需要互斥 |
软中断是Linux内核提供的一种下半部实现机制。内核使用softirq_action结构体表示软中断,定义在include/linux/interrupt.h中:
// 示例51.1.2.1 softirq_action结构体
struct softirq_action
{
void (*action)(struct softirq_action *);
};
在kernel/softirq.c文件中,定义了一个包含10个软中断的数组:
// 示例51.1.2.2 softirq_vec数组
static struct softirq_action softirq_vec[NR_SOFTIRQS];
NR_SOFTIRQS是枚举类型,定义在include/linux/interrupt.h中:
// 示例51.1.2.3 软中断类型枚举
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 */
TIMER_SOFTIRQ, /* 定时器软中断 */
NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet软中断 */
SCHED_SOFTIRQ, /* 调度软中断 */
HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
RCU_SOFTIRQ, /* RCU软中断 */
NR_SOFTIRQS
};
要使用软中断,需要使用open_softirq注册软中断处理函数:
// 注册软中断处理函数原型
void open_softirq(int nr, void (*action)(struct softirq_action *))
参数说明:
nr:要注册的软中断,从上述枚举中选择一个action:软中断对应的处理函数注册软中断以后,需要通过raise_softirq触发软中断:
// 触发软中断原型
void raise_softirq(unsigned int nr)
软中断在系统启动时初始化,softirq_init函数定义在kernel/softirq.c文件中:
// 示例51.1.2.4 softirq_init函数内容
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
从示例51.1.2.4可以看出,softirq_init函数默认会注册TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断。
软中断适用于:
tasklet是基于软中断实现的一种下半部机制。在中断和tasklet之间,推荐使用tasklet。Linux内核使用tasklet_struct结构体来表示tasklet:
// 示例51.1.2.5 tasklet_struct结构体
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个tasklet */
unsigned long state; /* tasklet状态 */
atomic_t count; /* 引用计数,记录tasklet是否被禁止 */
void (*func)(unsigned long); /* tasklet执行的函数 */
unsigned long data; /* 传递给func的参数 */
};
要使用tasklet,首先需要定义一个tasklet,然后使用tasklet_init初始化tasklet:
// tasklet_init函数原型
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
参数说明:
t:要初始化的taskletfunc:tasklet的处理函数data:传递给func的参数也可以使用宏DECLARE_TASKLET一次性完成tasklet的定义和初始化:
// DECLARE_TASKLET宏(include/linux/interrupt.h)
DECLARE_TASKLET(name, func, data)
参数说明:
name:要定义的tasklet名称(会自动创建一个tasklet_struct类型的变量)func:tasklet的处理函数data:传递给func的参数当需要调度tasklet执行时,使用tasklet_schedule函数:
// tasklet_schedule函数原型
void tasklet_schedule(struct tasklet_struct *t)
参数说明:
t:要调度的tasklet,也就是DECLARE_TASKLET宏定义的name// 示例51.1.2.7 tasklet使用示例
/* 定义tasklet */
struct tasklet_struct testtasklet;
/* tasklet处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet具体的处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
/* 启动tasklet */
tasklet_schedule(&testtasklet);
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
/* 初始化tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
}
count成员用于控制tasklet的启用和禁用工作队列是另一种下半部执行方式,工作在进程上下文执行,可以将工作推迟到一个内核线程去执行。因为工作队列工作在进程上下文,所以允许睡眠。如果推迟的工作需要睡眠,那么就只能选择工作队列。
Linux内核使用work_struct结构体表示一个工作:
// 示例51.1.2.8 work_struct结构体
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
这些工作组织成队列,使用workqueue_struct结构体表示工作队列:
// 示例51.1.2.9 workqueue_struct结构体(简化版)
struct workqueue_struct {
struct list_head pwqs;
struct list_head list;
struct mutex mutex;
int work_color;
int flush_color;
atomic_t nr_pwqs_to_flush;
struct wq_flusher *first_flusher;
struct list_head flusher_queue;
struct list_head flusher_overflow;
struct list_head maydays;
struct worker *rescuer;
int nr_drainers;
int saved_max_active;
struct workqueue_attrs *unbound_attrs;
struct pool_workqueue *dfl_pwq;
char name[WQ_NAME_LEN];
struct rcu_head rcu;
unsigned int flags ____cacheline_aligned;
struct pool_workqueue __percpu *cpu_pwqs;
struct pool_workqueue __rcu *numa_pwq_tbl[];
};
Linux内核使用工作者线程(worker thread)来处理工作队列中的各个工作。Linux内核使用worker结构体表示工作者线程:
// 示例51.1.2.10 worker结构体
struct worker {
union {
struct list_head entry;
struct hlist_node hentry;
};
struct work_struct *current_work;
work_func_t current_func;
struct pool_workqueue *current_pwq;
bool desc_valid;
struct list_head scheduled;
struct task_struct *task;
struct worker_pool *pool;
struct list_head node;
unsigned long last_active;
unsigned int flags;
int id;
char desc[WORKER_DESC_LEN];
struct workqueue_struct *rescue_wq;
};
从示例51.1.2.10可以看出,每个worker都有一个自己的任务队列,工作者线程负责将自己的任务队列中的所有工作都执行完毕。实际上工作队列中,我们只需要做的是创建工作(work_struct),在工作队列和工作者线程机制出来之前我们不用去管。简单来说就是,我们只需要定义一个work_struct结构体变量即可,然后使用INIT_WORK宏对其进行初始化:
// INIT_WORK宏定义
#define INIT_WORK(_work, _func)
参数说明:
_work:要初始化的工作_func:工作对应的处理函数也可以使用DECLARE_WORK宏一次性完成工作的定义和初始化:
// DECLARE_WORK宏定义
#define DECLARE_WORK(n, f)
参数说明:
n:定义的工作(work_struct)f:工作对应的处理函数和tasklet一样,工作也需要调度才能执行,工作的调度函数为schedule_work:
// schedule_work函数原型
bool schedule_work(struct work_struct *work)
参数说明:
work:要调度的工作返回值:0 成功,其他值 失败。
// 示例51.1.2.11 工作队列使用示例
/* 定义工作(work) */
struct work_struct testwork;
/* work处理函数 */
void testwork_func_t(struct work_struct *work)
{
/* work具体的处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
/* 调度work */
schedule_work(&testwork);
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
/* 初始化work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
}
除了使用系统默认的工作队列,也可以自己创建工作队列:
// 创建单线程工作队列
struct workqueue_struct *create_singlethread_workqueue(const char *name);
// 创建多线程工作队列
struct workqueue_struct *alloc_workqueue(const char *name,
unsigned int flags,
int max_active);
// 调度指定工作队列中的工作
bool queue_work(struct workqueue_struct *wq,
struct work_struct *work);
// 销毁工作队列
void destroy_workqueue(struct workqueue_struct *wq);
| 场景 | 选择 |
|---|---|
| 快速处理,不需要睡眠 | Tasklet |
| 耗时操作,需要睡眠 | 工作队列 |
| 高频事件 | 软中断 |
| 复杂处理,需要互斥 | 线程化中断 |
线程化中断是Linux内核提供的一种中断处理机制,将中断处理分为硬中断上下文和内核线程上下文两部分。线程化中断允许在下半部中睡眠,适合复杂的中断处理场景。
使用request_threaded_irq函数申请线程化中断:
// request_threaded_irq函数原型
int request_threaded_irq(unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags,
const char *devname,
void *dev_id);
参数说明:
irq:要申请的中断号handler:上半部处理函数(硬中断上下文,不可睡眠)thread_fn:下半部处理函数(内核线程上下文,可以睡眠)irqflags:中断标志devname:中断名称dev_id:传递给中断处理函数的参数// 上半部处理函数原型
irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
// 下半部处理函数原型
irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
// 上半部处理函数
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
/* 上半部:快速处理 */
printk("IRQ handler\r\n");
return IRQ_WAKE_THREAD; /* 唤醒下半部 */
}
// 下半部处理函数(线程上下文)
static irqreturn_t key_thread_fn(int irq, void *dev_id)
{
/* 下半部:可以睡眠 */
msleep(50);
printk("Thread handler\r\n");
return IRQ_HANDLED;
}
// 申请线程化中断
static int __init key_init(void)
{
request_threaded_irq(irq, key_irq_handler, key_thread_fn,
IRQF_TRIGGER_FALLING, "key", dev);
}
线程化中断适用于:
本实验基于I.MX6ULL开发板,使用KEY0按键,通过中断方式检测按键按下和释放。实验代码位于09、文档教程(非常重要)/【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1_拆分/57_第五十一章 Linux中断实验.pdf。
在设备树中添加key节点:
// 示例51.3.1.1 key节点信息
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};
在include/linux/irq.h中定义了中断类型:
// 示例51.3.1.2 中断状态枚举
enum {
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING |
IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW |
IRQ_TYPE_LEVEL_HIGH),
// ...
};
IRQ_TYPE_EDGE_BOTH表示上升沿和下降沿同时有效,相当于KEY0按下和释放都会触发中断。
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void*); /* 中断处理函数 */
};
/* imx6uirq设备结构体 */
struct imx6uirq_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
atomic_t keyvalue; /* 有效的按键值 */
atomic_t releasekey; /* 标记按键是否松开 */
struct timer_list timer; /* 定义一个定时器 */
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述信息数组 */
unsigned char curkeynum; /* 当前按键号 */
};
struct imx6uirq_dev imx6uirq; /* irq设备 */
// 中断处理函数,启动定时器
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
// 定时器处理函数,完成按键消抖
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 松开按键 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
}
// IO初始化函数
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd == NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 获取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,
"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且设置为中断模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0,
sizeof(imx6uirq.irqkeydesc[i].name));
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
gpio_request(imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(
imx6uirq.nd, i);
printk("key%d:gpio=%d, irqnum=%d\r\n", i,
imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
/* 初始化中断 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n",
imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
return 0;
}
static int __init imx6uirq_init(void)
{
/* 1、创建设备号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT,
IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT,
IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注册字符设备 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL,
imx6uirq.devid, NULL,
IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、初始化原子变量 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
/* 删除定时器 */
del_timer_sync(&imx6uirq.timer);
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc[i].gpio);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
// 应用程序 imx6uirqApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc, char *argv[])
{
int fd;
int ret = 0;
char *filename;
unsigned char data;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) { /* 数据读取错误 */
} else {
if (data) /* 读取到数据 */
printf("key value = %#X\r\n", data);
}
}
close(fd);
return ret;
}
编写Makefile文件:
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
# ... 其他内容
obj-m := imx6uirq.o
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译驱动模块:
make -j32
编译成功后会生成imx6uirq.ko驱动模块文件。
arm-linux-gnueabihf-gcc imx6uirqApp.c -o imx6uirqApp
编译成功后会生成imx6uirqApp应用程序。
depmod # 第一次加载时需要执行
modprobe imx6uirq # 加载驱动
cat /proc/interrupts
从中可以看到KEY0的中断信息:
./imx6uirqApp /dev/imx6uirq
按下开发板上的KEY0,终端就会输出按键值。
rmmod imx6uirq.ko
| 特性 | 软中断(Softirq) | Tasklet | 工作队列 | 线程化中断 |
|---|---|---|---|---|
| 执行上下文 | 软中断上下文 | 软中断上下文 | 进程上下文 | 内核线程 |
| 可睡眠 | 否 | 否 | 是 | 是 |
| 并发性 | 同一软中断可并发 | 同一tasklet不可并发 | 可并发 | 可并发 |
| 执行时间 | 极短 | 短 | 可以较长 | 可以较长 |
| 复杂度 | 高 | 低 | 中 | 中 |
| 适用场景 | 高频事件、网络 | 快速处理 | 耗时操作 | 复杂处理 |
| 系统开销 | 最小 | 小 | 较大 | 较大 |
graph TD
A[需要中断下半部?] --> B{需要睡眠?}
B -->|是| C{需要互斥?}
B -->|否| D{处理时间?}
C -->|是| E[线程化中断]
C -->|否| F[工作队列]
D -->|短| G[Tasklet]
D -->|长| H[工作队列]
G --> I{高频事件?}
I -->|是| J[软中断]
I -->|否| K[Tasklet]
| 机制 | 延迟 | 吞吐量 | CPU占用 | 内存占用 |
|---|---|---|---|---|
| 软中断 | 最低 | 最高 | 最低 | 最低 |
| Tasklet | 低 | 高 | 低 | 低 |
| 工作队列 | 中等 | 中等 | 中等 | 中等 |
| 线程化中断 | 较高 | 较低 | 较高 | 较高 |
| 特性 | I.MX6ULL | STM32 | RK3568 |
|---|---|---|---|
| 中断控制器 | GIC-400 | NVIC | GIC-400 |
| 中断优先级 | 256级 | 16级 | 256级 |
| 嵌套中断 | 支持 | 支持 | 支持 |
| 下半部机制 | 软中断/Tasklet/工作队列 | 无 | 软中断/Tasklet/工作队列 |
| 中断号获取 | irq_of_parse_and_map | HAL_NVIC_GetIRQ | irq_of_parse_and_map |
| 设备树支持 | 完整 | 无 | 完整 |
| 特性 | GIC(ARM) | NVIC(Cortex-M) | PLIC(RISC-V) |
|---|---|---|---|
| 中断数量 | 1020+ | 240+ | 1024+ |
| 优先级位数 | 8位 | 4位 | 可配置 |
| 中断嵌套 | 支持 | 支持 | 支持 |
| 软件触发 | 支持 | 不支持 | 支持 |
| 平台 | 中断入口 | 下半部 | 特点 |
|---|---|---|---|
| I.MX6ULL | Linux内核管理 | 软中断/Tasklet/工作队列 | 完整的中断分层机制 |
| STM32(裸机) | 中断向量表 | 无或简单任务切换 | 直接、简单 |
| STM32(FreeRTOS) | 中断向量表 | 任务通知 | 简单的延迟处理 |
| RK3568 | Linux内核管理 | 软中断/Tasklet/工作队列 | 与I.MX6ULL类似 |
考察点: 中断处理机制
参考答案:
| 特性 | 上半部 | 下半部 |
|---|---|---|
| 执行上下文 | 硬中断上下文 | 软中断/进程上下文 |
| 执行时间 | 极短(微秒级) | 可以较长(毫秒级) |
| 可睡眠 | 不可 | 可以(工作队列) |
| 优先级 | 最高 | 较低 |
| 中断屏蔽 | 会屏蔽其他中断 | 不会 |
设计原因:
考察点: 下半部机制
参考答案:
| 特性 | tasklet | workqueue |
|---|---|---|
| 执行上下文 | 软中断 | 进程上下文 |
| 可睡眠 | 不可 | 可以 |
| 并发性 | 同一tasklet不能并发 | 可以并发 |
| 执行效率 | 高 | 较低 |
| 适用场景 | 快速处理 | 耗时操作 |
选择指南:
考察点: 工作队列原理
参考答案:
工作队列通过工作者线程(worker thread)实现:
关键数据结构:
work_struct:表示一个工作workqueue_struct:表示工作队列worker:表示工作者线程考察点: 中断上下文限制
参考答案:
不能。原因:
mutex_lock可能导致睡眠解决方案:
考察点: 设备树中断解析
参考答案:
有两种方法:
使用irq_of_parse_and_map函数:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
dev:设备节点index:中断索引号使用gpio_to_irq函数:
int gpio_to_irq(unsigned int gpio);
gpio:GPIO编号设备树中断属性:
interrupt-parent = <&gpio1>; /* 指定中断控制器 */
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* 中断信息 */
interrupts属性格式取决于中断控制器,对于GIC:
代码来源: Linux驱动例程 13_irq 最后更新: 2026-09-17