tasklet

本文深入解析了Linux内核中的Tasklet机制,包括其数据结构、调度机制、执行流程及使用方法。介绍了Tasklet如何利用软中断实现延时执行任务,以及如何在设备驱动程序中应用。
tasklet 和 workqueue 是延期执行工作的机制,其实现基于 软中断。它们更易于使用,更适合设备驱动程序。
ISR(中断服务程序) 分为上半部(top half) 和 下半部(bottom half)。
下半部 负责 执行非时间关键操作。 tasklet就是一种下半部实现方式。
tasklet有两个软中断代表: HI_SOFTIRQ 和 TASKLET_SOFTIRQ. 两者的区别是HI_SOFTIRQ 优先级高于 TASKLET_SOFTIRQ.

1. 创建tasklet
 tasklet的数据结构 tasklet_struct:
    /* Tasklets --- multithreaded analogue of BHs.
       Main feature differing them of generic softirqs: tasklet
       is running only on one CPU simultaneously.
       Main feature differing them of BHs: different tasklets
       may be run simultaneously on different CPUs.
       Properties:
       * If tasklet_schedule() is called, then tasklet is guaranteed
         to be executed on some cpu at least once after this.
       * If the tasklet is already scheduled, but its excecution is still not
         started, it will be executed only once.
       * If this tasklet is already running on another CPU (or schedule is called
         from tasklet itself), it is rescheduled for later.
       * Tasklet is strictly serialized wrt itself, but not
         wrt another tasklets. If client needs some intertask synchronization,
         he makes it with spinlocks.
     */
    struct tasklet_struct
    {
        struct tasklet_struct *next;  //指针,指向下一个tasklet,用来创建tasklet_struct实例的链表。
        unsigned long state;          //任务当前的状态。
        atomic_t count;               //原子计数器,用于禁用已经调用的tasklet。       
        void (*func)(unsigned long);   //这个最重要,指向一个函数地址,该函数的执行将被延期。
        unsigned long data;            //传递给 func 函数的参数。
    };

--- state 成员只能在0, TASKLET_STATE_SCHED 和 TASKLET_STATE_RUN 这3个中取值。

TASKLET_STATE_SCHED --- 表示 tasklet 已经被调度,正准备投入运行。

TASKLET_STATE_RUN --- 表示 tasklet 正在运行,TASKLET_STATE_RUN 只有在多处理器的系统中 才会作为一种优化来使用;单处理器系统在任何时刻都知道单个tasklet 是不是正在运行。

--- count 是 tasklet 的引用计数. 如果它不为0,则 tasklet 被禁止,不允许执行;只有当 count = 0 时,tasklet 才被激活,并且设置为挂起状态(pending),该 tasklet 才能执行。

2. 调度tasklet, tasklet_schedule():

2.1 已调度的 tasklet (等同于被触发的软中断)存放于两个单处理器(per-cpu)的数据结构中:

--- tasklet_vec : 普通的 tasklet。

--- tasklet_hi_vec: 高优先级的tasklet。

这两个数据结构,相当于软中断的 softirq_vec[]数组, 只不过这两个数据结构是两个链表,定义如下:

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
/*
 * Tasklets
 */
struct tasklet_head
{
	struct tasklet_struct *head;
	struct tasklet_struct **tail;
};
这两个数据结构都是由 tasklet_struct 结构体构成的链表,链表中每个 tasklet_struct 代表一个不同的 tasklet。
2.2 tasklet 由 tasklet_schedule() 和 tasklet_hi_schedule() 函数进行调度。他们的参数是一个指向 tasklet_struct 结构的指针。

    extern void FASTCALL(__tasklet_schedule(struct tasklet_struct *t));
    static inline void tasklet_schedule(struct tasklet_struct *t)
    {
        if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) //检查 tasklet 状态是否为TASKLET_STATE_SCHED,如果是,说明tasklet已经被调度过了,函数立即返回
            __tasklet_schedule(t);                             //如果不是,则调用__tasklet_schedule()
    }
