linux中断处理下文:软中断tasklet机制分析

参考:

《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结构体

[cpp] view
plain
copyprint?

  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. tasklet结构变量是tasklet_vec链表的一个节点,next是链表的下一节点,state使用了两个位如下
  10. enum
  11. {
  12. TASKLET_STATE_SCHED,    /* 1已经被调度,0表示还没调度*/
  13. TASKLET_STATE_RUN   /* 1tasklet正在执行,0表示尚未执行,只针对SMP有效,单处理器无意义 */
  14. };
  15. count用于禁止使能,每禁止一次计数加一,没使能一次计数减一,只有禁止次数和使能次数一样(count等于0)时tasklet才会执行调用函数。
  16. func 执行函数不能有导致睡眠、不能阻塞的代码。
  17. data 执行函数的参数

tasklet的定义

[cpp] view
plain
copyprint?

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

tasklet的调用过程

[cpp] view
plain
copyprint?

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

相关函数

[cpp] view
plain
copyprint?

  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. static inline void tasklet_disable(struct tasklet_struct *t){
  9. tasklet_disable_nosync(t);
  10. tasklet_unlock_wait(t);  //等待TASKLET——STATE_RUN标记清零
  11. smp_mb();
  12. }
  13. static inline int tasklet_trylock(struct tasklet_struct *t){
  14. return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
  15. }
  16. static inline void tasklet_unlock(struct tasklet_struct *t){
  17. smp_mb__before_clear_bit();
  18. clear_bit(TASKLET_STATE_RUN, &(t)->state);
  19. }
  20. static inline void tasklet_unlock_wait(struct tasklet_struct *t){
  21. while (test_bit(TASKLET_STATE_RUN, &(t)->state)) {
  22. barrier();
  23. }
  24. }
  25. /*使能一个之前被disable的tasklet;若这个tasklet已经被调度, 它会很快运行。tasklet_enable和tasklet_disable必须匹配调用, 因为内核跟踪每个tasklet的"禁止次数"*/
  26. static inline void tasklet_enable(struct tasklet_struct *t)
  27. {
  28. smp_mb__before_atomic_dec();
  29. atomic_dec(&t->count);
  30. }
  31. /*和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet 在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期*/
  32. void tasklet_hi_schedule(struct tasklet_struct *t);
  33. /*确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行, 这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill 前它重新调度它自己,如同使用 del_timer_sync*/
  34. void tasklet_kill(struct tasklet_struct *t)
  35. {
  36. if (in_interrupt())
  37. printk("Attempt to kill tasklet from interrupt\n");
  38. while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { //检测t是否被调度
  39. do
  40. yield();
  41. while (test_bit(TASKLET_STATE_SCHED, &t->state));          //等待t调度位清零,还未执行调用函数
  42. }
  43. tasklet_unlock_wait(t);                                        //等待t调用函数执行完
  44. clear_bit(TASKLET_STATE_SCHED, &t->state);                     //函数调用完可能t被重新加入链表,所以再清一次保证不再调用
  45. }
  46. 这个函数不是真的去杀掉被调度的tasklet,而是保证tasklet不再调用
时间: 2024-11-19 15:42:31

linux中断处理下文:软中断tasklet机制分析的相关文章

Linux x86_64 APIC中断路由机制分析

不同CPU体系间的中断控制器工作原理有较大差异,本文是<Linux mips64r2 PCI中断路由机制分析>的姊妹篇,主要分析Broadwell-DE X86_64 APIC中断路由原理.中断配置和处理过程,并尝试回答如下问题: 为什么x86中断路由使用IO-APIC/LAPIC框架,其有什么价值? pin/irq/vector的区别.作用,取值范围和分配机制? x86_64 APIC关键概念 Pin 此处的pin特指APIC的中断输入引脚,与内外部设备的中断输入信号相连.从上图中可以看出,

Linux mips64r2 PCI中断路由机制分析

