中断的上半部与下半部

Linux内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,我们在使用request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。

上半部:上半部就是中断处理函数,那些处理过程比较快不会占用很长时间的处理就可以放在上半部完成。

下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

可以参考:

  • 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
  • 如果要处理的任务对时间敏感,可以放到上半部。
  • 如果要处理的任务与硬件有关,可以放到上半部
  • 除了上述三点以外的其他任务,优先考虑放到下半部

软中断

Linux 内核使用结构体softirq_action 表示软中断, softirq_action结构体定义在文include/linux/interrupt.h中softirq_action

1
2
3
4
struct softirq_action
{
void (*action)(struct softirq_action *);
};

在/include/linux/interrupt.h 文件中一共定义了10 个软中断,如下所示:interrupt.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS
};

数组softirq_vec 有10 个元素。softirq_action结构体中的action成员变量就是软中断的服务函数,数组softirq_vec是个全局数组,因此所有的CPU(对于SMP系统而言)都可以访问到,每个CPU都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个CPU所执行的软中断服务函数确是相同的,都是数组softirq_vec 中定义的action函数。

特点:

  • 执行上下文软中断上下文。处于中断退出阶段,不属于任何进程。

  • 抢占与睡眠不能睡眠/阻塞(因为不属于进程,没有任务结构体去调度)。执行时开中断,可以被新的硬件中断打断,但不能被自身嵌套(同一个CPU上同类型软中断不会重入)。

  • 并发性所有CPU上都可以同时运行,即使是同一种类型的软中断。这意味着开发者必须自己处理复杂的锁机制(如自旋锁)来保护共享数据,编程难度高。

  • 执行延迟延迟极低。一旦中断退出,会立刻检查并执行pending的软中断,响应非常快。

  • 静态分配:内核编译时静态定义(如 HI_SOFTIRQ, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ),数量有限,不能动态注册。

  • 机制:通过 raise_softirq() 触发,在 irq_exit() 中检查并执行。

  • 性能要求极高执行频率非常高的场景。

  • 典型代表:网络子系统(收发数据包)、块设备子系统(IO调度)。

  • 内核定时器的底半部 (TIMER_SOFTIRQ) 也使用它。

软中断API函数

注册软中断

1
void open_softirq(int nr, void (*action)(struct softirq_action *));
  • 参数
    • nr:软中断号(0-9),例如:TASKLET_SOFTIRQHI_SOFTIRQ
    • action:软中断处理函数指针
  • 说明:注册一个软中断类型及其处理函数

触发软中断

1
void raise_softirq(unsigned int nr);
  • 参数
    • nr:要触发的软中断号
  • 说明:在适当的时候(通常是中断返回时),内核会检查并执行已触发的软中断

实验三

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>

// 自定义软中断号 (需要在已定义的软中断之后)
#define MY_SOFTIRQ 11

// 设备结构体
struct my_device {
char *name;
// 其他设备相关数据
struct softirq_data *softirq_data;
};

// 软中断共享的数据结构
struct softirq_data {
unsigned int count;
unsigned long jiffies;
};

static struct my_device my_dev;
static struct softirq_data *data;

// 软中断处理函数
static void my_softirq_handler(struct softirq_action *action)
{
struct softirq_data *local_data = data;

// 在软中断上下文中处理数据
printk(KERN_INFO "My softirq handler running on CPU %d, count: %u, jiffies: %lu\n",
smp_processor_id(), local_data->count, local_data->jiffies);

// 模拟一些处理工作
local_data->count++;
local_data->jiffies = jiffies;
}

// 硬件中断处理函数(顶半部)
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = (struct my_device *)dev_id;

// 快速处理紧急任务
printk(KERN_INFO "IRQ handler running on CPU %d\n", smp_processor_id());

// 更新要传递给软中断的数据
data->count = 1;
data->jiffies = jiffies;

// 触发软中断
raise_softirq(MY_SOFTIRQ);

return IRQ_HANDLED;
}

// 模块初始化函数
static int __init my_softirq_init(void)
{
int ret;

printk(KERN_INFO "Initializing my softirq module\n");

// 分配软中断数据结构
data = kmalloc(sizeof(struct softirq_data), GFP_KERNEL);
if (!data) {
printk(KERN_ERR "Failed to allocate softirq data\n");
return -ENOMEM;
}

// 初始化设备结构
my_dev.name = "my_softirq_device";
my_dev.softirq_data = data;

// 注册软中断
open_softirq(MY_SOFTIRQ, my_softirq_handler);
printk(KERN_INFO "Registered softirq %d\n", MY_SOFTIRQ);

// 在实际驱动中,这里会请求硬件IRQ
// 为了示例,我们假设IRQ号为10
ret = request_irq(10, my_irq_handler, IRQF_SHARED,
"my_softirq_device", &my_dev);
if (ret) {
printk(KERN_ERR "Failed to request IRQ\n");
kfree(data);
return ret;
}

return 0;
}

