7.6工作队列

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shenwanjiang111/article/details/81909845

7.6 工作队列


从表面上看,工作队列类似于tasklet;它们允许内核代码请求在将来的某个时间调用一个函数。但是,两者还是存在着显著的差异,如下所示:

  • tasklet运行在软件中断上下文中,所以其代码必须是原子操作。相反,工作队列运行在内核进程上下文中;结果就是它们具有更大的灵活性。特别是,工作队列可以休眠。
  • tasklet始终运行在最初提交它们的处理器上。默认情况下,工作队列以相同方式工作。
  • 内核代码可以请求,将工作队列中的函数延迟一段时间后再执行

所以,工作队列和tasklet最大的不同就是tasklet执行的更快,因为其是原子的;但是,因为不必是原子的,所以工作队列具有更高的延迟。

工作队列使用的一种数据结构是struct workqueue_struct,定义在<linux/workqueue.h>中。工作队列在使用之前,必须使用下面的函数显式地创建:

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

每个工作队列都有一个或多个专用进程(“内核线程”),在其中运行提交给队列的函数。如果使用create_workqueue,则可以得到一个工作队列,该队列具有系统上每个处理器的专用线程。在许多情况下,这么多线程有点浪费;如果只要单个工作线程就够了,那么可以使用create_singlethread_workqueue

为了提交任务给工作队列,需要填充结构体work_struct。下面的宏就是在编译的时候完成数据结构的填充。

DECLARE_WORK(name, void (*function)(void *), void *data);

在这里,name是被声明的结构体的名称, function是要从工作队列中调用的函数,data是传递给函数的参数。如果需要在运行时建立结构体work_struct,则使用下面两个宏:

INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);

INIT_WORK在初始化结构体更彻底,应该结构体第一次声明时使用它。PREPARE_WORK做相同的工作,但是不会初始化链接到工作队列的work_struct结构的指针。它的用处就是,有时候可能需要改变这个结构体,那么就使用PREPARE_WORK,而不是INIT_WORK

将工作提交到工作队列的两个函数:

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 delay);

任何人都可以添加工作队列到给定的工作队列中。但是,使用queue_delayed_work函数的话,至少要延时delay个嘀嗒时间(jiffies)后才会执行真实的工作。返回0,则说明成功地添加工作到工作队列了;非0,说明该work_struct结构体已经在工作队列中,不需要第二次添加。

如果需要取消挂起的工作队列项可以调用下面的函数:

int cancel_delayed_work(struct work_struct *work);

如果某项工作在开始执行前被取消,返回值是非0。由内核保证在调用cancel_delayed_work之后不会启动给定work的执行。但是,即使返回0,该工作也有可能已经在不同的处理器上运行了。为了完全保证work函数没有在系统的任何地方运行,在调用cancel_delayed_work之后,紧接着调用下面这个函数。

void flush_workqueue(struct workqueue_struct *queue);

调用flush_workqueue函数之后,work已经不会在系统的任何地方运行了。接下来,可以调用下面的函数从工作队列中消除该work:

void destroy_workqueue(struct workqueue_struct *queue);

7.6.1 共享队列


在许多情况下,驱动程序不需要它自己的工作队列。如果只是偶尔提交任务到队列中,使用共享队列更有效率,内核默认使用共享队列。但是,使用共享队列,意味着你不能长时间占有工作队列。jiq模块(just in queue)生成了两个文件,阐释工作队列的使用。代码如下:

static struct work_struct jiq_work;
/* this line is in jiq_init( ) */
INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data);

当有进程读取/proc/jiqwq文件时,模块就会没有延时地启动共享队列的跳转。使用的函数是:

int schedule_work(struct work_struct *work);

注意:使用共享队列时,使用的函数不同。它只有work_struct类型的一个参数。jiq模块的代码如下:

prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE);
schedule_work(&jiq_work);
schedule( );
finish_wait(&jiq_wait, &wait);

jit模块一样,先打印一行;如果有需要,重新提交work_struct结构体到工作队列中。jiq_print_wq的完整代码如下:

static void jiq_print_wq(void *ptr)
{
    struct clientdata *data = (struct clientdata *) ptr;

    if (! jiq_print (ptr))
        return;
    if (data->delay)
        schedule_delayed_work(&jiq_work, data->delay);
    else
        schedule_work(&jiq_work);
}

如果用户正在读取有延时的设备(/proc/jiqwqdelay),工作函数使用schedule_delayed_work重新提交自己。

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

输出结果:

% cat /proc/jiqwq
 time delta preempt pid cpu command
 1113043 0 0 7 1 events/1
 1113043 0 0 7 1 events/1
 1113043 0 0 7 1 events/1
 1113043 0 0 7 1 events/1
 1113043 0 0 7 1 events/1
% cat /proc/jiqwqdelay
 time delta preempt pid cpu command
 1122066 1 0 6 0 events/0
 1122067 1 0 6 0 events/0
 1122068 1 0 6 0 events/0
 1122069 1 0 6 0 events/0
 1122070 1 0 6 0 events/0

从上面的结果来看,读取/proc/jiqwq文件时没有明显的延时。但是读取/proc/jiqwqdelay时,每行数据会有一个嘀嗒(jiffy)的延时。但是打印的进程名称是相同的;它就是内核实施共享队列的线程。斜杠后面打印的是CPU号;刚开始读取时,不知道在哪个CPU上运行,但是之后,就会一直在一个CPU上运行。

取消提交到共享队列中的工作,可以使用cancel_delayed_work函数。但是,刷新共享工作队列需要一个不同的函数,如下:

void flush_scheduled_work(void);

因为不知道是否还有其它地方使用,所以不会知道flush_scheduled_work函数多长时间返回。

猜你喜欢

转载自blog.csdn.net/shenwanjiang111/article/details/81909845