[Note] Some changes have taken place in the work queue since Linux 2.6.20. Currently, the materials searched from the network are generally about the work queue of the old version, and the introduction of the new version is rarely seen. This article gives a brief overview of the new and old versions, and provides simple implementation cases.
Work queue is a mechanism in Linux kernel to push and execute jobs. This mechanism is different from BH or tasklets in that the working queue transfers the pushed work to a kernel thread for execution. Therefore, the advantage of working queue is that it allows rescheduling or even sleep.
The working queue is a mechanism introduced by the 2.6 kernel. After 2.6.20, the data structure of the working queue has changed. Therefore, this article introduces the versions before and after 2.6.20 in two parts.
1. 2.6.0 ~ 2.6.19
Data structure:
Struct work_struct {
Unsigned long pending;
Struct list_head entry;
Void (* func) (void *);
Void * data;
Void * wq_data;
Struct timer_list timer;
};
Pending is used to record whether the work has been attached to the queue;
The entry is a circular linked list structure;
As a function pointer, func is implemented by users;
Data is used to store users' private data. This data is the func parameter;
Wq_data is generally used to point to the worker thread (the worker thread is referred to below );
Timer is the timer executed after pushing.
In these variables of work_struct, func and data are used by users, while others are internal variables, so we don't have to worry too much about them.
API:
1) init_work (_ work, _ FUNC, _ data)
Initialize the specified job to assign the parameter _ data required by the user-specified function _ func and _ FUNC to the func and data variables of work_struct.
2) int schedule_work (struct work_struct * Work)
Schedule the work, that is, submit the processing function of the given work to the default work queue and worker thread. A worker thread is essentially a common kernel thread. By default, each CPU has a worker thread of the "events" type. When schedule_work is called, this worker thread will be awakened to execute all the work on the Work linked list.
3) int schedule_delayed_work (struct work_struct * Work, unsigned long delay)
Delayed execution, similar to schedule_work.
4) void flush_scheduled_work (void)
Refresh the default work queue. This function waits until all the work in the queue is executed.
5) int cancel_delayed_work (struct work_struct * Work)
Flush_scheduled_work does not cancel any delayed execution. Therefore, if you want to cancel the delayed operation, you should call cancel_delayed_work.
The preceding steps use the default worker thread to Implement the work queue. The advantage is that it is easy to use. The disadvantage is that if the default work queue has heavy load, the execution efficiency will be very low, this requires us to create our own worker threads and work queues.
API:
1) struct workqueue_struct * create_workqueue (const char * name)
Create a new working queue and corresponding worker thread. The name is used to name the kernel thread.
2) int queue_work (struct workqueue_struct * WQ, struct work_struct * Work)
Similar to schedule_work, queue_work submits a given job to the created work queue WQ instead of the default queue.
3) int queue_delayed_work (struct workqueue_struct * WQ, struct work_struct * Work, unsigned long delay)
Delayed execution.
4) void flush_workqueue (struct workqueue_struct * WQ)
Refresh the specified work queue.
5) void destroy_workqueue (struct workqueue_struct * WQ)
Release the created work queue.
The following code is a simple example:
Void my_func (void * Data)
{
Char * name = (char *) data;
Printk (kern_info "Hello world, my name is % s! /N ", name );
}
Struct workqueue_struct * my_wq = create_workqueue ("My WQ ");
Struct work_struct my_work;
Init_work (& my_work, my_func, "Jack ");
Queue_work (my_wq, & my_work );
Destroy_workqueue (my_wq );
2. 2.6.20 ~ 2.6 .??
Since 2.6.20, the data structure of the work queue has changed, and the old method cannot be used.
Data structure:
Typedef void (* work_func_t) (struct work_struct * work );
Struct work_struct {
Atomic_long_t data;
Struct list_head entry;
Work_func_t func;
};
Compared with versions earlier than 2.6.19, work_struct is much reduced. From a rough look, the entry is the same as the previous version, func and data have changed, and there are no other variables.
The entry is the same as the previous version. The data type is atomic_long_t, which is literally an atomic type. When I first saw this variable, it was easy to mistakenly think that it is the same usage as the previous data, but the type has changed. In fact, the data here is the combination of pending and wq_data of previous versions, played the role of pending and wq_data.
The func parameter is a work_struct pointer, pointing to the data that defines the work_struct of func.
There are two questions: first, how can we pass user data to func as a parameter? In the past, void * data was used as a parameter, and it seems that there is no way to do it. Second, how to implement latency? Currently, work_struct does not define timer.
To solve the first problem, you need to change your mind. In versions 2.6.20 and later, work_struct needs to be defined in the user's data structure, and then the user data is obtained through container_of. For specific usage, refer to the implementation later.
For the second problem, the intention of the new work queue to remove timer is to make work_struct simpler. First, let's recall that timer is used only when execution is delayed. In general, timer is meaningless. Therefore, the previous practice is a waste of resources to some extent. In the new version, timer is removed from work_struct, and a new structure delayed_work is defined for processing delayed execution:
Struct delayed_work {
Struct work_struct work;
Struct timer_list timer;
};
The following lists the APIs. For the explanation of each function, refer to the introduction of previous versions or subsequent implementations:
1) init_work (struct work_struct * Work, work_func_t func)
2) init_delayed_work (struct delayed_work * Work, work_func_t func)
3) int schedule_work (struct work_struct * Work)
4) int schedule_delayed_work (struct delayed_work * Work, unsigned long delay)
5) struct workqueue_struct * create_workqueue (const char * name)
6) int queue_work (struct workqueue_struct * WQ, struct work_struct * Work)
7) int queue_delayed_work (struct workqueue_struct * WQ, struct delayed_work * Work, unsigned long delay)
8) void flush_scheduled_work (void)
9) void flush_workqueue (struct workqueue_struct * WQ)
10) int cancel_delayed_work (struct delayed_work * Work)
11) void destroy_workqueue (struct workqueue_struct * WQ)
Among them, 1), 2), 4), 7) is slightly different from the previous, and other usage is exactly the same.
Implementation:
Struct my_struct_t {
Char * Name;
Struct work_struct my_work;
};
Void my_func (struct work_struct * Work)
{
Struct my_struct_t * my_name = container_of (work, struct my_struct_t, my_work );
Printk (kern_info "Hello world, my name is % s! /N ", my_name-> name );
}
Struct workqueue_struct * my_wq = create_workqueue ("My WQ ");
Struct my_struct_t my_name;
My_name.name = "Jack ";
Init_work (& (my_name.my_work), my_func );
Queue_work (my_wq, & my_work );
Destroy_workqueue (my_wq );