linux下面的中断处理软件中断tasklet机制

系统 1796 0

參考:

《Linux内核设计与实现》

http://blog.csdn.net/fontlose/article/details/8279113

http://blog.chinaunix.net/uid-27212029-id-3386692.html


tasklet是中断处理下半部分最经常使用的一种方法,驱动程序一般先申请中断,在中断处理函数内完毕中断上半部分的工作后调用tasklet。tasklet有例如以下特点:


1.tasklet仅仅能够在一个CPU上同步地运行,不同的tasklet能够在不同地CPU上同步地运行。
2.tasklet的实现是建立在两个软件中断的基础之上的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,本质上没有什么差别,仅仅只是HI_SOFTIRQ的优先级更高一些
3.因为tasklet是在软中断上实现的,所以像软中断一样不能睡眠、不能堵塞,处理函数内不能含有导致睡眠的动作,如降低信号量、从用户空间拷贝数据或手工分配内存等。
4.一个 tasklet 可以被禁止而且之后被又一次使能; 它不会运行直到它被使能的次数与被禁止的次数同样.
5.tasklet的串行化使tasklet函数不必是可重入的,因此简化了设备驱动程序开发人员的工作。

6.每一个cpu拥有一个tasklet_vec链表,详细是哪个cpu的tasklet_vec链表,是依据当前线程是执行在哪个cpu来决定的。


tasklet是驱动程序实现可延迟函数的首选方法,tasklet建立在HI_SOFTIRT和TASKLET_SOFTIRQ两个软中断上。
原理
tasklet和高优先级的tasklet分别存放在tasklet_vec和tasklet_hi_vec数组中,二者都包括类型为tasklet_head的
NR_CPUS个元素,每一个元素都是指向tasklet描写叙述符链表的指针。
运行过程
HI_SOFTIRQ软中断相关的软中断函数是tasklet_hi_action(),而与TASKLET_SOFTIRQ相关的函数是tasklet_action()
     1.禁止本地中断
     2.获得本地CPU的逻辑号n
     3.把tasklet_vec[n]或tasklet_hi_vec[n]所指向的链表的地址存入局部变量list
     4.把tasklet_vec[n]或tasklet_hi_vec[n]的值赋为NULL,因此已调度的tasklet描写叙述符链表被清空
     5.打开本地中断
     6.对于list所指向的每一个tasklet描写叙述符
            a.在多处理器系统上,检查tasklet的TASKLET_STATE_RUN标志。
                    if标志被设置,list又一次插入结构数组,并激活TASKLET_SOFTIRQ或HI_SOFTIRQ软中断,这个tasklet
    被延迟
                    else 设置TASKLET_STATE_RUN标志,以便tasklet不能在其它CPU上执行            
            b.通过查看tasklet描写叙述符的count字段,看tasklet是否被禁止。假设是清TASKLET_STATE_RUN标志,把
    list又一次插入结构数组,并激活对应的软中断。
            c.假设tasklet被激活,清TASKLET_STATE_SCHED标志,并运行tasklet函数
编写一个设备驱动程序的步骤
1.分配一个新的tasklet_struct数据结构,并用tasklet_init()初始化它;
2.实现tasklet函数
3.禁止或使能tasklet

tasklet结构体

  1. struct  tasklet_struct  
  2. {  
  3.      struct  tasklet_struct *next;  
  4.     unsigned  long  state;  
  5.     atomic_t count;  
  6.      void  (*func)(unsigned  long );  
  7.     unsigned  long  data;  
  8. };  
  9.   
  10. tasklet结构变量是tasklet_vec链表的一个节点,next是链表的下一节点,state使用了两个位例如以下  
  11. enum   
  12. {  
  13.     TASKLET_STATE_SCHED,     /* 1已经被调度,0表示还没调度*/   
  14.     TASKLET_STATE_RUN    /* 1tasklet正在运行,0表示尚未运行,仅仅针对SMP有效,单处理器无意义 */   
  15. };  
  16.   
  17. count用于禁止使能,每禁止一次计数加一,没使能一次计数减一,仅仅有禁止次数和使能次数一样(count等于0)时tasklet才会运行调用函数。  
  18. func 运行函数不能有导致睡眠、不能堵塞的代码。  
  19. data 运行函数的參数  