如果设置了TASKLET_STATE_SCHED标志位,则注册结束,表示该tasklet注册过了。

    void fastcall __tasklet_schedule(struct tasklet_struct *t)
    {
        unsigned long flags;
        local_irq_save(flags);                //保存中断标志,并禁止本地中断
        t->next = __get_cpu_var(tasklet_vec).list;  //把需要调度的tasklet t 添加到每个处理器的tasklet_vec 或tasklet_hi_vec 链表上。
        __get_cpu_var(tasklet_vec).list = t;
        raise_softirq_irqoff(TASKLET_SOFTIRQ);    //触发 tasklet 软中断,即调用do_softirq()
        local_irq_restore(flags);                 //还原中断现场,并使能本地中断
    }

3.执行tasklet

tasklet关联到TASKLET_SOFTIRQ软中断,调用raise_softirq(TASKLET_SOFTIRQ),即可在下一个适当的时机执行当前处理器的tasklet。

内核使用tasklet_action作为action函数:

static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

	local_irq_disable();    //禁止本地中断,不需要保存中断状态,因为这里的代码总是被软中断调用,而且中断总是被激活的
	list = __get_cpu_var(tasklet_vec).head;  //检索当前处理器的tasklet_vec 或 tasklet_hi_vec链表.
	__get_cpu_var(tasklet_vec).head = NULL;   //将当前处理器上的该链表设置为NULL, 达到清空的效果
	__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
	local_irq_enable();     //允许响应中断。

	while (list) {  //遍历链表上的每个待处理的tasklet_struct
		struct tasklet_struct *t = list;

		list = list->next;

		if (tasklet_trylock(t)) {  //上锁,就是t->state 状态置为 TASKLET_STATE_RUN, 见后面两个函数
			if (!atomic_read(&t->count)) {  //检查count 值是否为0,确保tasklet 没有被禁止,如果禁止了,则跳过,进入下一个tasklet_struct.
				if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
					BUG();
				t->func(t->data);   //运行中断处理函数
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t); //解锁就是,清空 t->state 的 TASKLET_STATE_RUN 状态.
		}

		local_irq_disable();
		t->next = NULL;
		*__get_cpu_var(tasklet_vec).tail = t;
		__get_cpu_var(tasklet_vec).tail = &(t->next);
		__raise_softirq_irqoff(TASKLET_SOFTIRQ);
		local_irq_enable();
	}
}
一个tasklet只能在一个处理器上执行一次,但其他的tasklet可以并行运行, 所以需要特定的tasklet锁,state状态用作锁变量。

在执行一个tasklet处理函数之前,内核使用tasklet_trylock() 检查tasklet的状态是否为TASKLET_STATE_RUN. 即检查是否已在另一个处理器上运行。

    static inline int tasklet_trylock(struct tasklet_struct *t)
    {
        return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
    }
解tasklet锁:

    static inline void tasklet_unlock(struct tasklet_struct *t)
    {
        smp_mb__before_clear_bit();
        clear_bit(TASKLET_STATE_RUN, &(t)->state);
    }

可以看出 tasklet 通过运用 HI_SOFTIRQ 和 TASKLET_SOFTIRQ 这两个软中断实现。

当一个tasklet被调度时,内核就会唤起这两个软中断中的一个,随后,该软中断会被特定函数do_irq()处理,执行所有已调度的tasklet。

4. 较高优先级的tasklet,HI_SOFTIRQ

除了普通的tasklet,内核还是用了另一种tasklet,它具有更高的优先级:

a. 它使用HI_SOFTIRQ 作为软中断,而不是TASKLET_SOFTIRQ,相关的action是tasklet_hi_action.

b. 注册的tasklet在CPU相关的变量tasklet_hi_vec中排队,这是使用tasklet_hi_schedule()完成的。

5. 使用 tasklet:

5.1 声明及初始化自己的 tasklet:

静态创建 tasklet:

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }  //引用计数为0,tasklet 处于激活状态。

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }   //引用计数count = 1, tasklet处于禁止状态。

这两个宏都能静态的创建一个 tasklet_struct 结构,

例:

DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);
动态创建 tasklet:

struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
                                     my_tasklet_handler, dev};

这样就创建了一个名为 my_tasklet,处理程序为tasklet_handler,并且是已被激活了的tasklet。

当处理程序被调用时, dev就会被传递给 tasklet_handler 函数。