// 模块退出函数
static void __exit my_softirq_exit(void)
{
printk(KERN_INFO "Exiting my softirq module\n");

// 释放IRQ
free_irq(10, &my_dev);

// 释放分配的内存
kfree(data);
}

module_init(my_softirq_init);
module_exit(my_softirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("kevin");
MODULE_DESCRIPTION("Example of using softirq in a driver");

tasklet

Tasklet 是一种软中断(SoftIRQ) 机制,用于处理中断下半部(bottom half)的任务。它运行在中断上下文(但不在硬中断处理函数中),具有以下特点:

特点:

  • 执行上下文软中断上下文(基于 HI_SOFTIRQTASKLET_SOFTIRQ 两种软中断实现)。
  • 抢占与睡眠不能睡眠/阻塞,原因同软中断。
  • 并发性:这是与软中断的关键区别同一种类的tasklet在多个CPU上是串行执行的。一个tasklet一旦在某CPU上被调度执行,其他CPU不会同时执行同一种类的tasklet。这大大降低了并发编程的复杂度,通常不需要考虑复杂的锁。
  • 执行延迟:延迟低,与软中断类似,机制相同。
  • 动态分配:可以动态创建和初始化,使用灵活。
  • 机制:通过 tasklet_schedule() 调度。

使用场景:

  • 需要延后执行、频率较高不需要复杂并发控制的设备驱动程序。
  • 大多数设备驱动程序首选的底半部机制(除非性能要求极高到必须用软中断)。
  • 典型例子:按键中断鼠标中断等。将读取数据等操作放在顶半部,将数据处理和报告事件的工作放在tasklet中。
image-20250903221103956

API函数

函数/宏 说明
DECLARE_TASKLET(name, _callback) 声明并初始化一个 tasklet
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) 动态初始化一个 tasklet
void tasklet_schedule(struct tasklet_struct *t) 调度一个普通优先级的 tasklet
void tasklet_hi_schedule(struct tasklet_struct *t) 调度一个高优先级的 tasklet
void tasklet_kill(struct tasklet_struct *t) 杀死(取消)一个 tasklet

实验四

本次实验是基于实验一中的代码进行编写,我们首先分析需要将哪部分的代码放在中断的下半部分,按键消抖和状态检测不需要在中断上半部立即执行,因为它们非紧急处理

主要的代码分析:

1
2
3
4
struct tasklet_struct tasklet;
static void key_tasklet_func(unsigned long data){}
tasklet_init(&dev->tasklet, key_tasklet_func, (unsigned long)dev);//初始化
tasklet_schedule(&dev->tasklet);//中断服务函数的最后调用

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h> /* 新增:软中断相关头文件 */
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */

/* 中断IO描述结构体 */
struct irq_keydesc {
struct gpio_desc *gpiod; /* 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 platform_device *pdev; /* platform设备 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
struct tasklet_struct tasklet; /* 新增:tasklet软中断结构体 */
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};

/* 新增:软中断处理函数,替代原来的定时器函数 */
static void key_tasklet_func(unsigned long data)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)data;

num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];

/* 延时一小段时间进行消抖 */
udelay(10000); /* 10ms延时 */

value = gpiod_get_value(keydesc->gpiod); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
}

/* @description : 中断服务函数,触发软中断处理
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

dev->curkeynum = 0;

/* 触发软中断,替代原来的定时器 */
tasklet_schedule(&dev->tasklet);

return IRQ_RETVAL(IRQ_HANDLED);
}

/*
* @description : 按键IO初始化
* @param - dev : 设备结构体
* @return : 0 成功;其他 失败
*/
static int keyio_init(struct imx6uirq_dev *dev)
{
unsigned char i = 0;
int ret = 0;
struct device *pdev = &dev->pdev->dev;

/* 使用devm_gpiod_get获取GPIO描述符 */
dev->irqkeydesc[0].gpiod = devm_gpiod_get(pdev, "key", GPIOD_IN);
if (IS_ERR(dev->irqkeydesc[0].gpiod)) {
dev_err(pdev, "can't get key gpio\n");
return PTR_ERR(dev->irqkeydesc[0].gpiod);
}

/* 使用platform_get_irq获取中断号 */
dev->irqkeydesc[0].irqnum = platform_get_irq(dev->pdev, 0);
if (dev->irqkeydesc[0].irqnum < 0) {
dev_err(pdev, "can't get key irq number\n");
return dev->irqkeydesc[0].irqnum;
}

/* 初始化key描述 */
sprintf(dev->irqkeydesc[0].name, "KEY0");
dev->irqkeydesc[0].handler = key0_handler;
dev->irqkeydesc[0].value = KEY0VALUE;

printk("key0: gpio=%d, irqnum=%d\r\n",
desc_to_gpio(dev->irqkeydesc[0].gpiod),
dev->irqkeydesc[0].irqnum);

/* 申请中断 */
ret = devm_request_irq(pdev, dev->irqkeydesc[0].irqnum,
dev->irqkeydesc[0].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
dev->irqkeydesc[0].name, dev);
if (ret < 0) {
dev_err(pdev, "irq %d request failed!\r\n",
dev->irqkeydesc[0].irqnum);
return ret;
}

/* 初始化tasklet软中断,替代原来的定时器初始化 */
tasklet_init(&dev->tasklet, key_tasklet_func, (unsigned long)dev);

return 0;
}

