现在的位置: 首页 > 综合 > 正文

workqueue

2013年09月05日 ⁄ 综合 ⁄ 共 6674字 ⁄ 字号 评论关闭

7.6. Workqueues

转自

Workqueues are, superficially, similar totasklets; they allow kernel code to request that a function becalledat
some future time. There are, however, some significant differencesbetween the two, including:

  • Tasklets run in software interrupt context with the result that alltasklet code must be atomic. Instead, workqueue functions run in thecontext of a special kernel process; as a result, they have moreflexibility. In particular, workqueue functions
    can sleep.

  • Tasklets always run on the processor from which they were originallysubmitted. Workqueues work in the same way, by default.

  • Kernel code can request that the execution of workqueue functions bedelayed for an explicit interval.

The key difference between the two is that tasklets execute quickly,for a short period of time, and in atomic mode, while workqueuefunctions may have higher latency but need not be atomic. Eachmechanism has situations where it is appropriate.

Workqueues have a type of structworkqueue_struct, which is defined in<linux/workqueue.h>. A workqueue must beexplicitly created before use, using one of the following twofunctions:

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

Each workqueue has one or more dedicated processes("kernel threads"), which runfunctions submitted to the queue. If you usecreate_workqueue, you get a workqueue that has adedicated thread for each processor
on the system. In many cases, allthose threads are simply overkill; if a single worker thread willsuffice, create the workqueue withcreate_singlethread_workqueue instead.

To submit a task to a workqueue, you need to fill in awork_struct structure. This can be done at compiletime as follows:

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

Where name is the name of the structure to bedeclared,function is the function that is to becalled from the workqueue, anddata is a value topass to that function. If you need to set up thework_struct structure
at runtime, use thefollowing two macros:

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

INIT_WORK does a more thorough job ofinitializing the structure; you should use it the first time thatstructure is set up.PREPARE_WORK does almostthe same job, but it does
not initialize the pointers used to linkthework_struct structure into the workqueue. Ifthere is any possibility that the structure may currently besubmitted to a workqueue, and you need to change that structure, usePREPARE_WORK
rather thanINIT_WORK.

There are two functionsforsubmitting work to a workqueue:

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

Either one adds work to the givenqueue. If
queue_delayed_work
is used, however, the actual work is not performed until at leastdelay jiffies have passed. The return value fromthese functions is0 if the work was successfullyadded to the queue; a nonzero result means that thiswork_struct
structure was already waiting in thequeue, and was not added a second time.

At some time in the future, the work function will be called with thegivendata value. The function will be running inthe context of the worker thread, so it can sleep if needbe—although you should be aware of how that sleep might
affectany other tasks submitted to the same workqueue. What the functioncannot do, however, is access user space. Since it is running insidea kernel thread, there simply is no user space to access.

Should you need to cancel a pending workqueue entry, you may call:

int cancel_delayed_work(struct work_struct *work);

The return value is nonzero if the entry was canceled before it beganexecution. The kernel guarantees that execution of the given entrywill not be initiated after a call tocancel_delayed_work. Ifcancel_delayed_work
returns0, however, the entry may have already beenrunning on a different processor, and might still be running after acall tocancel_delayed_work. To be absolutelysure that the work function is not running anywhere
in the systemaftercancel_delayed_work returns0, you must follow that call with a call to:

void flush_workqueue(struct workqueue_struct *queue);

After flush_workqueue returns, no work functionsubmitted prior to the call is running anywhere in the system.

When you are done with a workqueue, you can get rid of it with:

void destroy_workqueue(struct workqueue_struct *queue);

7.6.1. The Shared Queue

A device driver, in many cases, does not need its own workqueue. Ifyou only submit tasks to the queue occasionally, it may be moreefficient to simply use the shared, default workqueue that isprovided by the kernel. If you use this queue,
however, you must beaware that you will be sharing it with others. Among other things,that means that you should not monopolize the queue for long periodsof time (no long sleeps), and it may take longer for your tasks toget their turn in the processor.

The jiq ("just inqueue") module exports two files that demonstratethe use of the shared workqueue. They use a singlework_struct structure, which is set up this way:

static struct work_struct jiq_work;

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

When aprocessreads /proc/jiqwq, the module initiates a seriesof trips through the shared workqueue with no delay. The function ituses is:

int schedule_work(struct work_struct *work);

Note that a different function is used when working with the sharedqueue; it requires only thework_struct structurefor an argument. The actual code injiq lookslike this:

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

The actual work function prints out a line just like thejit module does, then, if need be, resubmits thework_struct structure into the workqueue. Here isjiq_print_wq
in its entirety:

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

If the user is reading the delayed device(/proc/jiqwqdelay), the work function resubmitsitself in the delayed mode withschedule_delayed_work:

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

If you look at the output from these two devices, it looks somethinglike:

% 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

When /proc/jiqwq is read, there is no obviousdelay between the printing of each line. When, instead,/proc/jiqwqdelay is read, there is a delay ofexactly one jiffy between each line. In either case, we see the sameprocess
name printed; it is the name of the kernel thread thatimplements the shared workqueue. The CPU number is printed after theslash; we never know which CPU will be running when the/proc file is read, but the work function willalways run on the same processor
thereafter.

If you need to cancel a work entry submitted to the shared queue, youmay usecancel_delayed_work, as described above.Flushing the shared workqueue requires a separate function, however:

void flush_scheduled_work(void);

Since you do not know who
else might be using this queue, younever really know how long it might take forflush_scheduled_work to return.

转自http://www.makelinux.net/ldd3/chp-7-sect-6

抱歉!评论已关闭.