还可以使用 tasklet_init(),来动态的初始化 tasklet_struct:

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}
如 tasklet_init(t, tasklet_handler, dev);   //动态的创建一个tasklet。
5.2 编写 tasklet 处理函数:

void tasklet_handler(unsigned long data);

因为是靠软中断实现的,所以tasklet 不能睡眠,这意味着不能再tasklet 中使用信号量或者其他阻塞式的函数。

由于tasklet运行时,允许响应中断,所以如果tasklet和中断处理函数之间共享了某些数据的话,必须进行适当的锁保护。

同一个tasklet绝不会两个CPU上同时执行,这点和软中断不一样,但是两个不同的tasklet可以在两个处理器上同时执行。

如果一个tasklet和其他的tasklet或者软中断共享了数据,必须进行适当的锁保护。如spin_lock()

5.3 调度 tasklet --- tasklet_schedule()

例:

tasklet_schedule(&my_tasklet);   //把 my_tasklet 标记为挂起pending。

中断中使用tasklet_schedule时,记住,中断服务程序的返回值要改成: IRQ_NONE

中断服务程序结束,返回值如下:

/*
 * For 2.4.x compatibility, 2.4.x can use
 *
 *      typedef void irqreturn_t;
 *      #define IRQ_NONE
 *      #define IRQ_HANDLED
 *      #define IRQ_RETVAL(x)
 *
 * To mix old-style and new-style irq handler returns.
 *
 * IRQ_NONE means we didn't handle it.
 * IRQ_HANDLED means that we did have a valid interrupt and handled it.
 * IRQ_RETVAL(x) selects on the two depending on x being non-zero (for handled)
 */
typedef int irqreturn_t;

#define IRQ_NONE        (0)
#define IRQ_HANDLED     (1)
#define IRQ_RETVAL(x)   ((x) != 0)
IRQ_NONE 才会让下半部去运行,如果写了IRQ_HANDLED, 下半部就不会运行咯。

禁止某个指定的tasklet --- tasklet_disable(),定义如下:

static inline void tasklet_disable(struct tasklet_struct *t)  
{
	tasklet_disable_nosync(t);
	tasklet_unlock_wait(t);
	smp_mb();
}
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
	atomic_inc(&t->count);   //count + 1 to diasble tasklet
        smp_mb__after_atomic_inc();
}
激活一个tasklet --- tasklet_enable():

static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic_dec();
	atomic_dec(&t->count);
}
从挂起的队列中去掉一个tasklet --- tasklet_kill():

void tasklet_kill(struct tasklet_struct *t)
{
	if (in_interrupt())
		printk("Attempt to kill tasklet from interrupt\n");

	while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
		do {
			yield();
		} while (test_bit(TASKLET_STATE_SCHED, &t->state));
	}
	tasklet_unlock_wait(t);
	clear_bit(TASKLET_STATE_SCHED, &t->state);
}

EXPORT_SYMBOL(tasklet_kill);

