linux驱动学习之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 plaincopyprint?

  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 执行函数的参数

2.tasklet的定义

[cpp] view plaincopyprint?

  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. }

3.tasklet的调用过程

[cpp] view plaincopyprint?

  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. }

4.相关函数

[cpp] view plaincopyprint?

    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不再调用

linux驱动学习之tasklet分析

时间: 2024-11-07 07:26:10

linux驱动学习之tasklet分析的相关文章

linux 驱动学习笔记01--Linux 内核的编译

由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make config(基于文本的最为传统的配置界面,不推荐使用)#make menuconfig(基于文本菜单的配置界面)#make xconfig(要求 QT 被安装)#make gconfig(要求 GTK+被安装)在配置 Linux 2.6 内核所使用的 make config. make menuconfig. mak

Linux驱动学习之Linux-2.6.20.4内核移植

最近一段时间一直在学习向TQ2440开发板移植内核.移植驱动.真心觉得这方面的知识有很大的难度.但是从另一角度去看,难度越大,能力提升的空间就越大!! 1.解压源码 从网上下载一个Linux 内核,我是用的是Linux-2.6.20.4.然后用命令解压.建议解压到"/home/用户名"目录下.我的内核源码存放在: 2.添加对ARM的支持 因为所用的是TQ2440开发板,属于ARM9.因此要在系统中添加对ARM的支持. 方法:进入内核源码目录, 修改"Makefile"

Linux驱动学习之TQ2440 DM9000E网卡驱动移植(Linux-2.6.30.4)

引言 在之前的文章中,我们介绍了如何使用Scala IDE也就是eclipse中集成的Scala开发插件来进行Scala语言程序的开发,在使用了一段时间之后,发现eclipse对Scala的支持并不是很好.用户体验比较差,比如联想速度比较慢等.由于在公司一直使用的Scala开发工具是Intellij IDEA(好吧,其实我使用Scala IDE的目的就是想试一下这两个各有什么优缺点),各方面感觉还不错,所以在此介绍一下这个开发环境. Intellij IDEA是jetbrain开发的一个IDE,

linux驱动学习(1)——字符设备驱动开发

(一)驱动程序介绍 (a)Linux驱动程序学习 知识结构: 1. Linux驱动程序设计模式(40%) 2. 内核相关知识(30%) 3. 硬件相关知识(30%) (b)驱动分类: ①字符设备: 字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用. ②块设备: 在大部分的 Unix 系统, 块设备不能按字节处理数据,只能一次传送一个或多个长度是512字节( 或一个更大的 2 次幂的数 )的整块数据,而Lin

Linux驱动学习步骤(转载)

1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, lsmod, rmmod. 在驱动的init函数里打印hello world, insmod后应该能够通过dmesg看到输出. 5. 写一完整驱动, 加上read, write, ioctl, polling等各种函数的驱动实现. 在ioctl里完成从用户空间向内核空间传递结构体的实现. 6. 写一bl

【Linux驱动学习】SD卡规范学习

摘要: 学习SD卡的相关规范,包括定义,硬件特性,数据传输,命令系统等.不涉及代码. 文章针对Linux驱动开发而写,以助于理解SD卡驱动,不会涉及过多硬件内容. 纲要: 1. SD卡介绍 2. SD卡硬件规范 3. SD卡指令规范 4. SD卡寄存器 1. SD卡介绍 1.1 各类型储存卡/接口 首先了解一下我们在SD卡驱动学习中会碰到的主要几个储存卡名词: SD:Security Digital Memory Card,新一代多媒体储存卡,高速,安全(但安全机制貌似很少用到)MMC:Mult

Linux驱动学习之驱动开发准备工作

一.开启驱动开发之路 1.驱动开发的准备工作 (1)正常运行linux系统的开发板.要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的.原因在于在安装模块的时候会进行安全性校验 (2)内核源码树,其实就是一个经过了配置编译之后的内核源码.我们需要内核源码编译自己的模块 (3)nfs挂载的rootfs,主机ubuntu中必须搭建一个nfs服务器.这对于驱动开发前期是不可或缺的,能够提高我们的开发效率. 2.驱动开发的步骤 (1)驱动源码编写.Makefile编写.编译 (2)

Linux驱动学习之常用的模块操作命令

1.常用的模块操作命令 (1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 (2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko (3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息.,用法是modinfo xxx.ko,注意要加.ko,也就是说是一个静态的文件形式. (4)rmmod(remove module,卸载

linux 驱动学习(一)简单的字符设备驱动程序

linux 系统将设备分为三种类型:字符设备.块设备和网络接口设备. 文章将先给出字符设备驱动程序,参照程序记录知识点,可能会不全,以后会慢慢加 .知识点记录完成后,会贴出字符设备驱动程序的测试程序并记录测试过程. 注释版 1 #include "linux/kernel.h" //内核头文件,含有一些内核常用函数的原形定义 2 #include "linux/module.h" //包含大量加载模块需要的函数和符号的定义 3 #include "linu