这篇文章介绍一下linux中断的底半部分的tasklet和workquene两种处理机制,其中tasklet中不能有延时函数,workquene的处理函数可以加入延时操作

目录

  • (一)tasklet小任务处理机制
    • (1)tasklet相关函数接口
    • (2)tasklet使用流程
    • (3)tasklet实例代码
  • (二)workquene工作队列处理机制
    • (1)workqueue相关函数接口
    • (2)共享工作队列使用流程
    • (3)自定义工作队列使用流程
    • (4)共享workqueue实例代码

在Linux中为了提高系统的响应速度及并发能力,将Linux的中断划分为顶半部和底半部两部分。
顶半部(top half):做中断的登记操作,当然也可以做不耗时的中断处理(内核中会创建一个中断登记表)。
顶半部完成的一般是紧急的硬件操作,一般包括读取寄存的中断状态,清除中断标志,将底半部处理程序挂到底半部的执行队列中去,此过程不可被打断
底半部(bottom half):处理耗时操作,把耗时的操作放入底半部执行,这个过程可以被打断,耗时操作推后执行

(一)tasklet小任务处理机制

内核中关于tasklet的介绍:

/* Tasklets --- multithreaded analogue of BHs.Main feature differing them of generic softirqs: taskletis running only on one CPU simultaneously.Main feature differing them of BHs: different taskletsmay be run simultaneously on different CPUs.Properties:* If tasklet_schedule() is called, then tasklet is guaranteedto be executed on some cpu at least once after this.* If the tasklet is already scheduled, but its execution is still notstarted, it will be executed only once.* If this tasklet is already running on another CPU (or schedule is calledfrom tasklet itself), it is rescheduled for later.* Tasklet is strictly serialized wrt itself, but notwrt another tasklets. If client needs some intertask synchronization,he makes it with spinlocks.
(1)tasklet相关函数接口

小任务机制相关的数据结构:

struct tasklet_struct
{struct tasklet_struct *next;   //用来实现多个tasklet_struct结构链表unsigned long state;          //当前这个tasklet是否已经被调度atomic_t count;                 //值为0的时候用户才可以调度/*原子变量操作:指的是操作过程中不允许被打断机制typedef struct {int counter;} atomic_t;  */void (*func)(unsigned long);  //指向tasklet绑定的函数指针unsigned long data;               //传向tasklet绑定的函数的参数
};

小任务数据结构创建:

#define DECLARE_TASKLET(name, func, data) \        //静态初始化,默认为使能
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }#define DECLARE_TASKLET_DISABLED(name, func, data) \      //静态初始化,默认为失能
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

初始化小任务:

extern void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);

小任务加锁解锁:

//尝试加锁
static inline int tasklet_trylock(struct tasklet_struct *t)
{return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}//解锁
static inline void tasklet_unlock(struct tasklet_struct *t)
{smp_mb__before_clear_bit(); clear_bit(TASKLET_STATE_RUN, &(t)->state);
}
/*** test_and_set_bit - Set a bit and return its old value* @nr: Bit to set* @addr: Address to count from** This operation is atomic and cannot be reordered.* It may be reordered on other architectures than x86.* It also implies a memory barrier.*/
static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
{unsigned long mask = BIT_MASK(nr);unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);unsigned long old;unsigned long flags;_atomic_spin_lock_irqsave(p, flags);old = *p;*p = old | mask;_atomic_spin_unlock_irqrestore(p, flags);return (old & mask) != 0;
}

小任务登记:

static inline void tasklet_schedule(struct tasklet_struct *t)
{if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))__tasklet_schedule(t);
}

小任务失能:

static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{atomic_inc(&t->count);smp_mb__after_atomic_inc();
}static inline void tasklet_disable(struct tasklet_struct *t)
{tasklet_disable_nosync(t);tasklet_unlock_wait(t);smp_mb();
}

小任务使能:

static inline void tasklet_enable(struct tasklet_struct *t)
{smp_mb__before_atomic_dec();atomic_dec(&t->count);      //单纯的将count减一操作
}

结束小任务:

extern void tasklet_kill(struct tasklet_struct *t);
extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);
(2)tasklet使用流程
  1. 定义结构体并初始化
struct tasklet_struct task;
tasklet_init(&task,自定义函数功能名,函数形参);
  1. 在合适的地方(一般在中断里)对tasklet登记
   tasklet_schedule(&task);
(3)tasklet实例代码

chrdev.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>struct tasklet_struct task;void tasklet_fun(unsigned long data)
{printk("this is tasklet test\n");
}static int __init  tasklet_module_init(void)
{tasklet_init(&task,tasklet_fun,(unsigned long)10);//tasklet_disable(&task);//失能后不能卸载该tasklettasklet_schedule(&task);return 0;
}static void __exit  tasklet_module_cleanup(void)
{tasklet_kill(&task);
}
module_init(tasklet_module_init);
module_exit(tasklet_module_cleanup);
MODULE_LICENSE("GPL");

(二)workquene工作队列处理机制