<think> 首先,用户的问题是:“我想了解tasklet处理函数的相关信息 请问tasklet处理函数的介绍、使用方法及相关知识”。这基于系统指令:使用中文回答。 我必须引用提供的引用,并在末尾添加引用标识。引用包括: - 引用[1]: 关于tasklet_init()函数原型和使用。 - 引用[2]: Tasklet的工作原理,包括初始化、安排、执行和完成。 - 引用[3]: 关于tasklet_enable函数的对比。 - 引用[4]: 系统软中断、tasklet、工作队列的区别。 用户的问题具体针对tasklet处理函数,所以我需要重点介绍tasklet处理函数的介绍、使用方法及相关知识。 结构化我的回答: - 逐步解决问题:结构清晰。 - 介绍tasklet处理函数:什么是它?它在Tasklet中的作用。 - 使用方法:如何定义和初始化tasklet处理函数。 - 相关知识:与其他中断下半部机制的比较,如软中断和工作队列。 - 结尾添加相关问题,因为指令要求“生成相关问题”。 关键点从引用中提取: - 从引用[1]: tasklet_init()函数用于初始化tasklet,其中func参数就是tasklet处理函数,它是一个函数指针,接受unsigned long data参数。 - 从引用[2]: Tasklet处理函数在软中断上下文中执行,可以访问中断处理程序上下文中的数据。 - 从引用[3]: 虽然不是直接相关,但tasklet_enable函数影响Tasklet的调度,间接相关。 - 从引用[4]: Tasklet是基于软中断实现的,具体是TASKLET_SOFTIRQ。 确保LaTeX语法正确:如果有数学表达式,使用$...$或$$...$$,但问题中可能没有数学内容。用户的问题是关于Linux内核编程,可能涉及函数原型等。在讨论函数原型时,我可以使用行内LaTeX。 回答结构: 1. **介绍tasklet处理函数**:解释它是Tasklet的核心部分,定义要执行的任务。 2. **使用方法**: - 如何定义tasklet处理函数:函数签名必须是void func(unsigned long data)。 - 如何使用tasklet_init()初始化。 - 如何使用tasklet_schedule()安排执行。 3. **相关知识**: - 执行上下文:在软中断上下文中运行,不能睡眠。 - 与其他机制的区别:tasklet vs softirq vs work queue。 4. **引用标识**:在相关段落末尾添加[^n]。 最后,添加相关问题。 完整回答: - 开头直接回答问题。 - 然后逐步展开。 - 结尾添加相关问题。 相关问题示例: 1. tasklet处理函数可以调用哪些函数? 2. 如何避免tasklet处理函数中的竞态条件? 3. tasklet在实时系统中的应用限制? 确保语言中文。 </think> ### Tasklet处理函数的介绍、使用方法及相关知识 在Linux内核中断处理机制中,Tasklet是一种用于处理中断后半部(deferrable interrupt handling)的轻量级机制。它基于软中断实现,常用于执行非紧急的操作,以避免在中断上下文中阻塞CPU。Tasklet处理函数是Tasklet的核心部分,负责定义要执行的任务代码。下面我将从介绍、使用方法和相关知识三个方面,逐步解释tasklet处理函数。 #### 1. Tasklet处理函数的介绍 Tasklet处理函数是一个回调函数,它在软中断上下文中执行。当内核安排Tasklet执行时,该函数被调用,处理中断中延迟的任务。其关键特性包括: - **函数签名**:必须定义为`void func_name(unsigned long data)`,其中`data`是传递给函数的参数(通常用于传递数据指针或状态)。 - **执行上下文**:Tasklet处理函数在软中断上下文中运行(具体对应`TASKLET_SOFTIRQ`软中断类型)。这意味着: - 它不能睡眠或调用可能阻塞的函数(如`kmalloc()`)。 - 它可以访问中断处理程序上下文的数据,但必须避免冗长操作以防止软中断延迟[^2][^4]。 - **原子性和并发性**:同一个Tasklet处理函数在单个CPU上始终原子执行(不会被自身中断),但其在不同CPU上可能并行运行,因此需要适当的锁机制(如自旋锁)来保护共享数据[^2]。 - **用途**:常用于网络设备、存储驱动等场景,例如处理接收数据包的后续逻辑或定时器任务,以减轻中断处理负担[^4]。 Tasklet处理函数是中断下半部机制的一部分,相比于软中断(softirq)更易用,但性能略低于软中断;相比于工作队列(work queue),它开销小但无法睡眠[^4]。 #### 2. Tasklet处理函数的使用方法 使用Tasklet处理函数涉及定义、初始化、安排和清理四个步骤。以下是基于Linux内核API的详细指南(以C语言为例): - **步骤1: 定义处理函数** 定义一个符合签名的函数。例如,处理一个设备中断的数据处理任务: ```c void my_tasklet_handler(unsigned long data) { struct device_data *dev = (struct device_data *)data; // 强制转换参数为自定义数据结构 // 执行实际任务,如处理缓冲区或更新状态 printk(KERN_INFO "Tasklet executed with data: %lu\n", data); // 注意:这里不能调用sleep()或可能阻塞的函数 } ``` - **步骤2: 初始化Tasklet并将处理函数绑定** 使用`tasklet_init()`函数初始化`struct tasklet_struct`,并指定处理函数。函数原型为: ``` void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); ``` 其中: - `t`:指向要初始化的Tasklet结构体指针。 - `func`:指向处理函数的指针(即上一步定义的`my_tasklet_handler`)。 - `data`:传递给处理函数的参数(可以是数值或指针)[^1]。 示例代码: ```c struct tasklet_struct my_tasklet; // 声明Tasklet结构体 struct device_data dev_data; // 自定义数据结构 // 在驱动初始化函数中初始化Tasklet void init_device(void) { tasklet_init(&my_tasklet, my_tasklet_handler, (unsigned long)&dev_data); // 绑定处理函数和数据 } ``` - **步骤3: 安排Tasklet执行** 在中断处理函数或其他上下文中,使用`tasklet_schedule()`安排Tasklet: ```c irq_handler_t device_interrupt(int irq, void *dev_id) { // 中断处理逻辑 tasklet_schedule(&my_tasklet); // 将Tasklet加入软中断队列 return IRQ_HANDLED; } ``` Tasklet被添加到软中断队列后,内核会在下次软中断触发时执行处理函数。调度是非阻塞的,通常很快执行[^2]。 - **步骤4: 启用/禁用和管理Tasklet** - **启用和禁用**:使用`tasklet_enable()`和`tasklet_disable()`控制Tasklet的执行权限。`tasklet_enable()`减少Tasklet的计数器,当计数器归零时允许调度(常用于防止重入或资源竞争)[^3]。 - **清理资源**:在模块卸载时,调用`tasklet_kill()`确保Tasklet被移除: ```c void cleanup_device(void) { tasklet_kill(&my_tasklet); // 防止Tasklet继续执行 } ``` **完整示例**: ```c #include <linux/interrupt.h> #include <linux/module.h> struct device_data { int buffer[100]; }; struct tasklet_struct my_tasklet; struct device_data dev_data; void my_tasklet_handler(unsigned long data) { struct device_data *dev = (struct device_data *)data; // 处理数据,例如填充缓冲区 for (int i = 0; i < 100; i++) { dev->buffer[i] = i; } } irq_handler_t device_interrupt(int irq, void *dev_id) { tasklet_schedule(&my_tasklet); // 中断中安排Tasklet return IRQ_HANDLED; } static int __init my_init(void) { tasklet_init(&my_tasklet, my_tasklet_handler, (unsigned long)&dev_data); // 注册中断等 return 0; } static void __exit my_exit(void) { tasklet_kill(&my_tasklet); } module_init(my_init); module_exit(my_exit); ``` #### 3. Tasklet处理函数的相关知识 - **执行机制**: - Tasklet处理函数依赖于软中断子系统。当软中断被触发时(如中断发生后),内核调用`TASKLET_SOFTIRQ`处理队列中的所有Tasklet[^2][^4]。 - 每个Tasklet在初始化后被添加到每CPU队列,确保其在同一CPU上顺序执行,避免缓存问题。 - **性能考虑**: - **优点**:轻量级(比工作队列开销小),适用于高频小任务。 - **缺点**:处理函数不能阻塞,否则可能导致内核不稳定;在高负载下,软中断延迟可能影响实时性[^4]。 - **与其他机制比较**: - **软中断(Softirq)**:Tasklet基于软中断实现(类型为`TASKLET_SOFTIRQ`)。软中断更底层、性能更高,但需要静态注册且更易引入竞态;Tasklet简化了使用,适合大多数驱动场景[^4]。 - **工作队列(Work Queue)**:处理函数在进程上下文中运行,可以睡眠,但开销更大。Tasklet更适合低延迟任务,工作队列适合长时间操作[^4]。 - **总结**:选择机制时,优先Tasklet处理简单非阻塞任务;若需睡眠或复杂操作,则用工作队列。 - **最佳实践**: - 保持处理函数简短(微秒级执行时间),避免长时间占用CPU。 - 使用`data`参数传递状态,避免全局变量以减少竞态风险。 - 在SMP系统中,使用锁保护共享数据(例如`spin_lock_irqsave()`)。 通过上述介绍,您应该能理解tasklet处理函数在中断处理中的角色和实现方式。如果涉及具体驱动开发,建议参考Linux内核文档或相关教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值