06、中断下半部:tasklet 原理
📢 在上半部处理紧急的事情,在上半部的处理过程中,中断是被禁止的; 在下半部处理耗时的事情,在下半部的处理过程中,中断是使能的。
一、什么是 taskle
在 Linux 内核中,tasklet
是一种轻量级的软中断机制,广泛应用于处理与硬件中断相关的下半部任务(Bottom Half)。它的设计目标是提供一种高效且易于使用的机制,以确保在多核系统中避免并发问题,同时保持内核的高性能和稳定性。作为一种特殊的软中断实现,tasklet
在内核开发中占据重要地位,尤其适用于需要快速响应但又不能阻塞的任务场景。
Tasklet 的定义与结构
tasklet
的核心结构体 tasklet_struct
定义在头文件 include/linux/interrupt.h
中,其原型如下:
为了便于使用,内核还定义了类型别名 tasklet_t
,使得开发者可以直接使用 tasklet_t
来声明 tasklet
变量,而无需显式地使用 struct tasklet_struct
。
结构体成员详解
**next**
该成员是一个指向下一个tasklet
的指针,用于将多个tasklet
组织成链表结构。这种链表形式的设计允许内核高效地管理和调度多个tasklet
,从而支持复杂的任务处理需求。**state**``state
成员用于表示tasklet
的当前状态。它通常包含以下几个可能的值:TASKLET_STATE_SCHED
:表示该tasklet
已被调度,但尚未执行。TASKLET_STATE_RUN
:表示该tasklet
正在某个 CPU 上运行。 这些状态标志通过原子操作进行维护,以确保在多核环境下的正确性。
**count**``count
是一个引用计数器,用于管理tasklet
的启用和禁用状态。当count
的值为 0 时,tasklet
被认为是启用的,可以被调度执行;如果count
的值大于 0,则tasklet
被禁用,无法被调度。这种机制允许开发者在需要时动态地启用或禁用tasklet
,从而提高灵活性。**func**``func
是一个函数指针,指向tasklet
绑定的回调函数。该函数将在tasklet
被调度执行时调用,通常用于完成与硬件中断相关的后续处理任务。需要注意的是,func
函数不得调用可能导致休眠的操作(如内存分配或等待事件),否则可能会导致内核异常。**data**``data
是传递给func
函数的参数,通常用于携带与任务相关的上下文信息。例如,在网络驱动程序中,data
可能指向某个网络数据包的描述符。
二、tasklet 接口
2.1、静态初始化函数
在 Linux 内核中,有一个用于静态初始化 tasklet 的宏函数:DECLARE_TASKLET。这个宏函数可以帮助我们更方便地进行 tasklet 的静态初始化。宏函数的原型如下:
在 Linux 内核中,tasklet
是一种轻量级的软中断机制,用于处理延后执行的任务。它提供了一种高效的方式来调度和执行需要在中断上下文之外完成的工作,同时避免了直接操作硬件中断的复杂性。为了简化 tasklet
的使用,Linux 内核提供了一个名为 DECLARE_TASKLET
的宏函数,用于静态初始化 tasklet
结构体。这一机制不仅提高了代码的可读性和可维护性,还降低了开发者手动初始化 tasklet
时可能引入错误的风险。
DECLARE_TASKLET
宏的原型如下:
通过使用 DECLARE_TASKLET
宏,开发者可以以一种简洁而优雅的方式定义并初始化一个 tasklet
,而无需手动填充其结构体字段。这不仅减少了代码冗余,还确保了初始化过程的一致性和正确性。
如果 tasklet 初始化函数为非使能状态,使用以下宏定义:
2.2、动态初始化函数
在 Linux 内核中,可以使用 tasklet_init 函数对 tasklet 进行动态初始化。该函数原型为:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
2
参数解析:
t
:指向一个struct tasklet_struct
类型的指针,表示需要初始化的 tasklet 结构体。func
:这是一个函数指针,指向 tasklet 的处理函数。当 tasklet 被调度执行时,内核会调用此函数,并将data
作为参数传递给它。data
:这是传递给处理函数的参数,通常用于向处理函数提供上下文信息或任务相关的数据。
通过 tasklet_init
函数,开发者可以在运行时动态创建和初始化 tasklet,而不是在编译时静态定义。这种灵活性使得 tasklet 可以根据实际需求动态地分配和管理,从而更好地适应复杂的系统环境。
以下是一个完整的示例代码,展示了如何使用 tasklet_init
函数进行动态初始化:
#include <linux/interrupt.h>
#include <linux/module.h>
// 定义 tasklet 的处理函数
void my_tasklet_handler(unsigned long data) {
printk(KERN_INFO "Tasklet handler executed with data: %lu\n", data);
}
// 声明一个 tasklet 结构体
struct tasklet_struct my_tasklet;
// 模块加载函数
static int __init my_module_init(void) {
// 动态初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 12345);
// 调度 tasklet
tasklet_schedule(&my_tasklet);
printk(KERN_INFO "Tasklet initialized and scheduled.\n");
return 0;
}
// 模块卸载函数
static void __exit my_module_exit(void) {
// 销毁 tasklet
tasklet_kill(&my_tasklet);
printk(KERN_INFO "Tasklet destroyed.\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example of tasklet initialization and usage.");
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
2.3、关闭函数
在 Linux 内核中,可以使用 tasklet_disabled 函数来关闭一个已经初始化的 tasklet。该函数的原型如下:
void tasklet_disable(struct tasklet_struct *t)
其中,t 是指向 tasklet 结构体的指针。
需要注意的是,关闭 tasklet 并不会销毁 tasklet 结构体,因此可以随时通过调用tasklet_enable 函数重新启用 tasklet,或者调用 tasklet_kill 函数来销毁tasklet。
2.4、使能函数
在 Linux 内核中,可以使用 tasklet_enable 函数来使能(启用)一个已经初始化的 tasklet。该函数的原型如下:
void tasklet_enable(struct tasklet_struct *t)
其中,t 是指向 tasklet 结构体。
需要注意的是,使能 tasklet 并不会自动触发 tasklet 的执行,而是通过调用 tasklet_schedule函数来触发。同时,可以使用 tasklet_disable 函数来临时暂停或停止 tasklet 的执行。如果需要永久停止 tasklet 的执行并释放相关资源,则应调用 tasklet_kill 函数来销毁 tasklet
2.5、调度函数
在 Linux 内核中,可以使用 tasklet_schedule 函数来调度(触发)一个已经初始化的 tasklet 执行。该函数的原型如下:
void tasklet_schedule(struct tasklet_struct *t)
其中,t 是指向 tasklet 结构体的指针。
需要注意的是,调度 tasklet 只是将 tasklet 标记为需要执行,并不会立即执行 tasklet 的处理函数。实际的执行时间取决于内核的调度和处理机制。
2.6、销毁函数
在 Linux 内核中,可以使用 tasklet_kill 函数来销毁一个已经初始化的 tasklet,释放相关资源。该函数的原型如下:
void tasklet_kill(struct tasklet_struct *t);
调用 tasklet_kill 函数会释放 tasklet 所占用的资源,并将 tasklet 标记为无效。因此,销毁后的 tasklet 不能再被使用。
需要注意的是,在销毁 tasklet 之前,应该确保该 tasklet 已经被停止(通过调用tasklet_disable 函数)。否则,销毁一个正在执行的 tasklet 可能导致内核崩溃或其他错误。
一旦销毁了 tasklet,如果需要再次使用 tasklet,需要重新进行初始化(通过调用 tasklet_init函数)。
二、tasklet 内部机制
tasklet属于TASKLET_SOFTIRQ软件中断,入口函数为 tasklet_action,这在内核 kernel\softirq.c 中设置:
当驱动程序调用 tasklet_schedule 时,会设置 tasklet 的 state 为TASKLET_STATE_SCHED,并把它放入某个链表:
include/linux/interrupt.h
当发生硬件中断时,内核处理完硬件中断后,会处理软件中断。对于TASKLET_SOFTIRQ 软件中断,会调用 tasklet_action 函数。
执行过程还是挺简单的:从队列中找到 tasklet,进行状态判断后执行 func函数,从队列中删除 tasklet。
从这里可以看出:
① tasklet_schedule 调度 tasklet 时,其中的函数并不会立刻执行,而只是把tasklet 放入队列; ② 调用一次 tasklet_schedule,只会导致 tasklnet 的函数被执行一次; ③ 如果 tasklet 的函数尚未执行,多次调用 tasklet_schedule 也是无效的,只会放入队列一次。
我们softirq 初始化的时候 指定服务程序:
tasklet_action 函数解析如下: