Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2634760
  • 博文数量: 258
  • 博客积分: 9440
  • 博客等级: 少将
  • 技术积分: 6998
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-03 10:28
个人简介

-- linux爱好者,业余时间热衷于分析linux内核源码 -- 目前主要研究云计算和虚拟化相关的技术,主要包括libvirt/qemu,openstack,opennebula架构和源码分析。 -- 第五届云计算大会演讲嘉宾 微博:@Marshal-Liu

文章分类

全部博文(258)

文章存档

2016年(1)

2015年(4)

2014年(16)

2013年(22)

2012年(41)

2011年(59)

2010年(40)

2009年(75)

分类: LINUX

2011-04-24 11:02:51

    在文章《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。具体代码就不去分析了,估计大家都能看明白!
阅读(3413) | 评论(0) | 转发(12) |
给主人留下些什么吧!~~