12、中断下半部:中断线程化(推荐)*
一、中断线程化使用
以前处理中断时,会用一个工作线程来排队处理所有中断任务。但这个工作线程只能在一颗CPU上运行,不管有多少中断请求,都只能由它一个线程处理。在单核电脑里,这无可奈何。但在多核电脑中明明有很多CPU闲置,却让所有中断任务都挤在同一颗CPU上处理,这显然不合理,白白浪费了其他CPU的计算能力。
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)
2
3
4
- 以下是更直白的改写版本:
中断处理分为两个部分:
- 上半部(@handler函数) 这是直接响应硬件中断的函数,会在中断发生时立即运行。它的任务是:
- 快速完成紧急处理(比如记录关键数据)
- 如果能在0.1毫秒内完成,直接处理完毕并返回"IRQ_HANDLED"
- 如果需要更长时间(超过0.1毫秒),则返回"IRQ_WAKE_THREAD"触发下半部
- 下半部(@thread_fn函数) 当上半部返回"IRQ_WAKE_THREAD"时,系统会启动一个内核线程来执行这个函数。它负责:
- 完成耗时的处理工作(比如数据传输或复杂计算)
- 必须在处理完成后返回"IRQ_HANDLED"
- 只有在上半部再次触发时才会重新运行
关键规则:
- 上半部必须快速响应(不超过0.1毫秒)
- 耗时操作必须交给下半部处理
- 下半部运行期间不会被重复触发,需等待下次中断发生
这个机制确保了:
- 硬件中断能被及时响应(上半部)
- 复杂任务在不影响中断响应的线程环境中安全执行(下半部)
新技术 threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个 CPU 上执行,这提高了效率。
二、中断线程化使用案例
说明:
::: 硬件IO表:https://lceda001.feishu.cn/wiki/UN0pw7Y1KixKirkMh0rcP3Tandc
操作第七个管脚触发中断,对应GPIO1_A4 ::: ::: 引脚编号计算:
公式1:GPIO 小组编号计算公式:number = group * 8 + X
其中:group为小组号(A为0,B为1,C为2,D为3)
公式2:GPIO pin 脚计算公式:pin = bank * 32 + number
其中:bank 为组号,number由公式1计算得来
故:GPIO1_A4
1*32+0*8+4 = 36 :::
Makefiel:
export ARCH=arm64
export CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m +=interrupt.o #此处要和你的驱动源文件同名
KDIR := /home/book/rk/tspi/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make#操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
2
3
4
5
6
7
8
9
驱动代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
int irq;
// 中断处理函数的底半部(线程化中断处理函数)
irqreturn_t test_work(int irq, void *args)
{
// 执行底半部的中断处理任务
msleep(1000);
printk("This is test_work\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
// 中断处理函数的顶半部
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
// 将中断处理工作推迟到底半部
return IRQ_WAKE_THREAD; //我要把线程拉起来
}
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(36); //将GPIO映射为中断号
printk("irq is %d\n", irq);
// 用于请求并注册一个线程化的中断处理函数 //上升沿触发
ret = request_threaded_irq(irq, test_interrupt, test_work, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
return 0;
}
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
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
驱动加载:
拉高GPIO:
三、中断线程化原理
有些中断不能用多线程处理,比如时钟中断。因为有些程序会一直霸占CPU不松手,这时操作系统只能通过定期触发的时钟中断强行夺回控制权。这类中断对系统调度至关重要,注册处理函数时必须明确标记为"IRQF_NO_THREAD"。
3.1、irqaction 中包含thread_fn
每个中断处理描述符(irqaction)对应一个内核线程。这个线程的信息由irqaction的thread属性保存,而thread_fn属性则指向该线程要执行的具体处理函数。
简单来说:
- 每个中断处理单元(irqaction)都会创建一个内核线程
- thread字段记录着这个线程的控制信息
- thread_fn字段保存着线程要执行的任务函数
这样设计可以让中断处理在独立线程中运行,避免阻塞其他系统操作。
3.2、request_threaded_irq 原理
中断处理线程是实时内核线程,优先级设为50,使用先进先出(SCHED_FIFO)调度策略。这类线程名称以"irq/"开头,后面跟着Linux系统分配的中断编号,负责处理中断的函数是irq_thread()。
request_threaded_irq() -> __setup_irq() -> setup_irq_thread()kernel/irq/manage.c
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2,
};
if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
param.sched_priority -= 1;
}
if (IS_ERR(t))
return PTR_ERR(t);
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
/*
* We keep the reference to the task struct even if
* the thread dies to avoid that the interrupt code
* references an already freed task_struct.
*/
get_task_struct(t);
new->thread = t;
/*
* Tell the thread to set its affinity. This is
* important for shared interrupt handlers as we do
* not invoke setup_affinity() for the secondary
* handlers as everything is already set up. Even for
* interrupts marked with IRQF_NO_BALANCE this is
* correct as we want the thread to move to the cpu(s)
* on which the requesting code placed the interrupt.
*/
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}
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
在中断处理过程中,当系统检测到某个中断时,会调用一个叫__handle_irq_event_percpu的函数。这个函数会逐个检查与该中断相关的所有处理函数,依次执行它们。
当某个处理函数执行完毕后,如果它返回了一个名为IRQ_WAKE_THREAD的信号,就说明这个中断需要额外的线程来处理后续工作。此时系统会立即启动对应的后台线程,让这个线程继续完成剩下的中断处理任务。
handle_fasteoi_irq() -> handle_irq_event() -> handle_irq_event_percpu() -> __handle_irq_event_percpu()kernel/irq/handle.c
kernel/irq/handle.c
调用栈:
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action);//---------
/* Fall through to add to randomness */
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
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
中断处理线程的处理函数是irq_thread(),调用函数irq_thread_fn(),然后函数irq_thread_fn()调用注册的线程处理函数。
kernel/irq/manage.c
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);
if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;
init_task_work(&on_exit_work, irq_thread_dtor);
task_work_add(current, &on_exit_work, false);
irq_thread_check_affinity(desc, action);
while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;
irq_thread_check_affinity(desc, action);
action_ret = handler_fn(desc, action);
if (action_ret == IRQ_WAKE_THREAD)
irq_wake_secondary(desc, action);
wake_threads_waitq(desc);
}
/*
* This is the regular exit path. __free_irq() is stopping the
* thread via kthread_stop() after calling
* synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the
* oneshot mask bit can be set.
*/
task_work_cancel(current, irq_thread_dtor);
return 0;
}
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
static irqreturn_t irq_thread_fn(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t ret;
ret = action->thread_fn(action->irq, action->dev_id);
irq_finalize_oneshot(desc, action);
return ret;
}
2
3
4
5
6
7
8