工作队列提供了将功能推迟到下半部分的通用方法。核心是工作队列(struct workqueue_struct),这是工作所在的结构。内核中通过work_struct结构标识要延迟的工作和要使用的延迟功能。events / X内核线程(每个CPU一个)从工作队列中提取工作,并激活下半部处理程序之一。

工作队列是更新的延迟机制,已在2.5 Linux内核版本中添加。工作队列不是通用的延迟机制,不像Tasklet那样提供一站式的延迟方案,在该机制中,工作队列的处理函数可以休眠(在Tasklet模型中是不可能的),工作队列的延迟可能比任务小,但包含更丰富的API以进行工作延迟,延迟之前是通过keventd任务队列管理,现在由名为events / X的内核工作线程管理。

内核中有两种工作队列,一种是共享工作队列,另一种是自定义工作队列

共享工作队列 :内核提供,用户可直接使用,秩序调用对应的接口即可,更多的时候选择共享消息队列

自定义工作队列:需要用户手动创建,并手动销毁

共享工作队列 自定义工作队列
内核启动期间会创建一个工作全局的工作队列,所有的驱动都可以把自己延后执行的工作函数挂到这个共享工作队列中。 当你要执行工作不希望受到其他工作的影响时,可以自己创建一个工作队列,然后把自己的工作放在自定义的工作队列调度。
优点:不需要自己创建工作队列,简单,快捷,方便。 优点:不会受到其他工作的影响,工作函数执行有保障。
缺点:可能会受到其他工作的影响,前面的工作阻塞,影响到后面的工作的执 缺点:造成系统巨大开销大,如果过多创建自定义工作队列,会严重影响系统实时
(1)workqueue相关函数接口

工作队列数据结构:

struct work_struct {atomic_long_t data;struct list_head entry;work_func_t func;
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};typedef void (*work_func_t)(struct work_struct *work);

声明并初始化工作队列:

 1.静态方式:
#define DECLARE_WORK(n, f)                  \struct work_struct n = __WORK_INITIALIZER(n, f)#define DECLARE_DELAYED_WORK(n, f)              \struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)2. 动态形式初始化:
#define INIT_WORK(_work, _func)                 \do {                           \__INIT_WORK((_work), (_func), 0);      \} while (0)

创建自定义工作队列的时候使用:

extern int queue_work(struct workqueue_struct *wq, struct work_struct *work);

工作队列登记:

extern int schedule_work(struct work_struct *work);
/*** schedule_work - put work task in global workqueue* @work: job to be done** Returns zero if @work was already on the kernel-global workqueue and* non-zero otherwise.** This puts a job in the kernel-global workqueue if it was not already* queued and leaves it in the same position on the kernel-global* workqueue otherwise.*/
int schedule_work(struct work_struct *work)
{return queue_work(system_wq, work);
}

根据结构体成员找到结构体首地址:

/*** container_of - cast a member of a structure out to the containing structure* @ptr:  the pointer to the member.* @type:  the type of the container struct this is embedded in.* @member: the name of the member within the struct.**/
#define container_of(ptr, type, member) ({          \const typeof( ((type *)0)->member ) *__mptr = (ptr);    \(type *)( (char *)__mptr - offsetof(type,member) );})

container_of使用示例:

struct mywork{int m;int n;struct work_struct works;
}test;container_of根据结构体内部的某一成员获取结构的首地址container_of(ptr, type, member) @ptr: 指向结构体成员的指针.  如 struct work_struct *works;* @type:   the type of the container struct this is embedded in.  结构体类型 struct mywork* @member: the name of the member within the struct.  works
(2)共享工作队列使用流程

1.定义共享工作队列结构体并初始化

struct work_struct works;
INIT_WORK(&works,workqueue_fun);

2.在合适位置(一般为中断)对工作队列登记

 schedule_work(&works);
(3)自定义工作队列使用流程

1、创建工作队列

struct workqueue_struct  my_workqueue;
struct workqueue_struct *create_workqueue(&my_workqueue);
//struct workqueue_struct *create_singlethread_workqueue(const char *name);
//create_workqueue函数会在系统中的每个处理器上创建一个线程(多线程),而create_singlethread_workqueue只是创建一个单一的线程,如果单个线程足够使用,那么应该使用create_singlethread_workqueue函数。

2、创建任务

struct work_struct works;
INIT_WORK(&works,workqueue_fun);

3、提交任务,要将任务提交到工作队列中,内核提供了下面两个API:

int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);

这两个函数都会将任务提交到工作队列中,使用queue_delayed_work函数,则提交的任务至少延时由参数delay指定的时间才被执行。

如果要取消工作队列中的某个任务,使用cancel_delayed_work,原型如下:

int cancel_delayed_work(struct work_struct *work);

如果任务在被执行之前取消,那么cancel_delayed_work函数返回非零值,调用该函数之后内核会确保被取消的任务不被执行。但是返回0,则表示任务已经被执行,因此调用cancel_delayed_work函数后,任务有可能仍在运行,所以为了确保任务测地被取消,需要调用flush_workqueue函数,与方法1中的不同。

void flush_workqueue(struct workqueue_struct *wq);