/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
struct imx6uirq_dev *dev = container_of(inode->i_cdev, struct imx6uirq_dev, cdev);
filp->private_data = dev; /* 设置私有数据 */
return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;

data_error:
return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};

/*
* @description : platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行
* @param - pdev: platform设备
* @return : 0,成功;其他负值,失败
*/
static int imx6uirq_probe(struct platform_device *pdev)
{
struct imx6uirq_dev *dev;
int ret = 0;

/* 1、分配设备结构体 */
dev = devm_kzalloc(&pdev->dev, sizeof(struct imx6uirq_dev), GFP_KERNEL);
if (!dev) {
return -ENOMEM;
}

dev->pdev = pdev;

/* 2、构建设备号 */
if (dev->major) {
dev->devid = MKDEV(dev->major, 0);
ret = register_chrdev_region(dev->devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
ret = alloc_chrdev_region(&dev->devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
dev->major = MAJOR(dev->devid);
dev->minor = MINOR(dev->devid);
}
if (ret < 0) {
goto fail_devid;
}

/* 3、注册字符设备 */
cdev_init(&dev->cdev, &imx6uirq_fops);
dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&dev->cdev, dev->devid, IMX6UIRQ_CNT);
if (ret < 0) {
goto fail_cdev;
}

/* 4、创建类 */
dev->class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(dev->class)) {
ret = PTR_ERR(dev->class);
goto fail_class;
}

/* 5、创建设备 */
dev->device = device_create(dev->class, NULL, dev->devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(dev->device)) {
ret = PTR_ERR(dev->device);
goto fail_device;
}

/* 6、初始化按键 */
atomic_set(&dev->keyvalue, INVAKEY);
atomic_set(&dev->releasekey, 0);
ret = keyio_init(dev);
if (ret < 0) {
goto fail_keyinit;
}

/* 7、保存设备结构体到平台设备中 */
platform_set_drvdata(pdev, dev);

dev_info(&pdev->dev, "imx6uirq driver probe success\n");
return 0;

fail_keyinit:
device_destroy(dev->class, dev->devid);
fail_device:
class_destroy(dev->class);
fail_class:
cdev_del(&dev->cdev);
fail_cdev:
unregister_chrdev_region(dev->devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}

/*
* @description : platform驱动的remove函数,移除platform驱动时此函数会执行
* @param - pdev: platform设备
* @return : 0,成功;其他负值,失败
*/
static int imx6uirq_remove(struct platform_device *pdev)
{
struct imx6uirq_dev *dev = platform_get_drvdata(pdev);

/* 销毁tasklet软中断,替代原来的删除定时器 */
tasklet_kill(&dev->tasklet);

/* 注销字符设备 */
cdev_del(&dev->cdev);
unregister_chrdev_region(dev->devid, IMX6UIRQ_CNT);
device_destroy(dev->class, dev->devid);
class_destroy(dev->class);

return 0;
}

/* 匹配列表 */
static const struct of_device_id imx6uirq_of_match[] = {
{ .compatible = "kevin,key" },
{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, imx6uirq_of_match);

/* platform驱动结构体 */
static struct platform_driver imx6uirq_driver = {
.driver = {
.name = "imx6uirq",
.of_match_table = imx6uirq_of_match,
},
.probe = imx6uirq_probe,
.remove = imx6uirq_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
return platform_driver_register(&imx6uirq_driver);
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
platform_driver_unregister(&imx6uirq_driver);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kevin");

工作队列

工作队列(Workqueue) 就是一种非常常用的将工作推后到进程上下文中执行的方法。

它的核心思想是:

  1. 你把一个想要推后执行的任务(函数)包装成一个工作(work)

  2. 将这个工作提交到一个队列(queue) 中。

  3. 内核会有一个或多个专用的内核线程(称为工作者线程,worker thread)被创建来处理这个队列。

  4. 当线程被调度时,它会从队列中取出工作并执行其中指定的函数。

    1
    你的工作 (work) ---> 被加入 ---> 工作队列 (workqueue) ---> 由 ---> 工作者线程 (worker thread) 处理

特点:

  • 执行上下文进程上下文。由专门的内核线程(kworker)来执行其工作函数。
  • 抢占与睡眠可以睡眠/阻塞!这是它与前两者最根本的区别。因为它运行在进程上下文,可以被调度器抢占,也可以调用schedule()主动让出CPU,或者等待信号量、分配内存时阻塞。
  • 并发性:默认情况下,工作项(work)会被排入一个全局队列,由某个kworker线程执行,并发性由锁控制。也有并发工作队列(CMWQ)等更先进的机制来精细控制并发度。
  • 执行延迟延迟相对较高。因为它需要唤醒一个内核线程,然后由调度器来分配CPU时间片,这比直接在中退出路径上执行的软中断/ tasklet 要慢。
  • 动态分配:可以动态创建。可以创建自己的专用工作队列线程,也可以使用内核共享的默认工作队列。
  • 机制:通过 schedule_work() 调度到默认队列,或 queue_work() 到自定义队列。

使用场景:

  • 需要睡眠的任务。这是选择工作队列的最决定性因素
  • 需要执行大量、耗时操作的任务(如大数据块IO、文件系统操作)。
  • 典型例子:驱动需要与用户空间交互(这可能需要等待)、需要分配可能阻塞的内存(GFP_KERNEL)、需要等待磁盘IO完成。

工作队列的运行模式

  • 单线程式 (Single-threaded):所有提交到某个工作队列的工作都由同一个内核线程处理。这意味着工作是串行执行的,一个接一个。

    image-20250903222052475

  • 多线程式 (Multi-threaded):工作可以由多个内核线程处理(例如,每个CPU一个线程,或者为不同类型的任务创建不同优先级的线程池)。这允许工作在多个CPU上并发执行,提高了吞吐量。

    image-20250903222104650

工作队列的弊端

工作项(work item)的延迟执行和阻塞

image-20250903222213819

场景分析:

  • 有三个工作项 w0, w1, w2 被加入到同一个工作队列(绑定到同一个CPU)。
  • w0 先执行:
    • 0-5ms: 占用CPU(执行计算)。
    • 5-15ms: 睡眠(sleep)。这是关键!因为它运行在同一个工作者线程中,所以当 w0 睡眠时,整个线程都被挂起了。
    • 15-20ms: 醒来继续占用CPU直到结束。
  • w020ms 结束后,线程才开始处理 w1
  • 同理,w1 的睡眠又阻塞了 w2 的执行

结论:因为 w0, w1, w2 共享同一个执行线程,所以任何一个工作的睡眠或长时间操作都会阻塞后续所有工作的执行。这会导致其他工作的处理产生不可预测的延迟(latency)

工作队列的API函数

函数/宏 参数 作用 返回值/注意事项
DECLARE_WORK(name, func) name: struct work_struct 变量名 func: 要执行的下半部函数 (void (*func)(struct work_struct *work)) 静态地声明并初始化一个工作结构体。通常在文件全局或结构体内部使用。 宏。声明一个名为 name 的 work_struct。
INIT_WORK(work, func) work: 指向已存在的 struct work_struct 的指针 func: 下半部函数 动态地初始化一个已经分配好的工作结构体(例如,在你自己定义的设备结构体中)。 宏。通常在 kmallocdevm_kzalloc 分配内存后调用。
schedule_work(work) work: 指向已初始化的 struct work_struct 的指针 将工作提交到系统共享的全局工作队列(system_wq)。工作者线程会在稍后某个时间执行它。 返回: int 0: 工作已在队列中,未重复添加。 非 0: 工作已成功添加到队列等待执行。
cancel_work_sync(work) work: 指向要取消的 struct work_struct 的指针 取消一个已排队但尚未开始执行的工作。如果工作已经开始执行,则函数会等待其执行完毕。防止竞争的必要措施。 注意: 必须确保在之后工作不会再被调度(例如,在模块卸载或设备断开时调用)。不能在中断上下文使用。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 定义工作(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);
......
}

延迟工作队列

api函数

函数/宏 参数 作用 返回值/注意事项
DECLARE_DELAYED_WORK(name, func) name: struct delayed_work 变量名 func: 下半部函数 静态地声明并初始化一个延迟工作结构体。 宏。struct delayed_work 内部包含了一个普通的 work_struct 和一个定时器。
INIT_DELAYED_WORK(work, func) work: 指向已存在的 struct delayed_work 的指针 func: 下半部函数 动态地初始化一个已经分配好的延迟工作结构体。 宏。
schedule_delayed_work(dwork, delay) dwork: 指向已初始化的 struct delayed_work 的指针 delay: 延迟的时间,单位是 jiffies 将工作提交到系统共享的全局工作队列,并指定一个延迟。工作者线程会在 delay 时间之后才执行它。 返回: int 0: 失败(工作已在队列中)。 非 0: 成功。 提示: 可用 msecs_to_jiffies(ms) 将毫秒转换为 jiffies。
flush_delayed_work(dwork) dwork: 指向 struct delayed_work 的指针 刷新延迟工作。等待一个已经排队的延迟工作执行完毕。如果延迟尚未到期,它会先取消延迟,然后立即将工作放入队列执行,并等待其完成。 返回: bool false: 工作未在执行,已被刷新。 true: 工作正在执行,函数等待其完成。
cancel_delayed_work_sync(dwork) dwork: 指向要取消的 struct delayed_work 的指针 取消一个已排队的延迟工作。如果工作已经在执行,则等待其执行完毕。防止竞争的必要措施。 cancel_work_sync 类似,但用于延迟工作。

实验五

本实验是在实验一的基础上进行改进的,

  1. 按键消抖本质上就是一个需要延迟执行的任务
  2. 使用delayed_work可以直接替代定时器,代码更简洁
  3. 内核的延时工作队列机制已经优化得很好,性能足够
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h> // 可以删除,但保留以防其他依赖
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/workqueue.h> // 添加工作队列头文件
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */

/* 中断IO描述结构体 */
struct irq_keydesc {
struct gpio_desc *gpiod; /* 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 platform_device *pdev; /* platform设备 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键,包括按下和释放 */
struct delayed_work key_work; /* 按键消抖工作项 */ // 替换 timer_list
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};

/* @description : 工作队列处理函数,用于按键消抖
* @param - work: 工作项
* @return : 无
*/
static void key_work_handler(struct work_struct *work)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = container_of(to_delayed_work(work),
struct imx6uirq_dev, key_work);

num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];

value = gpiod_get_value(keydesc->gpiod); /* 读取IO值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
}

/* @description : 中断服务函数,调度延迟工作队列进行按键消抖
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

dev->curkeynum = 0;
schedule_delayed_work(&dev->key_work, msecs_to_jiffies(10)); /* 10ms后执行工作队列 */
return IRQ_RETVAL(IRQ_HANDLED);
}

