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 可以共享同一个软中断向量。
Tasklet 与软中断的关系架构 硬件中断 (IRQ) 软中断向量表 (Softirq Vector) HI_SOFTIRQ (高优先级) | TASKLET_SOFTIRQ (普通优先级) | 其他软中断... 高优先级 Tasklet 链表 普通优先级 Tasklet 链表 Tasklet A (func_a) Tasklet B (func_b) Tasklet C (func_c) Tasklet D (func_d) 说明:硬中断触发软中断处理,软中断处理函数遍历 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 睡眠,内核将无法调度它继续执行,导致系统挂起或死锁。
Tasklet 执行上下文与约束 Tasklet 执行 软中断上下文 ✅ 允许的操作 • 使用自旋锁 (spinlock) • 操作内核数据结构 • 访问 per-CPU 变量 • 调度其他 Tasklet ❌ 禁止的操作 • 睡眠 (msleep) • 使用互斥锁 (mutex) • 访问用户空间 • 调用 schedule() 核心原则:Tasklet 必须快速执行,不能阻塞,不能睡眠,不能长时间占用 CPU

4.4 Tasklet 的典型使用场景

基于上述特点,Tasklet 最适合以下场景:

  • 中断下半部处理:硬中断处理函数中需要完成的、耗时较短且不能睡眠的工作,如更新统计信息、唤醒等待队列、触发 DMA 操作等。
  • 网络协议栈处理:网络设备驱动中,接收数据包后的协议处理,如 TCP 确认、校验和计算等。
  • 定时器回调:需要定期执行的、非睡眠性的检查或维护任务。
  • 设备状态更新:轮询设备状态寄存器并更新内核状态变量。
选择建议:如果任务执行时间短(微秒级)、不需要睡眠、对实时性要求较高,优先选择 Tasklet。如果任务可能执行较长时间(毫秒级或更长)、需要睡眠或访问用户空间,则应选择 Workqueue。