Linux mips64r2 PCI中断路由机制分析 本文主要分析mips64r2 PCI设备中断路由原理和irq号分配实现方法,并尝试回答如下问题: PCI设备驱动中断注册(request_irq)时的irq#从哪里来?是硬件相关?还是软件相关? 中断上报时,CPU是如何获得这个irq#的? 本文主要分析PIC(可编程中断控制器)的工作原理,PIC一般集成在CPU中,不同arch.vendor CPU的PIC实现原理也不尽相同.本文基于kerne3.10 + mips64r2 XXX CPU分

《深入理解Linux内核》软中断/tasklet/工作队列

软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的"下半部"(bottom half)演变而来.下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者.本文重点在于介绍这三者之间的关系.(函数细节将不会在本文中出现,可以参考文献,点这里) (1)上半部和下半部的区别上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务.举个例子:在网络传输中,网卡接收到数据包这个事件不一

Linux X86下的TLB机制分析

TLB - translation lookaside buffer 快表,直译为翻译后备缓冲器,也可以理解为页表缓冲,地址变换高速缓存. 由于页表存放在主存中,因此程序每次访存至少需要两次:一次访存获取物理地址,第二次访存才获得数据.提高访存性能的关键在于依靠页表的访问局部性.当一个转换的虚拟页号被使用时,它可能在不久的将来再次被使用到,. TLB是一种高速缓存,内存管理硬件使用它来改善虚拟地址到物理地址的转换速度.当前所有的个人桌面,笔记本和服务器处理器都使用TLB来进行虚拟地址到物理地址的

Linux内核抢占实现机制分析【转】

Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介绍了内核抢占和用户抢占的概念和区别,接着分析了不可抢占内核的特点及实时系统中实现内核抢占的必要性.然后分析了禁止内核抢占的情况和内核抢占的时机,最后介绍了实现抢占内核所做的改动以及何时需要重新调度. [关键字]内核抢占,用户抢占,中断, 实时性,自旋锁,抢占时机,调度时机,schedule,pree

linux中断处理原理分析

首先需要了解一下中断的概念:一个"中断"仅仅是一个信号,当硬件需要获得处理器对它的关注时,就可以发送这个信号.内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表. 模块在使用中断前要先请求一个中断通道(或中断请求IRQ),然后在使用后释放该通道.用到的API就是request_irq()以及free_irq().注意在调用request_irq()和free_irq()的时机最好是在设备第一次打开和最后一次关闭之后. 对于中断处理例程来讲,它的一个典型的任务就是:如果中

Linux中断下半部tasklet机制

平台:Linux2.6.18 一,      软中断 1.1         在文件<linux/interrupt.h>中 1.1.1     当前内核用到的软中断类型 1 enum 2 { // HI_SOFTIRQ,TASKLET_SOFTIRQ为tasklet用软中断实现时用到的两个软中断 3 HI_SOFTIRQ=0, 4 TIMER_SOFTIRQ, 5 NET_TX_SOFTIRQ, 6 NET_RX_SOFTIRQ, 7 BLOCK_SOFTIRQ, 8 TASKLET_SOF

Linux内核抢占实现机制分析

Sailor_forever  [email protected] 转载请注明 http://blog.csdn.net/sailor_8318/archive/2008/09/03/2870184.aspx [摘要]本文详解了Linux内核抢占实现机制.首先介绍了内核抢占和用户抢占的概念和区别,接着分析了不可抢占内核的特点及实时系统中实现内核抢占的必要性.然后分析了禁止内核抢占的情况和内核抢占的时机,最后介绍了实现抢占内核所做的改动以及何时需要重新调度. [关键字]内核抢占,用户抢占,中断, 

linux驱动学习之tasklet分析

tasklet是中断处理下半部分最常用的一种方法,驱动程序一般先申请中断,在中断处理函数内完成中断上半部分的工作后调用tasklet.tasklet有如下特点: 1.tasklet只可以在一个CPU上同步地执行,不同的tasklet可以在不同地CPU上同步地执行. 2.tasklet的实现是建立在两个软件中断的基础之上的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,本质上没有什么区别,只不过HI_SOFTIRQ的优先级更高一些 3.由于tasklet是在软中断上实现的,所以像软中断一