And then, in the last section, we talked about soft interrupts and tasklet, so this is the work queue.
The work queue is not the same as the other forms discussed earlier, it can push the work back to a kernel thread to execute----The work is always executed in the process context. Thus, executing code through a work queue can take all the benefits of the process context, and most importantly, the work queue allows for rescheduling or even sleep. Compared to the front two, this choice is very easy. I said, the front two is not allowed to sleep, this is allowed to sleep, it is clear that is not? This means that when you need to get a lot of memory, when you need to get a semaphore, it will be very useful when you need to perform blocking I/O operations (speak first, this is not what I said, it's what the book says).
The Work queue subsystem is an interface for creating kernel threads, and processes created by it are responsible for performing tasks that are queued to other parts of the kernel. The kernel threads it creates are called worker threads (worker threads). The work queue allows your driver to create a dedicated worker thread to handle work that needs to be pushed back. 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.
1. Work This thread structure is represented by the following structure:
| 123 |
structworkqueue_struct{ structcpu_workqueue_struct cpu_wq[NR_CPUS];} |
Each of the arrays in the structure corresponds to a CPU of the system. Next, look at the core data structure in KERNEL/WORKQUEUE.C cpu_workqueue_struct:
| 12345678910 |
structcpu_workqueue_struct{ spinlock_t lock; atomic_t nr_queued; structlist_head worklist; wait_queue_head_t more_work; wait_queue_head_t work_done; structworkqueue_struct *wq; task_t *thread; structcompletion exti;} |
2. Represents the work data structure: All worker threads are implemented with 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. The work has a work_struct (LINUX/WORKQUEUE) structure that says:
| 12345678 |
structwork_struct{ unsigned longpending; structlist_head entry; //连接所有工作的链表 void(*func)(void*); //处理函数 void*data; //传递给处理函数的参数 void*wq_data; structtimer_list timer; //y延迟工作队列所用到的定时器} |
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 core flow of the Woker_thread function is as follows:
| 1234567891011 |
for(;;){ set_task_state(current,TASK_INTERRUPTIBLE); add_wait_queue(&cwq->more_work,&wait); if(list_empty(&cwq->worklist)) schedule(); else set_task_state(current,TASK_RUNNING); remove_wait_queue(&cwq->more_work,&wait); if(!list_empty(&cwq->worklist)) run_workqueue(cwq);} |
analyze the above code. The thread first sets itself to hibernate and joins itself in the wait queue. If the work on the column is empty, the thread calls the schedule () function into sleep state. If the linked list has an object, the thread sets itself as the running state, leaving the waiting queue. Then, call Run_workqueue () again to perform the deferred work. Well, then, the problem is tangled up in Run_workqueue (), which completes the work actually pushed backwards:
| 12345678 |
while(!list_empty(&cwq->worklist)){ struct work_struct *work = list_entry(cwq->worklist.next,structwork_struct,entry); void(*f)(void*) = work->func; void*data = work->data; list_del_init(cwq->worklist.next); clear_bit(0,&work->pending); f(data);} |
This function loops through each pending work on the linked list, executing the Func member function of work_struct on each node on the list:
1. When the linked list is not empty, select the next node object. 2. Get the function func we want to execute and its parameter data. 3. Put the node from the list next, the pending flag is pending clear 0. 4. Call the function. 5. Repeated execution. |
The teacher said good: Light said no practice, not a hero. Now let's continue to see how to use it:
1. First, actually create some work that needs to be pushed backwards, and you can create the data structure statically at compile time:
| 1 |
DECLARE_WORK(name,void(*func)(void *),void*data); |
Of course, if you want, we can certainly create a job dynamically at run time with pointers:
| 1 |
INIT_WORK(structwork_struct *work, void (*func)(void *),void*data); |
2. The Work queue handler function is executed by a worker thread, so the function runs in the process context, and by default, the corresponding interrupt is allowed and no lock is held. 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:
| 1 |
voidwork_hander(void*data); |
3. Dispatch the work. After the preparation is done, the following can start dispatching, just call Schedule_work (&work). This will be dispatched immediately, and once the worker thread on the processor on which it resides is awakened, it will be executed. Of course if you do not want to execute quickly, but want to delay the execution of a period of time, press on the Schedule_delay_work (&work,delay);d Elay is to delay the time beat, said later.
4. Refresh the operation. The work that is inserted into the queue executes 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.
5. Create a new Task Force column. It was best to use the default thread, but what if you insisted on using the thread you created? At this point you should create a new work queue and a corresponding worker thread, using the following function: struct workqueue_struct *create_workqueue (const char *name); name is the name of the new kernel thread. This creates all of the worker threads (one for each processor in the system) and prepares all the work before starting the process. Once created, call the following function:
| 12 |
int queue_work ( struct workqueue_struct *wq, struct work_struct *work); int queue_delayed_work ( struct workqueue_struct *wq, struct work_struct *work,unsigned Code class= "CPP Color1 bold" >long delay); |
These two functions are similar to Schedule_work () and Schedule_delayed_work (), except that they can operate on a specific work queue rather than the default event queue.
OK, the work queue is finished, I still combine the front one, the three floor of the strategy of the implementation of a comparison, convenient choice later.
First, the Tasklet is based on a soft interrupt implementation, the two are similar, the work queue mechanism is completely different from them, by the kernel thread to achieve. A soft interrupt provides the least guaranteed serialization, which requires the interrupt handler to take extra care to ensure that the shared data is secure, and that two or more soft interrupts of the same class may be executed concurrently on different processors. A soft interrupt is a good choice if the code you are looking at is doing a very good job of working with multiple threads, and it uses a single processor variable entirely. It performs the fastest for demanding time-critical and highly efficient applications. Otherwise, the choice of tasklets is more significant. The Tasklet interface is simple, and the two same types of tasklet cannot be executed at the same time, so it is easier to implement. If you need to postpone the task to the context of the process, you can only select the work queue. If hibernation is not required, then soft interrupts and tasklet may be more appropriate. In addition, the work queue is the most expensive, of course, this is relative, for most cases, the work queue can provide sufficient support. From the convenience of consideration is: Work queue, tasklets, and finally the soft interrupt. When we are driving, we need to consider two points about these three lower halves: first, whether you need a scheduler to perform the work that needs to be pushed back (that is, the need for hibernation), and if so, the work queue is the only option, otherwise it is best to use Tasklet. If the performance is the most important, then the soft interrupt it.
Finally, there are some sections that prohibit the lower half, giving a table:
Function |
Describe |
| void Local_bh_disable () |
Disables the processing of soft interrupts and tasklet on local processors |
| void Local_bh_enable () |
Activating soft interrupts and tasklet processing on the local processor |
These functions may be nested using----the last called local_bh_enable () to eventually activate the lower half. The function maintains a counter for each process through Preempt_count. When the counter becomes 0 o'clock, the lower half is able to be processed. Because the processing of the lower part has been banned, local_bh_enable () also needs to check all existing pending lower halves and execute them.
Well, this time finished, painted two times, we mentioned in these two times some of the simultaneous problems, there may be data sharing mutex access problem, this is the kernel synchronization aspects of the matter, we will slowly say this thing later.
Linux kernel analysis notes----top and bottom half (bottom)