tasklet的定义

  1. 定义时初始化     
  2.     定义变量名为name的tasklets_struct变量,并初始化调用函数为func,參数为data,使能tasklet  
  3.     DECLARE_TASKLET(name, func, data);     #define DECLARE_TASKLET(name, func, data) \  
  4.      struct  tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }  
  5.   
  6.     定义变量名为name的tasklets_struct变量,并初始化调用函数为func,參数为data,禁止tasklet  
  7.     DECLARE_TASKLET_DISABLED(name, func, data);  
  8.     #define DECLARE_TASKLET_DISABLED(name, func, data) \   
  9.      struct  tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }  
  10.   
  11. 执行中初始化    先定义     struct  tasklet_struct name ;  
  12.     后初始化    
  13.   
  14. void  tasklet_init( struct  tasklet_struct *t, void  (*func)(unsigned  long ), unsigned  long  data)  
  15. {  
  16.     t->next = NULL;               //   
  17.     t->state = 0;                 //设置为未调度 未执行     
  18.     atomic_set(&t->count, 0);     //默认使能   
  19.     t->func = func;               //调用函数   
  20.     t->data = data;               //调用函数參数   
  21. }  

tasklet的调用过程

  1. static   inline   void  tasklet_schedule( struct  tasklet_struct *t);使用此函数就可以完毕调用  
  2. static   inline   void  tasklet_schedule( struct  tasklet_struct *t)  
  3. {  
  4.      /*test_and_set_bit设置调度位TASKLET_STATE_SCHED,test_and_set_bit返回t->state设置前状态,假设设置前状态为1(已被调用)那么直接退出否则进入__tasklet_schedule函数*/   
  5.      if  (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))  
  6.         __tasklet_schedule(t);  
  7. }  
  8.   
  9.   
  10. void  fastcall __tasklet_schedule( struct  tasklet_struct *t)  
  11. {  
  12.     unsigned  long  flags;  
  13.     local_irq_save(flags);                       //关中断保存中断状态   
  14.     t->next = __get_cpu_var(tasklet_vec).list;   //这两行用于将新插入的节点 放置在tasklet_vec链表的头部   
  15.     __get_cpu_var(tasklet_vec).list = t;         //    
  16.     raise_softirq_irqoff(TASKLET_SOFTIRQ);       //触发一个软终端   
  17.     local_irq_restore(flags);                    //使能中断的同一时候还恢复了由 local_irq_save() 所保存的中断状态   
  18. }  
  19. 至此调度函数已经触发了一个软中断,详细中断函数看tasklet的初始化  
  20. void  __init softirq_init( void )  
  21. {  
  22.         open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); //能够看到软中断触发后会运行tasklet_action这个函数   
  23.         open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);  
  24. }  
  25.   
  26.   
  27. static   void  tasklet_action( struct  softirq_action *a)  
  28. {  
  29.      struct  tasklet_struct *list;  
  30.   
  31.     local_irq_disable();                        //这里先关中断 保证原子操作   
  32.     list = __get_cpu_var(tasklet_vec).list;     //取出tasklet_vec链表表头   
  33.     __get_cpu_var(tasklet_vec).list = NULL;     //由于以下将会一次处理完,这里能够预先清空tasklet_vec链表,对于为处理完的会又一次增加链表   
  34.                                                 //也能够实如今tasklet的处理函数中又一次增加自己。   
  35.     local_irq_enable();  
  36.   
  37.   
  38.   
  39.      while  (list) {  
  40.          struct  tasklet_struct *t = list;        //取一节点   
  41.   
  42.         list = list->next;                      //循环遍历所有节点    
  43.   
  44.          if  (tasklet_trylock(t)) {               //这里仅仅是測试TASKLET_STATE_RUN标记,防止tasklet反复调用     
  45.                                                 //疑问:这里假设推断tasklet已经在上执行了,trylock失败,那么为什么后面会被又一次增加链表呢,那不是下次又执行了?   
  46.              if  (!atomic_read(&t->count)) {      //疑问: 假设tasklet被禁止了那么后面有把它加回链表中又一次触发一次软中断,这样不是一直有软中断了吗?为什么不在禁止的时候移出链表,使能时候在增加呢?     
  47.                  if  (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))  //检查可调度位是否设置了,正常应该设置了的   
  48.                      BUG();                     
  49.                 t->func(t->data);               //处理调用函数   
  50.                 tasklet_unlock(t);              //清TASKLET_STATE_RUN标记   
  51.                  continue ;  
  52.             }  
  53.             tasklet_unlock(t);  
  54.         }  
  55.   
  56.         local_irq_disable();  
  57.         t->next = __get_cpu_var(tasklet_vec).list;  //对于trylock失败和tasklet禁止的节点会被又一次增加链表   
  58.         __get_cpu_var(tasklet_vec).list = t;  
  59.         __raise_softirq_irqoff(TASKLET_SOFTIRQ);    //发起新的软中断,这里有两条链表一条是处理中的链表list,一个是当前tasklet_vec中的链表,当出现不能处理的节点时将节点又一次增加tasklet_vec中后发起新的软中断,那么未处理的节点也会在下次中断中处理。   
  60.         local_irq_enable();  
  61.     }  
  62. }  

