Mastering the Linux kernel Design (v): The work queue of the lower half mechanism and the choice of several mechanisms

Source: Internet
Author: User

"copyright notice: respect for the original, reproduced please retain the source: Blog.csdn.net/shallnet, the article is only for learning exchange, do not use for commercial use "
another of the lower half of the task force is to postpone the work in the form of execution. Unlike soft interrupts and tasklet, the work queue is pushed back to a kernel thread for execution, and the nextThe half part is always executed in the context of the process. This allows the work queue to be re-dispatched or even asleep. So, if the deferred task requires sleep, select the work queue. If you don't need sleep, choose soft interrupts or Tasklet. A work queue is the only implementation mechanism that can run in the context of a process, and only it can sleep. The Work queue subsystem is an interface for creating kernel threads, and the processes created by it are responsible for performing tasks that are queued to other parts of the kernel. These kernel threads that it creates are called worker threads. The work queue allows your driver to create a dedicated worker thread to handle the work that needs to be postponed. However, the work queue subsystem provides a default worker thread to handle these tasks. As a result, the most basic representation of a work queue turns into an interface that takes tasks that need to be deferred to a particular generic thread. The default worker thread is called event/n. Each processor corresponds to a thread, where n represents the processor number. Unless a driver or subsystem has to build a kernel thread of its own, it is best to use the default thread. use the following command to see the default event worker thread, one thread per Processor:
# PS X | grep Event | Grep-v grep    9?        S      0:00 [events/0]   ten?        S      0:00 [EVENTS/1]
worker threads Use the WORKQUEUE_STRUCT structure representation (located in <kernel/workqueue.c>):
struct Workqueue_struct {    struct cpu_workqueue_struct *cpu_wq;    The array is a processor struct List_head list in each of the corresponding systems    ;    const char *name;    int singlethread;    int freezeable;     /* Freeze Threads during suspend */    int rt; #ifdef CONFIG_LOCKDEP    struct lockdep_map lockdep_map; #endif}
Each processor, each worker thread corresponds to a cpu_workqueue_struct structure (in <kernel/workqueue.c>):
struct Cpu_workqueue_struct {    spinlock_t lock;    Protecting the structure of    struct list_head worklist;    Work list    wait_queue_head_t more_work;    Wait queue, where the worker thread is sleeping due to wait while the    struct work_struct *current_work;    struct workqueue_struct *wq;    Association work queue Structure    struct task_struct *thread;    The association thread, which points to the worker thread's process descriptor pointer in the struct} ____cacheline_aligned;
each worker thread type Associates one of its own workqueue_struct, in the struct, assigns a cpu_workqueue_struct to each thread , and therefore assigns one to each processor, Because each processor has a worker thread of that type.         All worker threads are implemented using normal kernel threads, all of which execute the Worker_thread () function. After it is initialized, the function executes a loop and begins to hibernate, and the thread is awakened when an operation is inserted into the queue in order to perform these operations. When there is no remaining time, it will continue to hibernate. Work by work_struct (located in <kernel/workqueue.c>) structure:
struct Work_struct {                                                                                                                                        atomic_long_t data;    struct List_head entry;//connect all linked lists    work_func_t func; ...};
when a worker thread is awakened, it performs all the work on its linked list. Once the work is done, it removes the corresponding Work_struct object from the list and continues to hibernate when the list no longer has an object. The Woker_thread () function is as follows:
static int worker_thread (void *__cwq) {    struct cpu_workqueue_struct *cwq = __cwq;    Define_wait (WAIT);    if (cwq->wq->freezeable)        set_freezable ();    for (;;) {    //the thread sets itself to hibernate and adds itself to the wait queue        prepare_to_wait (&cwq->more_work, &wait, task_interruptible);        if (!freezing (current) &&            !kthread_should_stop () &&            list_empty (&cwq->worklist))            schedule ();//If the work on the column is empty, the thread calls the schedule () function into the sleep state        finish_wait (&cwq->more_work, &wait);        Try_to_freeze ();//If the list has an object, the thread will set itself as the run state, out of the waiting queue        if (Kthread_should_stop ())            break;//call Run_workqueue again () Performing a deferred work        run_workqueue (CWQ);    }    return 0;}
This is followed by the Run_workqueue () function to do the actual work of postponing it:
static void Run_workqueue (struct cpu_workqueue_struct *cwq) {SPIN_LOCK_IRQ (&cwq->lock); while (!list_empty (&cwq->worklist)) {//list is not empty, select the next node object struct work_struct *work = list_entry (CWQ-&G        T;worklist.next, struct work_struct, entry); Gets the function func and its arguments that you want to execute data work_func_t f = work->func; Trace_workqueue_execution (Cwq->thread, work)        ;        Cwq->current_work = Work;        List_del_init the node from the linked list (cwq->worklist.next);        SPIN_UNLOCK_IRQ (&cwq->lock);        BUG_ON (Get_wq_data (work)! = CWQ);        Pending the pending flag to 0 work_clear_pending (work);        Lock_map_acquire (&AMP;CWQ-&GT;WQ-&GT;LOCKDEP_MAP);        Lock_map_acquire (&AMP;LOCKDEP_MAP);        Execute function f (work);        Lock_map_release (&AMP;LOCKDEP_MAP);        Lock_map_release (&cwq->wq->lockdep_map); Spin_lock_irq (&cwq->lock);    Cwq->current_work = NULL; } Spin_unloCK_IRQ (&cwq->lock);} 
The system allows multiple types of worker threads to exist, by default the kernel only has an event type of worker thread, and each worker thread is represented by aThe cpu_workqueue_struct struct indicates that in most cases, the driver uses the existing default worker thread. the use of the Task Force column is simple. You can use the default events queue, or you can create a new worker thread. The first step is to create the work that needs to be postponed. declare_work (Name,void (*func) (void *), void *data); Compile-time static creationinit_work (struct work_struct *work, void (*func) (void *)); Run-time dynamic creationThe second step, write the queue handler function, the processing function is executed by the worker thread, therefore, the function runs in the process context, by default, allows the corresponding interruption, and does not hold the lock. If necessary, the function can sleep. It is important to note that although the handler function runs in the context of the process, it cannot access the user space because the kernel thread does not have a corresponding memory map in the user space. The function prototypes are as follows:void Work_hander (void *data);The third step, scheduling work queue. calledschedule_work (&work);Work is dispatched immediately, and once the worker thread on the processor on which it is located is awakened, it is executed. Of course if you do not want to execute quickly, but want to delay execution for a while, callschedule_delay_work (&work,delay);Delay is the time beat to delay. The default worker thread scheduling function is actually a layer of encapsulation, reducing the default worker thread parameter input, as follows:
int schedule_work (struct work_struct *work) {    return queue_work (KEVENTD_WQ, work);} int schedule_delayed_work (struct delayed_work *dwork, unsigned long delay)                                             {    return queue_delayed_work ( KEVENTD_WQ, dwork, delay);}
The fourth step, the refresh operation, the work of inserting the queue will be executed when the worker thread is awakened the next time. Sometimes, you must ensure that some operations have been performed and so on before proceeding to the next step. For these reasons, the kernel provides a function to refresh the specified work queue:void flush_scheduled_work (void);This function waits until all the objects in the queue have been executed before returning. The function goes into hibernation while waiting for all pending work to be performed, so it can only be used in the context of the process. It is necessary to note that the function does not cancel any deferred work. The work to cancel deferred execution should be called: int cancel_delayed_work (struct work_struct *work); This function cancels any pending work related to Work_struct. Here is an example:
#include <linux/init.h> #include <linux/module.h> #include <linux/workqueue.h>//work_strcut// struct work_struct      ws;struct delayed_work     dw;void workqueue_func (struct work_struct *ws)    //processing function {    PRINTK (Kern_alert "Hello, this is shallnet!\n");} static int __init kwq_init (void) {    printk (kern_alert "===%s===\n", __func__);    Init_work (&ws, workqueue_func);    Work    //schedule_work (&WS) that needs to be postponed;    Dispatch work    init_delayed_work (&DW, workqueue_func);    Schedule_delayed_work (&DW, 10000);    return 0;} static void __exit kwq_exit (void) {    printk (kern_alert "===%s===\n", __func__);    Flush_scheduled_work ();} Module_init (Kwq_init); Module_exit (Kwq_exit); Module_license ("GPL"); Module_author ("Shallnet"); Module_description ("Blog.csdn.net/shallnet");
the above is done using the default work queue, and here's how to create a new work queue? creating a new work queue and corresponding worker threads is simple, using the following functions:
struct workqueue_struct *create_workqueue (const char *name);
Name is the name of the new kernel thread. For example, the creation of the default events queue is used in this way:
struct workqueue_struct    *keventd_wq;kevent_wq = Create_workqueue ("event");
This creates all worker threads, one for each processor. Then call the following function to dispatch:
int queue_work (struct workqueue_struct *wq, struct work_struct *work); int queue_delayed_work (struct workqueue_struct * Wq,struct delayed_work *work,unsigned long delay);
Finally, you can call Flush_workqueue (struct workqueue_struct *wq) and refresh the specified work queue. The following is an example of customizing a new work queue:
 #include <linux/init.h> #include <linux/module.h> #include <linux/ workqueue.h>//work_strcutstruct workqueue_struct *sln_wq = null;//struct work_struct ws;struct delayed_work D W;void workqueue_func (struct work_struct *ws) {PRINTK (Kern_alert "Hello, this is shallnet!\n");}    static int __init kwq_init (void) {PRINTK (Kern_alert "===%s===\n", __func__);    Sln_wq = Create_workqueue ("Sln_wq");    Create a work queue named Sln_wq//init_work (&ws, Workqueue_func);    Queue_work (Sln_wq, &WS);    Init_delayed_work (&DW, Workqueue_func);    Queue_delayed_work (SLN_WQ, &DW, 10000); return 0;}    static void __exit kwq_exit (void) {PRINTK (Kern_alert "===%s===\n", __func__); Flush_workqueue (SLN_WQ);} Module_init (Kwq_init); Module_exit (Kwq_exit); Module_license ("GPL"); Module_author ("Shallnet"); Module_description ("Blog.csdn.net/shallnet"); 
use PS to view worker threads named Sln_wq. Download the source code in this section:http://download.csdn.net/detail/gentleliu/8941433

in the current 2.6.32 release, we talked about three lower-half mechanisms: soft interrupts, Tasklet, and task queues. Where Tasklet is based on soft interrupts, and the work queue is implemented by kernel threads. The use of soft interrupts must ensure the security of shared data, as soft interrupts of the same class may be performed concurrently on different processors. Consider using soft interrupts, such as network subsystems, in applications where time requirements are stringent and high frequency of execution, or if you are ready to take advantage of variable or type scenarios on each processor. Tasklet interface is simple, can be dynamically created, and the two notification type of tasklet can not be executed at the same time, so the implementation is relatively simple. The driver should try to choose Tasklet instead of soft interrupts. Task Force lists work in the process context and are easy to use. It can be expensive to switch between kernel threads or contexts. If you need to postpone the task to the context of the process, or if you need to hibernate, then only use the work queue.

Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Mastering the Linux kernel Design (v): The work queue of the lower half mechanism and the choice of several mechanisms

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.