Linux驱动——使用tasklet处理中断

一、中断

前文已经提到过中断,这里再回忆一下。中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU 就不能及时响应其他的中断请求,从而造成中断的丢失。

因此,Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。

例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。

因此,内核把中断处理分为两部分:上半部(top half)和下半部(bottom half),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理。

首先,一个快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了 DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。

下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。

但是,内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做 Bottom Half(简称 bh),在 2.4 以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。

对于在上半部和下半部之间划分工作,尽管不存在某种严格的规则,但还是有一些提示可供借鉴:

  • 如果一个任务对时问非常敏感,将其放在中断处理程序中执行。
  • 如果一个任务和硬件相关,将其放在中断处理程序中执行。
  • 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程执行。
  • 其他所有任务,考虑放置在下半部执行。

本文介绍常用的小任务(Tasklet)机制。

二、tasklet 的运行上下文

一次中断可以拆成:

1
2
3
4
5
6
hardirq 上半部
-> 记录事件
-> 调度 tasklet

softirq 下半部
-> 执行 tasklet 函数

tasklet 的本质是建立在 softirq 之上的延后执行接口。

它的执行上下文仍然不是普通进程:

1
2
3
4
tasklet 运行在 softirq 上下文
仍然不能睡眠
仍然不能拿 mutex
仍然不能调用 copy_to_user

它和 hardirq 的区别主要是:

对比项 hardirq tasklet
执行时机 中断到来后立即执行 上半部返回后,在 softirq 阶段执行
能否睡眠 不能 不能
适合 最小必要处理 很短、不能睡眠的延后处理
典型 API request_irq() tasklet_schedule()

所以 tasklet 解决的是“延后”,不是“可以睡眠”。

并且,tasklet运行在软中断上下文中,所以其代码必须是原子操作。

三、tasklet 的简单使用

使用步骤:

  • 1、定义一个 tasklet 的执行任务;
  • 2、初始化 taskelet,将处理任务的函数和 takslet 任务通过 DECLARE_TASKLET 捆绑;
  • 3、调度 tasklet :tasklet_schedule(&tasklet);

特点:

  • tasklet 不能引起休眠,同一个 tasklet 不能在两个 CPU 上同时运行。
  • 但是不同 tasklet 可能在不同 CPU 上同时运行,则需要注意共享数据的保护。

声明、初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <linux/interrupt.h>  


DECLARE_TASKLET(tasklet_name, tasklet_func, func_data);

//相当于:
struct t asklet_struct tasklet_name;
tasklet_init(&tasklet_name, tasklet_func, func_data);


// 结构体原型
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

函数功能:将函数和一个tasklet绑定在一起。

参数解析:

  • tasklet_name : tasklet 的名字
  • tasklet_func : 执行的 tasklet 的函数名
  • func_data : 执行 tasklet 所附带的参数,unsigned long,可以像 ioctl 一样传递指针值进去。

执行:

1
2
3
4
#include <linux/interrupt.h>  

// 执行tasklet
tasklet_schedule(struct tasklet_struct* t);

描述:调用后,让系统在适当的时候进行调度之前绑定好的 tasklet。

参数解析:t: 某一个 tasklet 。

接口:

tasklet 以一个数据结构形式存在,使用前必须被初始化。初始化能够通过调用一个特定函数或者通过使用某些宏定义声明结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);   

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

void tasklet_disable(struct tasklet_struct *t);
/*函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet再次被enable;若这个tasklet当前在运行, 这个函数忙等待直到这个tasklet退出*/
void tasklet_disable_nosync(struct tasklet_struct *t);
/*和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU */

void tasklet_enable(struct tasklet_struct *t);
/*使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会很快运行。tasklet_enable和tasklet_disable 必须匹配调用, 因为内核跟踪每个tasklet的"禁止次数"*/

void tasklet_schedule(struct tasklet_struct *t);
/*调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己*/

void tasklet_hi_schedule(struct tasklet_struct *t);
/*和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet 在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期*/

void tasklet_kill(struct tasklet_struct *t);
/*确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill 前它重新调度它自己,如同使用 del_timer_sync*/

例子:

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
// 有关函数声明
void xxx_do_tasklet(unsigned long);
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs);

// 声明一个小任务,并绑定某个函数
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);

// 构造这个模块
int _init xxx_init(void)
{
// …… ;

// 注册中断
result = request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);

// …… ;
}
// 析构这个模块
void _exit xxx_exit(void)
{
// …… ;

// 释放中断
free_irq(xxx_irq,xxx_irq_interrupt);

// 取消某个小任务
tasklet_kill(&xxx_tasklet);
// …… ;
}

// 写好中断处理函数
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
// …… ;

// 在某个中断中调度小任务
tasklet_schedule(&xxx_tasklet);
// …… ;
}

// 执行tasklet
void xxx_do_tasklet(unsigned long)
{
// …… ;
}

Linux驱动——使用tasklet处理中断
http://example.com/2026/08/08/Linux驱动——使用tasklet处理中断/
作者
Yu xin
发布于
2026年8月8日
许可协议