tasklet

本文深入解析了Linux内核中的Tasklet机制,包括其数据结构、调度机制、执行流程及使用方法。介绍了Tasklet如何利用软中断实现延时执行任务,以及如何在设备驱动程序中应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值