当前位置: 首页 > news >正文

linux设备驱动编写_tasklet机制

在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成。

为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们需要把一些在中断处理中不是非常紧急的任务放在后面执行,而让中断处理程序尽快返回。在老版本的 linux 中通常将中断处理分为 top half handler 、 bottom half handler 。利用 top half handler 处理中断必须处理的任务,而 bottom half handler 处理不是太紧急的任务。

但是 linux2.6 以后的 linux 采取了另外一种机制,就是软中断来代替 bottom half handler的处理。而 tasklet 机制正是利用软中断来完成对驱动 bottom half 的处理。 Linux2.6 中软中断通常只有固定的几种: HI_SOFTIRQ( 高优先级的 tasklet ,一种特殊的 tasklet) 、TIMER_SOFTIRQ (定时器)、 NET_TX_SOFTIRQ (网口发送)、 NET_RX_SOFTIRQ (网口接收) 、 BLOCK_SOFTIRQ (块设备)、 TASKLET_SOFTIRQ (普通 tasklet )。当然也可以通过直接修改内核自己加入自己的软中断,但是一般来说这是不合理的,软中断的优先级比较高,如果不是在内核处理频繁的任务不建议使用。通常驱动用户使用 tasklet 足够了。

软中断和 tasklet 的关系如下图:

 

 
   
   上图可以看出, ksoftirqd 是一个后台运行的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了( pend ),就执行对应的处理函数,对于 tasklet 来说,此处理函数就是 tasklet_action ,这个处理函数在系统启动时初始化软中断的就挂接了。

Tasklet_action 函数,遍历一个全局的 tasklet_vec 链表(此链表对于 SMP 系统是每个CPU 都有一个),此链表中的元素为 tasklet_struct 。此结构如下 :

struct tasklet_struct

{

       struct tasklet_struct *next;

       unsigned long state;

       atomic_t count;

       void (*func)(unsigned long);

       unsigned long data;

};

每个结构一个函数指针,指向你自己定义的函数。当我们要使用 tasklet ,首先新定义一个tasklet_struct 结构,并初始化好要执行函数指针,然后将它挂接到 task_vec 链表中,并发一个软中断就可以等着被执行了。

原理大概如此,对于 linux 驱动的作者其实不需要关心这些,关键是我们如何去使用 tasklet这种机制。

Linux 中提供了如下接口:

DECLARE_TASKLET(name,function,data) :此接口初始化一个 tasklet ;其中 name是 tasklet 的名字, function 是执行 tasklet 的函数; data 是 unsigned long 类型的function 参数。

static inline void tasklet_schedule(struct tasklet_struct *t) :此接口将定义后的tasklet 挂接到 cpu 的 tasklet_vec 链表,具体是哪个 cpu 的 tasklet_vec 链表,是根据当前线程是运行在哪个 cpu 来决定的。此函数不仅会挂接 tasklet ,而且会起一个软 tasklet 的软中断 , 既把 tasklet 对应的中断向量挂起 (pend) 。

两个工作完成后,基本上可以了, tasklet 机制并不复杂,很容易的使程序尽快的响应中断,避免造成中断丢失。

 

--

 

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来决定的。

 

1.tasklet结构体

 

[cpp]  view plain  copy
 
 print?
  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 执行函数的参数  

 

 

2.tasklet的定义

 

[cpp]  view plain  copy
 
 print?
  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. }  

 

3.tasklet的调用过程

 

[cpp]  view plain  copy
 
 print?
  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. }  


4.相关函数

 

 

[cpp]  view plain  copy
 
 print?
    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不再调用  

转载于:https://www.cnblogs.com/Ph-one/p/5884086.html

相关文章:

  • hrtimer和work工作队列的使用
  • nautilus-open-terminal很有用的插件--鼠标右键打开终端
  • userdebug版本开机串口log打开
  • no branch 问题
  • 网页撤销后ubuntu本地撤销
  • 电子类网站
  • ubuntu查看内存占用和查看cpu使用情况的简单方法(ubuntu内存管理)
  • 文件映射mmap
  • Linux的bg和fg命令简单介绍
  • ubuntu下查看cpu信息
  • ubuntu安装和查看已安装
  • ISO C90 forbids mixed declarations and code 警告
  • Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现
  • Linux下c++中的atoi、atol、atoll、atof函数调用实例
  • 图像处理的基本概念
  • crontab执行失败的多种原因
  • eclipse的离线汉化
  • JavaScript设计模式之工厂模式
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • spring cloud gateway 源码解析(4)跨域问题处理
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • 产品三维模型在线预览
  • 复杂数据处理
  • 缓存与缓冲
  • 京东美团研发面经
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 深入浅出webpack学习(1)--核心概念
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 新手搭建网站的主要流程
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • # 数论-逆元
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • #QT项目实战(天气预报)
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • (HAL库版)freeRTOS移植STMF103
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (九)信息融合方式简介
  • (亲测有效)解决windows11无法使用1500000波特率的问题
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • /boot 内存空间不够
  • @Autowired和@Resource的区别
  • [ArcPy百科]第三节: Geometry信息中的空间参考解析
  • [AutoSar]BSW_Memory_Stack_004 创建一个简单NV block并调试
  • [C# 开发技巧]如何使不符合要求的元素等于离它最近的一个元素
  • [delphi]保证程序只运行一个实例
  • [flink总结]什么是flink背压 ,有什么危害? 如何解决flink背压?flink如何保证端到端一致性?
  • [Flutter]设置应用包名、名称、版本号、最低支持版本、Icon、启动页以及环境判断、平台判断和打包
  • [IE技巧] IE8中HTTP连接数目的变化
  • [iOS]-NSTimer与循环引用的理解
  • [java/jdbc]插入数据时获取自增长主键的值
  • [JavaWeb玩耍日记]Maven的安装与使用
  • [PHP]实体类基类和序列化__sleep问题
  • [PostgreSQL的 SPI_接口函数]