向下之旅(十一):下半部和推后执行的工作(二)

  工作队列是另外一种将工作推后执行的形式,通过交由一个内核线程去执行——这个下半部分总是在进程上下文中执行,所以工作队列允许重新调度甚至是睡眠。

  工作队列的实现

  工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。这些内核线程被称为工作者线程。工作队列允许让你的驱动程序创建一个专门的工作者线程来处理要推后的工作,不过,它提供了一个默认的工作者线程来处理这些工作。因此,通常是将推后的工作交给这个默认的工作者线程,叫做events/n,n代表处理器的编号,每个处理器对应一个线程。该结构如下:

  

其中有一个由 cpu_workqueue_struct结构组成的数组,其中的每一项对应一个处理器,每一个处理器对应一个工作者进程。该结构体如下:

  

每个工作者线程类型关联一个自己的workqueue_struct。在该结构体里面,给每个线程分配一个cpu_workqueue_struct,因而也就是给每个处理器分配一个,因为每个处理器都有一个该类型的工作进程。

  所有的工作者线程都是用普通的内核线程实现的,它们都要执行worker_thread()函数。在它初始化完以后,这个函数执行一个死循环并开始休眠,当有新的工作加入队列的时候,它被唤醒并开始工作,其中工作的结构体如下:

  

  这些结构体被链接成链表,在每个处理器上的每种类型的队列都对应这样一个链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。每当一个工作完成,就将相应的work_struct对象从链表上移走。当链表上没有对象的时候,它就进入休眠。

  例如,在系统默认的通用的events工作者类型之外,有自己加入了一种falcon工作者类型。并且使用一个拥有四个处理器的计算机。那么,系统现在有四个events类型的线程(也就有四个cpu_workqueue_strcut结构体)和四个falcon类型的线程(也会有四个cpu_workqueue_strcut结构体)。同时,有一个对应events类型的_workqueue_struct和一个对应falcon类型的workqueue_struct。

  使用工作队列

  1.创建需要推后的工作

  通过DECLARE_WORK在编译时静态创建该结构体

  

  这样会静态的创建一个名为name,处理函数为func,参数为data的work_struct结构体

  动态注册方式

  INIT_WORK(struct work_struct *work, void (*func) (void *), void *data);

  2.工作队列处理函数

  工作队列处理函数的原型是:

  void work_handler(vodi *data)

  该函数由一个工作者线程执行,因此,函数运行在进程上下文中,可失眠,可响应中断,并且不持有任何锁,但它不能访问用户空间。

  3.对工作进行调度

  通过 schedule_work(&work) work马上就会被调度,一旦其所在的处理器上的工作者函数被唤醒,它就会被执行。若是不希望被立即执行,可通过 schedule_delayed_work(&work,delay)来延时执行。

  4.刷新操作

  通过 void flush_scheduled_work(void) 来刷新工作队列中的工作

  5.创建新的工作队列

  如果默认的队列不能满足你的需要,你可以创建一个新的任务队列和与之相应的工作者线程。

  在下半部之间加锁

  使用taklet的一个好处在于它自己负责执行的序列化保障:两个相同类型tasklet不允许同时执行,即使在不同的处理器上也不行。但是软中断则不保障执行序列化(即使相同类型的软中断也有可能有两个实例在同时执行)所以所有的共享数据都需要合适的锁。

  如果进程上下文和一个下半部共享数据,在访问这些数据之前,需要禁止下半部的处理并得到锁的使用权。

  如果中断上下文和一个下半部共享数据,在访问数据之前,需要禁止中断并得到锁的使用权。

 

  参考自:《Linux kernel Development》.

posted on 2016-03-21 13:40  画家丶  阅读(247)  评论(0编辑  收藏  举报