`
xitong
  • 浏览: 6204876 次
文章分类
社区版块
存档分类
最新评论

tasklet原理和源码分析

 
阅读更多

在文章《softirq原理和源码分析》中对中断的下半部机制softirq进行了简单分析,在Linux内核中比较有名的中断下半部机制还有tasklet和workqueue等,本文重点围绕tasklet的原理和源码进行了详细的分析。

一 tasklet基本概念
tasklet是建立在softirq基础上的一种中断的下半部机制,在本质上与softirq基本相同,但却有简单的编程接口和宽松的锁规则。
tasklet是通过两种softirq来实现的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,因此,tasklet也被分为两种:高优先级的tasklet(基于HI_SOFTIRQ)和普通优先级的tasklet(基于TASKLET_SOFTIRQ)。在Linux内核中,tasklet通过结构体struct tasklet_struct来描述:
  1. struct tasklet_struct
  2. {
  3. struct tasklet_struct *next; /*构成tasklet的链表*/
  4. unsigned long state; /*tasklet的状态*/
  5. atomic_t count;/*使能计数*/
  6. void (*func)(unsigned long); /*tasklet处理函数*/
  7. unsigned long data; /*处理函数的参数*/
  8. };

每个成员的含义参见注释。这里详细介绍下state成员,它表示tasklet的状态,当前Linux内核中支持两种状态:

(1)TASKLET_STATE_SCHED:表示tasklet已经准备好了,只要被选择到就可以运行了,类似于进程的就绪态。

(2)TASKLET_STATE_RUN:表示tasklet正在执行,类似于进程的RUN态,需要注意的是,TASKLET_STATE_RUN态仅仅在Linux支持SMP的情况下才有定义,因为在UP上处理器能够明确知道当前执行的tasklet是哪个。

另外,只有count=0的时候,tasklet才能被执行,因为对某个tasklet,内核或驱动开发者希望能够显示的使能/禁止其执行,所以才在tasklet结构体中增加了此成员。可以使得count成员变化的接口如下:

  1. static inline void tasklet_disable_nosync(struct tasklet_struct *t)
  2. {
  3. atomic_inc(&t->count);
  4. smp_mb__after_atomic_inc();
  5. }
  6. static inline void tasklet_disable(struct tasklet_struct *t)
  7. {
  8. tasklet_disable_nosync(t);
  9. tasklet_unlock_wait(t);
  10. smp_mb();
  11. }
  12. static inline void tasklet_enable(struct tasklet_struct *t)
  13. {
  14. smp_mb__before_atomic_dec();
  15. atomic_dec(&t->count);
  16. }
  17. static inline void tasklet_hi_enable(struct tasklet_struct *t)
  18. {
  19. smp_mb__before_atomic_dec();
  20. atomic_dec(&t->count);
  21. }

从这些接口的名称也可以看出count成员的主要功能,而不能简单的将count理解为引用计数,容易操作误解,所以理解为使能计数更贴切些!

二、tasklet声明和定义

1、静态和动态创建tasklet的两种方式

(1)静态创建方式

  1. #define DECLARE_TASKLET(name, func, data) /
  2. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
  3. #define DECLARE_TASKLET_DISABLED(name, func, data) /
  4. struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

从代码中可以清楚的看到,两个宏的区别在于tasklet的count不同,前面已经讲过count表示tasklet的使能计数,只有当count=0,表示tasklet创建后处于使能状态;count=1,表示tasklet创建后处于禁止状态。

(2)动态创建

  1. struct tasklet_struct t;
  2. void (*func)(unsigned long);
  3. tasklet_init(t,func,NULL);

2. tasklet处理函数

与softirq一样,tasklet不能睡眠和阻塞,因此在设计tasklet处理函数时必须格外小心,不能使用信号或其他引起阻塞的函数。另外,同一个tasklet不可能同时在两个processor上运行,如果两个不同的tasklet之间有数据共享,需要注意采用合适的锁机制。

三、tasklet的执行过程

Linux内核中采用两个每CPU变量来存储属于当前CPU的tasklet:

(1)tasklet_vec:普通的tasklet,即基于TASKLET_SOFTIRQ实现的tasklet

(2)tasklet_hi_vec:高优先级的tasklet,即基于HI_SOFTIRQ实现的tasklet

在Linux内核中通过显示的调用tasklet_schedule()和tasklet_hi_schedule()函数来实现tasklet的调度,然后将在之后的某个时间,该tasklet将会被执行。需要注意一点:tasklet一定是在调度它的CPU上执行

  1. static inline void tasklet_schedule(struct tasklet_struct *t)
  2. {
  3. if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  4. __tasklet_schedule(t);
  5. }
  6. extern void __tasklet_hi_schedule(struct tasklet_struct *t);
  7. static inline void tasklet_hi_schedule(struct tasklet_struct *t)
  8. {
  9. if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  10. __tasklet_hi_schedule(t);
  11. }

从代码可以看到,首先检查当前tasklet是否处于TASKLET_STATE_SCHED状态,即test_and_set_bit(TASKLET_STATE_SCHED, &t->state),个人感觉只有深入分析了这个函数才能真正了解其实现过程。

  1. static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
  2. {
  3. int oldbit;
  4. asm volatile(LOCK_PREFIX "bts %2,%1/n/t"
  5. "sbb %0,%0" : "=r" (oldbit), ADDR : "Ir" (nr) : "memory");
  6. return oldbit;
  7. }

这里首先介绍两个比较少用的汇编指令:(内联汇编的知识就不多讲了,不理解的同学可以专门去查阅这方面的知识)

(1)bts %2,%1

bts汇编指令的原型为:bts dest,src 表示将目的操作数dest的第src位赋值给CF,然后再职位dest的第src位。在这里2%表示是输入操作数nr,值为0。1%表示的是输出操作数ADDR,即&t->state。那么,这句指令的意思即是将t->state的第0位先赋值给CF,然后将t->state的第0位置1。如果之前t->state = 0,那么此时CF=0,如果此前CF指向的第0位为1,那么CF=1。

(2)sbb %0,%0

指令格式:sbb 操作对象1,操作对象2 功能:操作对象1=操作对象1-操作对象2-CF

这里0%表示是输出操作数oldbit,即oldbit = oldbit - oldbit - CF,因此返回值只于CF有关,如果为0,返回值就为0,CF不为0,返回值就不为0。

根据bts的讲解,当CF=t->state的第nr位的值,即如果原来这个tasklet被设置为TASKLET_STATE_SCHED状态,那么CF=1,那么test_and_set_bit(TASKLET_STATE_SCHED, &t->state)的返回值就非0,即不能调度执行,如果原来这个tasklet没有被设置为TASKLET_STATE_SCHED状态,那么CF=0,那么那么test_and_set_bit(TASKLET_STATE_SCHED, &t->state)的返回值就为0,这个tasklet就可以调度执行。(不知道有没有讲清楚???)

下面来分析函数__tasklet_schedule()

  1. void __tasklet_schedule(struct tasklet_struct *t)
  2. {
  3. unsigned long flags;
  4. local_irq_save(flags);
  5. t->next = NULL;
  6. *__get_cpu_var(tasklet_vec).tail = t;
  7. __get_cpu_var(tasklet_vec).tail = &(t->next);
  8. raise_softirq_irqoff(TASKLET_SOFTIRQ);
  9. local_irq_restore(flags);
  10. }

先通过local_irq_save(flags)保存并禁止当前cpu上的中断,然后把该tasklet添加当per-CPU变量tasklet_vec链表的末尾,然后激活tasklet对应的softirq,最后恢复并使能当前cpu上的中断。这里来分析以下函数raise_softirq_irqoff(TASKLET_SOFTIRQ):

  1. /*
  2. * This function must run with irqs
  3. */
  4. inline void raise_softirq_irqoff(unsigned int nr)
  5. {
  6. __raise_softirq_irqoff(nr);
  7. /*
  8. * If we're in an interrupt or softirq, we're done
  9. * (this also catches softirq-disabled code). We will
  10. * actually run the softirq once we return from
  11. * the irq or softirq.
  12. *
  13. * Otherwise we wake up ksoftirqd to make sure we
  14. * schedule the softirq soon.
  15. */
  16. if (!in_interrupt())
  17. wakeup_softirqd();
  18. }
  19. #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

首先在__raise_softirq_irqoff(nr)中激活这个softirq,然后,激活软中断处理内核线程。其中in_interrupt在上一篇文章中已经详细分析过了。(in_interrupt判断当有硬件中断嵌套,其他软中断以及不可屏蔽中断的情况下,返回非0值)

再往下分析就是softirq相关的知识了。

四、用于实现tasklet的softirq的处理函数

  1. void __init softirq_init(void)
  2. {
  3. int cpu;
  4. for_each_possible_cpu(cpu) {
  5. int i;
  6. per_cpu(tasklet_vec, cpu).tail =
  7. &per_cpu(tasklet_vec, cpu).head;
  8. per_cpu(tasklet_hi_vec, cpu).tail =
  9. &per_cpu(tasklet_hi_vec, cpu).head;
  10. for (i = 0; i < NR_SOFTIRQS; i++)
  11. INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
  12. }
  13. register_hotcpu_notifier(&remote_softirq_cpu_notifier);
  14. open_softirq(TASKLET_SOFTIRQ, tasklet_action);
  15. open_softirq(HI_SOFTIRQ, tasklet_hi_action);
  16. }

从上面代码可以看出,Linux内核在进行softirq的初始化的时候,就实现指定了用于实现tasklet的响应的softirq的处理函数为:tasklet_action和tasklet_hi_action。

  1. static void tasklet_action(struct softirq_action *a)
  2. {
  3. struct tasklet_struct *list;
  4. local_irq_disable();
  5. list = __get_cpu_var(tasklet_vec).head;
  6. __get_cpu_var(tasklet_vec).head = NULL;
  7. __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
  8. local_irq_enable();
  9. while (list) {
  10. struct tasklet_struct *t = list;
  11. list = list->next;
  12. if (tasklet_trylock(t)) {
  13. if (!atomic_read(&t->count)) {
  14. if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
  15. BUG();
  16. t->func(t->data);
  17. tasklet_unlock(t);
  18. continue;
  19. }
  20. tasklet_unlock(t);
  21. }
  22. local_irq_disable();
  23. t->next = NULL;
  24. *__get_cpu_var(tasklet_vec).tail = t;
  25. __get_cpu_var(tasklet_vec).tail = &(t->next);
  26. __raise_softirq_irqoff(TASKLET_SOFTIRQ);
  27. local_irq_enable();
  28. }

这个函数清晰的展示了Linux内核中tasklet的处理过程,即在while循环中,执行了tasklet的处理函数t->func。具体代码就不去分析了,估计大家都能看明白!

分享到:
评论

相关推荐

    XDU嵌入式驱动程序设计 实验三 Tasklet和工作队列

    2.分析对比Tasklet和工作队列的差异。 二、实验环境 Linux 3.14.0 嵌入式开发板 三、实验内容及实验原理 写一个简单的驱动程序,要求: ①定义一个Tasklet和一个工作队列,实现打印输出 ②定义两个定时器,...

    深入分析Linux内核源码

    深入分析Linux内核源码 前言 第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核 1.3.1 Linux内核的特征 1.3.2 Linux内核版本的变化 1.4 分析Linux内核的意义 ...

    Linux2.6内核标准教程(共计8-- 第1个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第6个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第3个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第4个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第2个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第7个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第5个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

    Linux2.6内核标准教程(共计8--第8个)

    在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU这两种新的 同步机制。 《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员...

Global site tag (gtag.js) - Google Analytics