/*
* @description : 按键IO初始化
* @param - dev : 设备结构体
* @return : 0 成功;其他 失败
*/
static int keyio_init(struct imx6uirq_dev *dev)
{
unsigned char i = 0;
int ret = 0;
struct device *pdev = &dev->pdev->dev;

/* 使用devm_gpiod_get获取GPIO描述符 */
dev->irqkeydesc[0].gpiod = devm_gpiod_get(pdev, "key", GPIOD_IN);
if (IS_ERR(dev->irqkeydesc[0].gpiod)) {
dev_err(pdev, "can't get key gpio\n");
return PTR_ERR(dev->irqkeydesc[0].gpiod);
}

/* 使用platform_get_irq获取中断号 */
dev->irqkeydesc[0].irqnum = platform_get_irq(dev->pdev, 0);
if (dev->irqkeydesc[0].irqnum < 0) {
dev_err(pdev, "can't get key irq number\n");
return dev->irqkeydesc[0].irqnum;
}

/* 初始化key描述 */
sprintf(dev->irqkeydesc[0].name, "KEY0");
dev->irqkeydesc[0].handler = key0_handler;
dev->irqkeydesc[0].value = KEY0VALUE;

/* 初始化延迟工作队列 */
INIT_DELAYED_WORK(&dev->key_work, key_work_handler);

printk("key0: gpio=%d, irqnum=%d\r\n",
desc_to_gpio(dev->irqkeydesc[0].gpiod),
dev->irqkeydesc[0].irqnum);

/* 申请中断 */
ret = devm_request_irq(pdev, dev->irqkeydesc[0].irqnum,
dev->irqkeydesc[0].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
dev->irqkeydesc[0].name, dev);
if (ret < 0) {
dev_err(pdev, "irq %d request failed!\r\n",
dev->irqkeydesc[0].irqnum);
return ret;
}

return 0;
}