4、销毁工作队列, 使用完工作队列之后,可以使用destroy_workqueue销毁工作队列:

void destroy_workqueue(struct workqueue_struct *wq);
(4)共享workqueue实例代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>struct work_struct works;
void workqueue_fun(struct work_struct * work)
{printk("this is workqueue test\n");
}
static int __init  workqueue_module_init(void)
{INIT_WORK(&works,workqueue_fun);//初始化共享工作队列结构体schedule_work(&works);//将工作队列进行登记return 0;
}
static void __exit  workqueue_module_cleanup(void)
{printk("module is exit\n");
}
module_init(workqueue_module_init);
module_exit(workqueue_module_cleanup);
MODULE_LICENSE("GPL");

本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作

微信公众号:zhjj0729

微博:文艺to青年

(十三)linux中断底半部分处理机制相关推荐

  1. linux 中断底半部的实现

    一.描述 工作队列(work queue) 类似 tasklet,允许调用者请求在将来某个时间调用一个函数. tasklet 在软件中断上下文中运行,所以tasklet执行很快,持续短,并且一般在原子 ...

  2. linux中断底半部机制

    中断处理程序 ----中断处理程序ISR是在中断发生时被调用时用来处理中断的函数,在中断运行期间,不能 ----执行有可能引起睡眠测操作,不能同用户空间交换数据,不能调用schedule函数,实现 - ...

  3. Linux内核中断底半部处理--工作队列

    工作队列的使用过程: 工作队列相关函数介绍: #include <workqueue.h> /*头文件包含*/ 1.工作队列的创建及销毁: 定义一个工作队列结构体指针 static str ...

  4. 中断底半部:softirq、tasklet、workqueue

    为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top  half)和底半部(bottom half). 顶半部完成尽可能少的比 ...

  5. 基于ARM A53开发板,使用按键中断及中断底半部实现《led灯状态取反》的驱动

    #include <linux/init.h> #include <linux/module.h> #include <linux/timer.h> #includ ...

  6. Linux内核中断顶半部和底半部的理解

    工科生一枚,热衷于底层技术开发,有强烈的好奇心,感兴趣内容包括单片机,嵌入式Linux,Uboot等,欢迎学习交流! 爱好跑步,打篮球,睡觉. 欢迎加我QQ1500836631(备注CSDN),一起学 ...

  7. 上下文保存 中断_Linux内核中断顶半部和底半部的理解

    @[toc] 中断上半部.下半部的概念   设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍.但是,这个良好的愿望往往与现实并不吻合.在大多数真实的系 ...

  8. linux 中断程序设计,Linux中断编程

    Linux实现底半部机制主要有tasklet,工作队列和软中断. 1.tasklet tasklet的使用较简单,我们只需要定义tasklet及其处理函数并将两者关联.使用模版如下: /*定义task ...

  9. 【Linux驱动编程】Linux中断上半部和下半部

    前言   cpu在执行程序时,如果有外部中断触发时,如定时器中断.串行总线中断等,cpu停止当前任务从而转去响应中断处理.对于中断函数的处理,原则是尽快处理完事务并退出中断,这一点也比较好理解,尽快处 ...

最新文章

  1. android动态设置冷启动图片拉伸变形,Android冷启动时间优化
  2. 【技术】一个由于时间问题引发的血案
  3. js实现的简单模态对话框
  4. try catch finally的执行顺序到底是怎样的?
  5. 【飞秋】WF3.0和4.0区别介绍
  6. 信息学奥赛一本通 1016:整型数据类型存储空间大小 | OpenJudge NOI 1.2 01
  7. Mysql数据库设计规范之三数据库SQL开发规范
  8. 从JAVA内存到垃圾回收,带你深入理解JVM
  9. web 开发一个能进行人员管理(查询,删除,添加)的应用
  10. 我的Android进阶之旅------Android检测wifi连接状态
  11. linux mint 14 shurufa
  12. 标准模板库(STL)之 vector 列传 (二)
  13. 计算机视觉基本研究方向
  14. python语言基础与应用 mooc答案_Python语言基础与应用_中国大学 MOOC_章节考试选修课答案...
  15. diskgenius创建efi分区_无损分区大小调整
  16. 【HCIE安全】双机热备-主备备份
  17. 非线性发展方程定解问题
  18. en结尾的单词_en后缀形容词——动词
  19. android allow usb debugging,Android USB debugging 功能失效
  20. 学计算机怎么介绍自己的产品,商品基本描述

热门文章

  1. VS Code 常用快捷键
  2. Flowable springboot项目自定义中文字体
  3. flowable实战(一)flowable与spring boot集成
  4. 企业级实战02_SpringMVC整合ActiveMQ 实战需求
  5. HTTP系列学习(笔记一):一文带你详解HTTP协议
  6. binlog日志_mysql 重要日志文件总结
  7. android api接口文档,API 接口文档
  8. java.lang.ClassNotFoundException: lombok.Data
  9. oracle导出数据视频教程,Oracle导入导出数据的几种方式
  10. cocos android-1,Cocos2D-Android-1之源码详解:5.Box2dTest