4. Tasklet 概述
Tasklet 是 Linux 内核中一种基于软中断的、延迟执行的机制,专门用于处理那些对原子性要求严格、执行时间短且不能睡眠的任务。它是软中断机制的一种特殊实现,旨在简化软中断的编程模型,同时保持较高的执行效率。
4.1 Tasklet 的定义
Tasklet 本质上是一种特殊的软中断,它被设计为在软中断上下文中运行。每个 Tasklet 都与一个特定的函数绑定,当 Tasklet 被调度后,它会在一个合适的时机(通常是当前 CPU 上的硬中断处理完成后)被内核执行。
从数据结构上看,Tasklet 由 struct tasklet_struct 表示,定义在 <linux/interrupt.h> 中:
struct tasklet_struct {
struct tasklet_struct *next; /* 链表中的下一个 tasklet */
unsigned long state; /* TASKLET_STATE_SCHED 或 TASKLET_STATE_RUN */
atomic_t count; /* 引用计数,非零时禁止执行 */
void (*func)(unsigned long); /* 要执行的函数 */
unsigned long data; /* 传递给 func 的参数 */
};
关键字段说明:
- state:标识 Tasklet 的状态,
TASKLET_STATE_SCHED表示已被调度,TASKLET_STATE_RUN表示正在运行。 - count:当
count != 0时,Tasklet 被禁用,即使被调度也不会执行。 - func:实际执行的回调函数,接收一个
unsigned long类型的参数。
4.2 Tasklet 与软中断的关系
Tasklet 是建立在软中断机制之上的更高层抽象。理解它们之间的关系,有助于把握内核延迟执行机制的层次结构。
| 对比维度 | 软中断 (Softirq) | Tasklet |
|---|---|---|
| 层次 | 底层机制,直接由内核管理 | 基于软中断的高层封装 |
| 并发性 | 同一类型的软中断可在多 CPU 上并行执行 | 同一 Tasklet 在同一时刻只能在一个 CPU 上执行 |
| 数量 | 静态分配,数量有限(通常 10 个) | 动态创建,数量几乎无限制 |
| 使用复杂度 | 高,需要处理重入和并发 | 低,自动串行化,无需额外锁 |
| 执行上下文 | 软中断上下文 | 软中断上下文 |
Tasklet 实际上是通过软中断向量 HI_SOFTIRQ 和 TASKLET_SOFTIRQ 来实现的。内核维护了两个 Tasklet 链表:高优先级链表(对应 HI_SOFTIRQ)和普通优先级链表(对应 TASKLET_SOFTIRQ)。当软中断处理函数 tasklet_action 或 tasklet_hi_action 被调用时,它会遍历对应的链表,依次执行链表中所有已调度的 Tasklet。
核心关系:Tasklet 是软中断的“实例化”产物。每个 Tasklet 都对应一个软中断处理函数,但多个 Tasklet 可以共享同一个软中断向量。
4.3 Tasklet 的特点
Tasklet 的设计哲学是“简单且安全”。它通过以下两个核心特性来保证这一点:
4.3.1 原子性
Tasklet 的执行具有严格的原子性保证:
- 同一 Tasklet 串行执行:同一个 Tasklet 在任何时刻最多只能在一个 CPU 上运行。如果 Tasklet A 已经在 CPU0 上执行,那么即使 CPU1 也调度了 Tasklet A,它也必须等待 CPU0 上的执行完成。
- 不同 Tasklet 可并行:不同的 Tasklet 可以在不同的 CPU 上同时执行,这为多核系统提供了并行处理能力。
- 与软中断的关系:Tasklet 的执行期间,软中断是开启的,因此可以被更高优先级的硬中断抢占,但不会被其他 Tasklet 或软中断打断同一 Tasklet 的执行。
重要提示:虽然 Tasklet 保证了同一实例的串行执行,但如果多个 Tasklet 访问共享资源,仍然需要使用自旋锁等机制来保护临界区。Tasklet 的原子性仅针对其自身实例,不涉及跨 Tasklet 的资源保护。
4.3.2 不可睡眠
Tasklet 运行在软中断上下文中,这意味着它必须遵守以下约束:
- 禁止睡眠:不能调用任何可能导致睡眠的函数,如
msleep()、wait_event()、mutex_lock()等。 - 禁止调度:不能调用
schedule()或任何可能触发调度的函数。 - 禁止用户空间访问:不能访问用户空间内存,因为软中断上下文不属于任何进程。
- 禁止使用信号量:信号量(semaphore)可能导致睡眠,应使用自旋锁(spinlock)代替。
为什么不能睡眠? 因为 Tasklet 不是在进程上下文中运行的,它没有自己的进程控制块(task_struct)来保存和恢复状态。如果 Tasklet 睡眠,内核将无法调度它继续执行,导致系统挂起或死锁。
4.4 Tasklet 的典型使用场景
基于上述特点,Tasklet 最适合以下场景:
- 中断下半部处理:硬中断处理函数中需要完成的、耗时较短且不能睡眠的工作,如更新统计信息、唤醒等待队列、触发 DMA 操作等。
- 网络协议栈处理:网络设备驱动中,接收数据包后的协议处理,如 TCP 确认、校验和计算等。
- 定时器回调:需要定期执行的、非睡眠性的检查或维护任务。
- 设备状态更新:轮询设备状态寄存器并更新内核状态变量。
选择建议:如果任务执行时间短(微秒级)、不需要睡眠、对实时性要求较高,优先选择 Tasklet。如果任务可能执行较长时间(毫秒级或更长)、需要睡眠或访问用户空间,则应选择 Workqueue。