驱动学习之延时机制(tasklet与工作队列)

前言:linux内核处理中断的时候,经常要用到延时机制,这里学习一下

一、tasklet

1.1 tasklet 引入

tasklet是softirq的一种,根据优先级的不同,内核将tasklet分为两种,在softirq中对应TASKLET_SOFTIRQ和HI_SOFTIRQ,后者优先级高于前者。
说起来tasklet的机制,不知道从何说起,这里我简单说一下我的总体理解:

  1. 首先tasklet是运行在中断上下文的,所以不能有睡眠的操作。这里可能刚开始接触会有疑问,不是延迟机制吗?为什么不能睡眠,那还有啥用?这里说一下,中断处理的时候,由于要关闭中断,时间太长可能会对系统造成影响,所以顶半部会很快的处理完然后打开中断,这个时候进入底半部也就是tasklet部分的话,这个时候虽然中断打开了,但是还是在中断上下文中执行tasklet的执行函数,所以如果有睡眠等操作,会被调度器调度出去。由于是中断上下文,调度器无法找到,所以不能有睡眠等。(不知道这里理解对不对)
  2. tasklet在内核中实现的办法就是初始化的时候,中断类型为TASKLET_SOFTIRQ和HI_SOFTIRQ都会初始化一个自己对应的链表,当我们调用tasklet_schedule提交一个我们在驱动程序中定义的tasklet的时候,我们的tasklet会作为一个节点加入到TASKLET_SOFTIRQ所对应的的链表中,然后当中断到来的时候,执行到底半部的时候,如果中断类型为TASKLET_SOFTIRQ对应的链表中有我们提交的未被执行的节点的时候,就会执行我们提交的节点中的成员函数。
  3. 通过tasklet机制,我们可以把我们不着急的代码都放在tasklet执行函数中,挂载到链表中,等待执行。这就是延时;

1.2 驱动中使用方法

上面说了下机制,但是可能只是我自己能看懂,这里不追求完美了,直接列出驱动中实现吧:

1.分配对象
struct tasklet_struct
{
struct tasklet_struct *next;
void (*func)(unsigned long data); //tasklet底半部处理函数
unsigned long data; //向底半部传递的数据
};

2.初始化对象
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

3.调用tasklet处理函数执行
tasklet_schedule(struct tasklet_struct *t)

二、工作队列

工作队列是另外一种实现延时的方法。这里简单分析一下原理及驱动中实现的方法。
驱动中使用的办法:

1.分配对象
DECLARE_WORK(n, f)
struct work_struct {
atomic_long_t data; //atomic_set
work_func_t func; -------->typedef void (*work_func_t)(struct work_struct *work); 底半部处理函数
}
struct work_struct work;

2.初始化
INIT_WORK(&work, _func)

3.创建一个工作队列
create_workqueue(struct workqueue wq)

4.将工作节点加入到队里中
queue_work(struct workqueue wq,work_struct work)
queue_delayed_work(struct workqueue wq,work_struct work,unsigned long delay)//等待delay时间之后,才提交等到队列。
int schedule_work(struct work_struct *work)(将工作节点直接添加到系统自己的工作队列中)

5.驱动卸载时释放资源
destroy_workqueue(struct workqueue w)

实现机制:
在这里插入图片描述
上面的图描述了工作队列的实现框架,我们可以看到,工作队里里面包含了很多东西。我这里大概描述一下机制,对它有个大概的了解:

  1. 首先我们需要创建一个工作队列(linux系统初始化的时候,会创建一个keventd_eq的工作队列,我们也可以使用)。
  2. 生成一个线程worker_thread,linux内核中所谓的线程其实是一个进程,并将新的进程加入到系统的运行队列中,这样就可以被调度,同时会先把改进程睡眠在该工作队列的等待队列头上。
  3. 然后我们通过queue_work(schedule_work向内核自己的工作队列中添加)将我们自己创建的工作节点work,添加到工作队列中的worklist链表中。
  4. worker_thread会循环检测,一旦发现我们将新的work提交到worklist,就会醒来调用这个work节点中的func函数。处理完之后除非再次被提交,否则不会再次出现在工作队列中了。
  5. 当驱动卸载是,我们应该从内存中删除用到的等待队列。

注:

  1. 等待队列不需要依赖于中断执行,随时可以执行。
  2. 等待队列因为是单独的进程里面执行延迟的函数,所以允许睡眠等操作。这是跟tasklet的本质上的不同。
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页