Work Executed in the lower half and after the push
4. tasklet
Tasklet is implemented through Soft Interrupt, which is represented by two types of Soft Interrupt: hi_softirq and tasklet_softirq. The only difference between the two is that the former takes precedence over the latter.
Tasklet is represented by the tasklet_struct struct. Each struct represents a tasklet, which is defined in <Linux/interrupt. h>:
Struct tasklet_struct {struct tasklet_struct * Next;/* The next tasklet */unsigned long state in the linked list;/* tasklet status */atomic_t count; /* Reference Counter */void (* func) (unsigned long);/* tasklet processing function */unsigned long data;/* parameters for tasklet processing functions */}
The func member in the struct is tasklet processing.Program(Like the action in Soft Interrupt), data is its unique parameter. The State member can only be set between 0, tasklet_state_sched, and tasklet_state_run. Tasklet_state_sched indicates that the tasklet has been scheduled and is ready for running. Tasklet_state_run indicates that the tasklet is running. The Count member is the reference counter of tasklet. If it is not 0, tasklet is forbidden and cannot be executed. tasklet is activated only when it is 0, this tasklet can be executed only when it is set to the suspended state.
The scheduled tasklet is stored in two single-processor data structures: tasklet_vec (Common tasklet) and tasklet_hi_vec (high-priority tasklet. Scheduling is performed by tasklet_schedule () and tasklet_hi_schedule () respectively. Details of tasklet_schedule:
1) check whether the tasklet status is tasklet_state_sched. If yes, it indicates that the tasklet has been scheduled (it may be that a tasklet has been scheduled but has not been executed yet, and the tasklet has been invoked again). The function returns immediately.
2) Save the interruption status and disable local interruption. Execute taskletCodeThis ensures that the data on the processor will not be messed up when tasklet_schedule () processes these tasklets.
3) Add the tasklet to the header of the tasklet_vec or tasklet_hi_vec linked list of each processor.
4) Call the tasklet_softirq or hi_softirq Soft Interrupt so that the tasklet is executed the next time do_softirq () is called.
5) resume the interruption to the original state and return.
Tasklet_action () and tasklet_hi_action () are the core of tasklet processing. Their details:
1) interruption is prohibited. (There is no need to save its status first, because the code is always called as a soft interrupt and the interrupt is always activated) and retrieves the tasklet_vec or tasklet_hi_vec linked list for the current processor.
2) set the linked list on the current processor to null to clear the list.
3) allow response interruption. There is no need to restore them to the original state, because the program itself is called as a soft interrupt handler, so the interrupt should be allowed.
4) cyclically traverse each tasklet to be processed on the linked list.
5) for a multi-processor system, check the tasklet_state_run status flag to determine whether the tasklet is running on another processor. If it is running, do not execute it now, jump to the next tasklet to be processed (only one worker can be executed for the same type of tasklet at the same time ).
6) if the current tasklet is not executed, set its status flag to tasklet_state_run so that other processors will not execute it again.
7) check whether the Count value is 0 to ensure that the tasklet is not disabled. If tasklet is disabled, it is skipped to the next suspended tasklet.
8) This tasklet is not executed elsewhere and is set to the execution status so that it will not be executed in other parts and the reference count is 0, now you can execute the tasklet processing program.
9) after tasklet is run, clear the tasklet_state_run status flag of the tasklet state field.
10) Repeat the next tasklet until there is no remaining tasklet waiting for processing.
5. Use tasklet
Tasklet can be created statically or dynamically. If you want to create a tasklet statically, use one of the two macros defined in <Linux/interrupt. h> below.
Declare_tasklet (name, func, data );
Declare_tasklet_disabled (name, func, data );
Both macros can create a tasklet_struct structure statically Based on the given name. After the tasklet is scheduled, the specified function func is executed and its parameters are provided by data. The difference between these two macros is that the initial value setting of the reference counter is different. The previous macro sets the reference counter for tasklet creation to 0, and the tasklet processing is activated. The other sets the reference counter to 1, so the tasklet is forbidden.
Call the tasklet_init function for dynamic creation:
Tasklet_init (T, tasklet_handler, Dev );
The tasklet handler must comply with the specified function type.
Void tasklet_handler (unsigned long data)
Tasklet cannot sleep because it is implemented by soft interruptions. The tasklet operation allows the response to be interrupted, but the two identical tasklets will never be executed at the same time.
By calling the tasklet_schedule () function and passing it to its corresponding tasklet_struct pointer, The tasklet will be scheduled for execution.
Tasklet_schedule (& my_tasklet );
After a tasklet is scheduled, it runs as early as possible if it has the opportunity. If a same tasklet is scheduled again, it will only run once. If it is already running, for example, on another processor, the new tasklet will be rescheduled and run again. As an optimization measure, A tasklet is always executed on the processor that schedules it-this is intended to make better use of the processor's high-speed cache.
You can call the tasklet_disable () function to disable a specified tasklet. If the tasklet is currently being executed, this function will wait until it is executed and then return. You can also call the tasklet_disable_nosync () function, which can also be used to disable the specified tasklet, but it does not need to wait until the tasklet is executed before the return. This is often not safe. Call the tasklet_enable () function to activate a tasklet. Call the tasklet_kill () function to remove a tasklet from the suspended queue.
6. ksoftirqd
Each processor has a set of kernel threads that assist in handling soft interruptions (and tasklet). When there is a large number of soft interruptions in the kernel, these kernel threads will assist them. These threads run at the lowest priority (Nice value is 19) to prevent resources from being snatched from other repetitive tasks. All threads are named ksoftirad/n. The difference is that N corresponds to the processor number.
7. Work queue
A work queue is another form of pushing and executing a job. It is executed by a kernel thread-the lower half is always executed in the process context. The most important thing is that the work queue allows rescheduling or even sleep.
If the task to be executed after the push needs to sleep, select the work queue. Otherwise, select Soft Interrupt or tasklet. In fact, the working queue can usually be replaced by the kernel thread, but because kernel developers are very opposed to creating new kernel threads, we recommend that you use the Working queue.
The working queue subsystem is an interface used to create a kernel thread. The process created through it is responsible for executing tasks in the queue from other parts of the kernel. The kernel threads it creates are called worker threads ). Working queue allows your driver to create a dedicated worker thread to handle jobs that need to be pushed back. However, the work queue subsystem provides a default worker thread to handle these jobs.
The default worker thread is events/n, where N is the processor number. Each processor corresponds to one thread.
To actually create some work that needs to be pushed back and completed, you may use declare_work to create it statically during compilation: declare_work (name, void (* func) (void *), void * data ), in this way, a workstruct struct named name, processing function func, and parameter data will be created statically. At the same time, you can also create a job through the pointer at runtime: init_work (struct work_struct * Work, void (* func) (void *), void * data );
The prototype of the work queue processing function is void work_handler (void * data), which is executed by a worker thread. Therefore, the function runs in the process context. By default, the response interruption is allowed and no lock is held. If needed, the function can sleep. It should be noted that although the operation handler function runs in the process context, it cannot access the user space because the kernel thread does not have relevant memory ing in the user space. Generally, when a system call occurs, the kernel indicates that the process in the user space runs. In this case, it can access the user space and map the user space memory.
Use schedule_work (& work) and schedule_delayed_work (& work, delay) to schedule a job. flush_scheduled_work (void) ensures that the operation has been completed. Int cancel_delayed_work) cancel the delayed execution. Struct workqueue_struct * create_workqueue (const char * Name) creates a worker thread on each processor and calls int queue_work (struct workqueue_struct * WQ, struct work_struct * work ); and int queue_delayed_work );