相关函数

  1. /*和tasklet_disable类似,可是tasklet可能仍然执行在还有一个 CPU */   
  2. static   inline   void  tasklet_disable_nosync( struct  tasklet_struct *t)  
  3. {  
  4.     atomic_inc(&t->count);       //降低计数后,t可能正在执行   
  5.     smp_mb__after_atomic_inc();  //保证在多处理器时同步   
  6. }  
  7. /*函数临时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在执行, 这个函数忙等待直到这个tasklet退出*/   
  8.   
  9. static   inline   void  tasklet_disable( struct  tasklet_struct *t){  
  10.    tasklet_disable_nosync(t);   
  11.    tasklet_unlock_wait(t);   //等待TASKLET——STATE_RUN标记清零      
  12.    smp_mb();  
  13. }  
  14.   
  15. static   inline   int  tasklet_trylock( struct  tasklet_struct *t){  
  16.     return  !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);  
  17. }  
  18.   
  19. static   inline   void  tasklet_unlock( struct  tasklet_struct *t){     
  20.         smp_mb__before_clear_bit();       
  21.         clear_bit(TASKLET_STATE_RUN, &(t)->state);  
  22. }  
  23.   
  24. static   inline   void  tasklet_unlock_wait( struct  tasklet_struct *t){  
  25.      while  (test_bit(TASKLET_STATE_RUN, &(t)->state)) {  
  26.           barrier();   
  27.      }  
  28. }  
  29.   
  30. /*使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会非常快执行。tasklet_enable和tasklet_disable必须匹配调用, 由于内核跟踪每一个tasklet的"禁止次数"*/    
  31. static   inline   void  tasklet_enable( struct  tasklet_struct *t)  
  32. {  
  33.     smp_mb__before_atomic_dec();  
  34.     atomic_dec(&t->count);  
  35. }  
  36.   
  37. /*和tasklet_schedule类似,仅仅是在更高优先级执行。当软中断处理执行时, 它处理高优先级 tasklet 在其它软中断之前,仅仅有具有低响应周期要求的驱动才应使用这个函数, 可避免其它软件中断处理引入的附加周期*/   
  38. void  tasklet_hi_schedule( struct  tasklet_struct *t);  
  39.   
  40. /*确保了 tasklet 不会被再次调度来执行,通常当一个设备正被关闭或者模块卸载时被调用。假设 tasklet 正在执行, 这个函数等待直到它执行完成。若 tasklet 又一次调度它自己,则必须阻止在调用 tasklet_kill 前它又一次调度它自己,如同使用 del_timer_sync*/   
  41. void  tasklet_kill( struct  tasklet_struct *t)  
  42. {  
  43.      if  (in_interrupt())  
  44.         printk( "Attempt to kill tasklet from interrupt\n" );  
  45.   
  46.          while  (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {  //检測t是否被调度   
  47.          do   
  48.             yield();  
  49.          while  (test_bit(TASKLET_STATE_SCHED, &t->state));           //等待t调度位清零,还未运行调用函数   
  50.     }  
  51.     tasklet_unlock_wait(t);                                         //等待t调用函数运行完   
  52.     clear_bit(TASKLET_STATE_SCHED, &t->state);                      //函数调用完可能t被又一次增加链表,所以再清一次保证不再调用   
  53. }  
  54. 这个函数不是真的去杀掉被调度的tasklet,而是保证tasklet不再调用  

linux下面的中断处理软件中断tasklet机制


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论