Linux设备驱动之workqueue----中断底半部机制

文章为本人学习笔记和总结,如有错误,请多多指教;

引言:

linux实现中断底半部的机制主要有tasklet、工作队列、软中断和线程化;

本文主要介绍下工作队列---workqueue

1、工作队列(workqueue)简介

Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化。workqueue是内核中实现简单而有效的机制,他显然简化了内核daemon的创建,方便了用户的编程.

      工作队列(workqueue)是另外一种将工作推后执行的形式.工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠;

由于每一个工作队列都有一个或者多个专用的进程(内核线程),这些进程运行提交到该队列的函数;

从表面看工作队列(workqueue)类似于tasklet,它们都允许内核代码请求某个函数在将来的时间被调用。但两者有很大差别:

  • tasklet在软中断上下文中运行,因此所有的tasklet代码必须是原子的;而工作队列函数是在特殊的内核进程上下文中运行,因此具有更好的灵活性,如工作队列可以调用和休眠
  • tasklet始终运行在被初始提交的同一CPU上,但这只是工作队列的默认方式
  • 内核代码可以请求延时给定的时间间隔后执行工作队列函数

两者关键区别:tasklet会在很短时间段内执行,并且是原子模式的,而工作队列函数具有更长延时并且不必原子化;

2、工作队列(workqueue)使用

  •  工作(work)数据结构:
struct work_struct {  
    atomic_long_t data;       /*工作处理函数func的参数*/  
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */  
#define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */  
#define WORK_STRUCT_FLAG_MASK (3UL)  
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)  
    struct list_head entry;        /*连接工作的指针*/  
    work_func_t func;              /*工作处理函数*/  
#ifdef CONFIG_LOCKDEP  
    struct lockdep_map lockdep_map;  
#endif  
}; 
//工作队列执行函数的原型:
void (*work_func_t)(struct work_struct *work);
//该函数会由一个工作者线程执行,因此其在进程上下文中,可以睡眠也可以中断。但只能在内核中运行,无法访问用户空间。
  • 工作将以队列形式组织成工作队列(workqueue),其数据结构如下:
struct workqueue_struct {
 struct cpu_workqueue_struct *cpu_wq;
 struct list_head list;
 const char *name;   /*workqueue name*/
 int singlethread;   /*是不是单线程 - 单线程我们首选第一个CPU -0表示采用默认的工作者线程event*/
 int freezeable;  /* Freeze threads during suspend */
 int rt;
}; 
  • 多CPU多线程工作队列cpu_workqueue_struct 
truct cpu_workqueue_struct {
 spinlock_t lock;/*因为工作者线程需要频繁的处理连接到其上的工作,所以需要枷锁保护*/
 struct list_head worklist;
 wait_queue_head_t more_work;
 struct work_struct *current_work; /*当前的work*/
 struct workqueue_struct *wq;   /*所属的workqueue*/
 struct task_struct *thread; /*任务的上下文*/
} ____cacheline_aligned;

综上所述:工作有struct work_struct的类型,工作队列有struct workqueue_struct的类型,该结构定义在<linux/workqueue.h>中。在使用前,我们必须显示的创建一个工作队列,介绍如下:

  • 创建工作队列workqueue

在使用工作队列之前,首先我们需要创建工作队列,可以使用下面两个函数之一:

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name); 

两个函数的区别:

使用create_workqueue()函数,内核则会在系统中的每一个CPU上为该工作队列创建专用的线程;这种情况下,可能会对系统性能造成一定影响。

而如果单个线程能够满足要求,则建议使用create_singlethread_workqueue()函数创建工作队列。

  • 提交工作work

1)创建好工作队列后,那么如何提交工作到一个工作队列,首先需要填充work_struct结构体,可以有如下方法:

DECLARE_WORK(name, void (*func)(void *), void *data)
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data)
PREPARE_WORK(struct work_struct *work, void (*func)(void *), void *data)

区别如下:

DECLARE_WORK()是一个宏定义,并且该宏定义是在编译时完成的,即静态创建方法,其中name为结构名称,func是工作队列要调度的函数,data是传递给该函数的参数;

如果要在运行时构造work_struct结构,应该使用后两个宏。INIT_WORK完成更加彻底的初始化工作,因此在首次使用该结构时,建议使用这个宏。而PREPARE_WORK完成几乎相同的工作,但是它不会初始化链接work_struct结构的工作队列的指针;因此如果工作work_struct已经被提交到工作队列中,而只是需要修改该结构,则应该使用PREPARE_WORK。

2)然后要将工作提交到工作队列,则使用下面两个函数之一:

int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work,
                unsigned long delat);

它们都会将work添加到指定的workqueue。但是如果使用queue_delayed_work,则实际的工作至少会在经过指定的jiffies(由delay指定)之后才会被执行;如果工作被成功添加到队列,则上述函数返回值为1,。返回值为非零时,则意味着给定的work_struct结构已经等待在该队列中,从而不能添加两次。

3、工作队列调度schedule

使用如下函数调度工作队列中的工作,使用如下函数之一:

int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);

两者区别:如果要马上调度运行work,则使用schedule_work()函数,即一旦其所在的处理器上的工作者线程被唤醒,它就会被执行;有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行,则使用schedule_delayed_work()函数。

工作队列是没有优先级的,基本按照FIFO的方式进行处理;

4、工作队列取消

如果要取消某个挂起的工作队列入口项,可调用:

int cancel_delayed_work(struct work_struct *work);
void flush_workqueue(struct workqueue_struct *queue);

在结束对工作队列的使用后,可调用下面函数释放相关资源:

void destroy_workqueue(struct workqueue_struct *queue);
发布了14 篇原创文章 · 获赞 15 · 访问量 4003

猜你喜欢

转载自blog.csdn.net/wsq_zqfl/article/details/84103446