/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
struct imx6uirq_dev *dev = container_of(inode->i_cdev, struct imx6uirq_dev, cdev);
filp->private_data = dev; /* 设置私有数据 */
return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;

data_error:
return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};

/*
* @description : platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行
* @param - pdev: platform设备
* @return : 0,成功;其他负值,失败
*/
static int imx6uirq_probe(struct platform_device *pdev)
{
struct imx6uirq_dev *dev;
int ret = 0;

/* 1、分配设备结构体 */
dev = devm_kzalloc(&pdev->dev, sizeof(struct imx6uirq_dev), GFP_KERNEL);
if (!dev) {
return -ENOMEM;
}

dev->pdev = pdev;

/* 2、构建设备号 */
if (dev->major) {
dev->devid = MKDEV(dev->major, 0);
ret = register_chrdev_region(dev->devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
ret = alloc_chrdev_region(&dev->devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
dev->major = MAJOR(dev->devid);
dev->minor = MINOR(dev->devid);
}
if (ret < 0) {
goto fail_devid;
}

/* 3、注册字符设备 */
cdev_init(&dev->cdev, &imx6uirq_fops);
dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&dev->cdev, dev->devid, IMX6UIRQ_CNT);
if (ret < 0) {
goto fail_cdev;
}

/* 4、创建类 */
dev->class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(dev->class)) {
ret = PTR_ERR(dev->class);
goto fail_class;
}

/* 5、创建设备 */
dev->device = device_create(dev->class, NULL, dev->devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(dev->device)) {
ret = PTR_ERR(dev->device);
goto fail_device;
}

/* 6、初始化按键 */
atomic_set(&dev->keyvalue, INVAKEY);
atomic_set(&dev->releasekey, 0);
ret = keyio_init(dev);
if (ret < 0) {
goto fail_keyinit;
}

/* 7、保存设备结构体到平台设备中 */
platform_set_drvdata(pdev, dev);

dev_info(&pdev->dev, "imx6uirq driver probe success\n");
return 0;

fail_keyinit:
device_destroy(dev->class, dev->devid);
fail_device:
class_destroy(dev->class);
fail_class:
cdev_del(&dev->cdev);
fail_cdev:
unregister_chrdev_region(dev->devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}

/*
* @description : platform驱动的remove函数,移除platform驱动时此函数会执行
* @param - pdev: platform设备
* @return : 0,成功;其他负值,失败
*/
static int imx6uirq_remove(struct platform_device *pdev)
{
struct imx6uirq_dev *dev = platform_get_drvdata(pdev);

/* 取消延迟工作队列 */
cancel_delayed_work_sync(&dev->key_work);

/* 注销字符设备 */
cdev_del(&dev->cdev);
unregister_chrdev_region(dev->devid, IMX6UIRQ_CNT);
device_destroy(dev->class, dev->devid);
class_destroy(dev->class);

return 0;
}

/* 匹配列表 */
static const struct of_device_id imx6uirq_of_match[] = {
{ .compatible = "kevin,key" },
{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, imx6uirq_of_match);

/* platform驱动结构体 */
static struct platform_driver imx6uirq_driver = {
.driver = {
.name = "imx6uirq",
.of_match_table = imx6uirq_of_match,
},
.probe = imx6uirq_probe,
.remove = imx6uirq_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
return platform_driver_register(&imx6uirq_driver);
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
platform_driver_unregister(&imx6uirq_driver);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kevin");

CMWQ工作队列

Concurrency Managed Workqueue,即并发托管的工作队列

CMWQ 的核心思想是将工作项的提交(生产者)与工作线程的管理(消费者)完全分离。开发者只需关心将工作项提交到哪个工作队列,而内核则负责以最优化、最高效的方式动态管理执行这些工作项所需的线程(称为 worker)及其并发度。

image-20250903225324180

目的:

  • 线程浪费:每个工作队列(workqueue)都有自己的专用线程(kworker)。如果系统中有很多驱动都创建了自己的工作队列,就会产生大量大部分时间都在休眠的线程,浪费资源。
  • 并发性差:传统的每CPU工作队列,其并发度是固定的(通常是每CPU一个线程)。如果一个工作项阻塞(如工作队列小节的图片中 w0 睡眠),那么该CPU上该队列的所有后续工作项都会被阻塞,即使系统有其他空闲的CPU核心也无法帮忙处理。

特点:

  1. 线程池化(Thread Pooling)
    • 内核预先创建好一组通用的工作线程(kworker/uX:Ykworker/uX:Y),称为 worker_pool
    • 所有通过 CMWQ API(如 schedule_work() )提交的工作项,最终都由这个共享的线程池来处理。
    • 好处:极大地减少了系统内核线程的数量,节省了内存和上下文切换的开销。
  2. 动态并发管理(Dynamic Concurrency Management)
    • 这是“并发托管”中“托管”的含义。内核会自动管理执行工作项所需的线程数量。
    • 规则:只要有一个可运行(runnable)的工作项,内核就会尝试提供一个空闲的线程来执行它。如果没有空闲线程,内核可以根据需要动态创建新的线程
    • 好处
      • 避免阻塞:如果一个工作线程在某个工作项上阻塞(比如调用了 msleep),内核会检测到该线程休眠,并立即唤醒或创建另一个线程来执行同一队列上的其他工作项。这直接解决了你图片中 w0 阻塞 w1w2 的问题。
      • 自动扩展:在高负载下,线程池会扩大以处理更多工作;在空闲时,多余的线程会被自动回收。
  3. 向后兼容的API
    • 最棒的一点是,开发者几乎不需要修改现有代码。像 schedule_work(), INIT_WORK() 这样的API保持不变,但它们底层已经使用了CMWQ机制。
    • 旧的函数如 create_workqueue() 被重写为基于CMWQ的兼容层,但建议使用新的 alloc_workqueue() 函数来显式创建具有特定属性(如并发度限制)的工作队列。
特性 旧工作队列 (Pre-CMWQ) CMWQ (并发托管工作队列)
线程模型 每工作队列专用线程 共享线程池
线程数量 静态,与工作队列数量成正比 动态,根据负载扩展和收缩
并发管理 固定(通常每CPU一个线程) 自动、动态管理,避免饥饿和阻塞
资源效率 低效,线程可能大量空闲 高效,线程被充分共享和利用
API create_workqueue() alloc_workqueue()兼容旧API
解决阻塞 无法解决,一个阻塞会阻塞整个队列 完美解决,阻塞后自动创建新线程
deepseek_mermaid_20250903_266727
  1. 工作队列(workqueue)层 - 生产者接口

    • 这是开发者直接交互的层面。工作队列(如 system_wq, system_highpri_wq 或用户用 alloc_workqueue() 创建的队列)充当工作项(work_struct)的容器。
    • 每个工作队列可以定义自己的属性,最最重要的是 max_active,它限制了该工作队列每个CPU上最多可以同时执行的工作项数量。这提供了对并发度的精细控制。
  2. worker_pool 层 - 核心资源管理

    • 这是CMWQ的“大脑”。内核为每个CPU(和一些特殊场景)创建了一组 worker_pool
    • 绑定到特定CPU的worker_pool: 这是最常见的类型。例如,每个CPU都有两个worker_pool:一个用于普通优先级工作(kworker/uX:Y),一个用于高优先级工作(kworker/uX:Y)。
    • Unbound worker_pool: 用于那些不需要绑定特定CPU、且可能长时间运行的任务。它们的线程可以运行在任何CPU上,名字如 kworker/uX:Y
    • 工作项最终会被派发到某个worker_pool。例如,通过 schedule_work() 提交的普通工作,会被派发到当前CPU的普通优先级worker_pool。
  3. 工作者线程(worker)层 - 消费者执行单元

    • 这是真正执行代码的线程,即 kworker 线程。
    • 它们由内核动态地从 worker_pool 中管理。当一个worker_pool中有工作项需要处理时:
      • 如果有空闲的worker线程,它会被唤醒去处理工作。
      • 如果没有空闲worker且当前活跃worker数未达到上限,内核会创建一个新的worker线程。
      • 如果一个worker线程在执行工作项时进入睡眠(阻塞),内核会立即感知到,并可能创建新的worker来处理同一worker_pool中的其他工作项(这就是解决阻塞问题的关键)。
      • 当worker处理完工作后,它会保持一段时间空闲。如果一直没新工作,它会被内核自动销毁,从而释放资源。
    image-20250903224808412 image-20250903224825722

    通过图片我们可以知道:

  4. 用户通过 alloc_workqueue 创建工作队列时,需要指定 flags 参数(如 WQ_UNBOUND, WQ_HIGHPRI)。

  5. 这个 flags 参数决定了该工作队列中的工作项将被分配到哪种类型的线程池(前端与后端的绑定)。

  6. max_active:这是一个极其重要的参数。它限制了每个 CPU 上可以同时并发执行(处于 running 状态)的该工作队列的工作项数量。它实现了对并发度的精细控制。

  7. 这展示了 CMWQ 将 max_active 设置为大于等于工作项数量时的效果。内核线程池会提供足够的线程,使得三个工作项几乎完全并发地执行:

    • w0 睡眠时,w1w2 立即开始运行。
    • 所有工作项在 25ms 内完成,效率提升一倍!

中断线程

中断线程化(Threaded IRQs) 是一种将中断处理程序(Interrupt Handler)的大部分工作转移到内核线程中执行的技术。它将一个中断的处理分为两部分:

  1. 硬中断处理程序(Hard IRQ Handler):在中断上下文中执行,要求快速、不可休眠。它只负责最紧急的任务(如读取状态寄存器、清除中断标志),然后通知内核需要调度对应的线程。
  2. 中断线程(Threaded IRQ Handler):在进程上下文中执行,作为一个独立的内核线程运行。它负责处理中断大部分耗时的、可能休眠的操作。

简单来说,中断线程化就是把一个中断“转换”成了一个由中断信号触发的、特殊的内核线程。

特点:

  • 本质:它不是传统意义上的底半部,而是一种全新的中断处理模型。它将整个中断处理过程(相当于顶半部+底半部)都放到了一个内核线程中执行。
  • 执行上下文进程上下文
  • 抢占与睡眠可以睡眠/阻塞(在线程化的handler部分)。
  • 并发性:每个中断线程可以有自己的调度策略和优先级。
  • 执行延迟:顶半部变得极短(只是一个唤醒线程的信号),真正的处理在线程中完成,延迟高于软中断/tasklet。
  • 优先级:可以给中断线程设置实时优先级,从而提供确定性的响应,这对实时系统(PREEMPT_RT)至关重要。
  • 机制:使用 request_threaded_irq() 申请中断,并指定一个“线程化处理函数”。

使用场景:

  • 实时系统 (PREEMPT_RT Patch):其主要目标就是将所有中断线程化,以减少关中断时间,提供更好的系统实时性。
  • 需要为中断处理提供明确的调度优先级
  • 简化驱动设计,整个中断处理流程都可以在一个可以睡眠的上下文中完成,无需再区分顶半部和底半部。

api函数

核心函数:request_threaded_irq

1
2
3
4
5
6
int request_threaded_irq(unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags,
const char *name,
void *dev);
参数 类型 描述
irq unsigned int 要申请的中断号。
handler irq_handler_t (硬中断处理函数) 在中断上下文中运行的函数。它的职责是进行最快速的处理,然后决定是否需要唤醒中断线程。
thread_fn irq_handler_t (中断线程处理函数) 在进程上下文(内核线程)中运行的函数。负责处理中断的主要工作。
flags unsigned long 中断标志。与 request_irq 的标志类似,但有一些用于线程化中断的特殊标志。
name const char * 中断名称,会在 /proc/interrupts 中显示。同时也是中断线程名的前缀。
dev void * 传递给处理程序的设备标识符,通常是设备结构体的指针。

handlerthread_fn 的返回值

这两个函数的返回值都是 irqreturn_t 类型:

  • IRQ_NONE: 表示这不是本设备发出的中断,未处理。
  • IRQ_HANDLED: 表示中断已处理。
  • IRQ_WAKE_THREAD: 这是关键handler 函数如果返回此值,内核就会唤醒对应的 thread_fn 线程来执行。如果 handlerNULL,内核会默认返回 IRQ_WAKE_THREAD

实验六

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <linux/interrupt.h>

struct my_device {
struct work_struct work;
// ... 其他设备数据 ...
};

/* 硬中断处理函数(可选,如果为NULL则默认唤醒线程) */
static irqreturn_t my_hardirq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;

/* 1. 快速读取状态寄存器确认中断 */
/* 2. 清除中断标志(非常重要!) */
/* 3. 如果需要线程处理,返回 IRQ_WAKE_THREAD */
return IRQ_WAKE_THREAD;
}

/* 中断线程处理函数(主要工作在这里完成) */
static irqreturn_t my_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;

/* 这里可以处理复杂逻辑,甚至可以休眠! */
// msleep(10); // 这是允许的!
// mutex_lock(&my_mutex); // 这也是允许的!

printk(KERN_INFO "Processing interrupt in thread context for device\n");
return IRQ_HANDLED;
}

static int my_probe(struct platform_device *pdev)
{
struct my_device *dev;
int irq, ret;

// ... 设备初始化 ...

/* 申请线程化中断 */
ret = request_threaded_irq(irq,
my_hardirq_handler, // 硬中断处理函数
my_thread_fn, // 线程处理函数
IRQF_ONESHOT, // 必须的标志
"my-threaded-irq",
dev);
if (ret) {
dev_err(&pdev->dev, "Failed to request threaded IRQ\n");
return ret;
}

return 0;
}

工作流程

  1. 硬件中断发生,CPU 跳转到对应的中断向量。
  2. 内核调用为该中断号注册的硬中断处理程序 (handler)
  3. handler 函数执行(在中断上下文):
    • 读取硬件状态,确认是本设备中断。
    • 清除中断源(防止再次触发)。
    • 返回 IRQ_WAKE_THREAD
  4. 内核唤醒与该中断关联的内核线程
  5. 该内核线程开始执行中断线程处理程序 (thread_fn)(在进程上下文):
    • 执行所有耗时操作(如处理数据、唤醒进程、访问可能休眠的硬件等)。
    • 返回 IRQ_HANDLED

总结

特性 软中断 (Softirq) Tasklet 工作队列 (Workqueue) 中断线程 (Threaded IRQ)
执行上下文 软中断上下文 软中断上下文 进程上下文 进程上下文
可否睡眠
并发性 高,同类型可多CPU并发 低,同类型串行执行 可配置,默认共享 每个中断一个线程
执行延迟 极低 较高 较高(但响应确定)
编程复杂度 (需处理自旋锁) 低(无需复杂锁) 低(可使用睡眠锁) 低(可使用睡眠锁)
机制 静态,编译时定义 动态,基于软中断 动态,内核线程执行 动态,内核线程执行
典型应用 网络、块设备、定时器 大多数设备驱动 可睡眠的耗时任务 实时系统,需优先级调度