Softirq is a latency execution mechanism provided by the kernel. It is completely triggered by software. Although it is a latency mechanism, in fact, in most cases, it is compared with common processes, faster response time. Soft Interrupt is also the basis of other kernel mechanisms, such as tasklet and high-resolution timer.
/*************************************** **************************************** **********************/
Statement: the content of this blog is created at http://blog.csdn.net/droidphone. please refer to it for help. Thank you!
/*************************************** **************************************** **********************/
1. Data Structure of software interruption 1.1 struct softirq_action the kernel uses softirq_action structure to manage registration and activation of software interruption. Its definition is as follows:
struct softirq_action{void(*action)(struct softirq_action *);};
Very simple. There is only one function pointer for callback. The resources for software interruption are limited. At present, the kernel only implements 10 types of software interruption, which are:
enum{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */NR_SOFTIRQS};
Kernel developers do not recommend that we increase the number of software interruptions without authorization. If new software interruptions are required, implement them as tasklet Based on Software interruptions. In contrast to the enumerated values above, the kernel defines a structure array of softirq_action. Each Soft Interrupt corresponds to one of the arrays:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
1.2 irq_cpustat_t multiple soft interrupts can run on multiple CPUs at the same time. Even if the same Soft Interrupt occurs, it may run on multiple CPUs at the same time. The kernel manages a pending Soft Interrupt variable (pending) for each CPU, Which is irq_cpustat_t:
typedef struct {unsigned int __softirq_pending;} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
Each bit in the _ softirq_pending field corresponds to a Soft Interrupt. If a bit is set to a bit, the soft interrupt is waiting for processing.
1.3 ksoftirqd, A Soft Interrupt daemon, creates a ksoftirqd daemon for each CPU during the hot swapping of the CPU, A per_cpu variable is defined to save the task_struct structure pointer of each daemon:
DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
In most cases, soft interruptions are executed in the irq_exit phase. Soft interruptions not completed in the irq_exit phase may be executed in the daemon process.
2. to trigger a soft interrupt, you only need to call the API: raise_softirq. The implementation is simple. First, disable the local CPU interrupt, and then call: raise_softirq_irqoff.
void raise_softirq(unsigned int nr){unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);}
Let's take a look at raise_softirq_irqoff:
inline void raise_softirq_irqoff(unsigned int nr){__raise_softirq_irqoff(nr); ......if (!in_interrupt())wakeup_softirqd();}
First, use _ raise_softirq_irqoff to set the CPU Soft Interrupt pending flag (irq_stat [nr_cpus]), and then use in_interrupt to determine whether the current interrupt context or Soft Interrupt is disabled, if none of them are true, wake up the Soft Interrupt daemon and execute the Soft Interrupt callback function in the daemon. Otherwise, the Soft Interrupt will be executed in the exit phase of the interrupt. 3. The execution of Soft Interrupt is based on the above description. The execution of Soft Interrupt can be executed either in the daemon process or in the exit phase of the interrupt. In fact, soft interruptions are mostly executed in the exit phase of the interrupt (irq_exit) to achieve a faster response. The Daemon mechanism is added, just worried that a large number of soft interruptions will wait for execution, the kernel remains in the interrupted context for a long time. 3.1 run the following command in irq_exit to check the irq_exit part:
void irq_exit(void){ ......sub_preempt_count(IRQ_EXIT_OFFSET);if (!in_interrupt() && local_softirq_pending())invoke_softirq(); ......}
If the interrupt is nested, in_interrupt () ensures that the invoke_interrupt will be called only in the irq_exit phase of the outermost interrupt. Of course, local_softirq_pending will also determine whether the current CPU has a pending Soft Interrupt. The code will eventually enter _ do_softirq, and the kernel will ensure that when _ do_softirq is called, the interruption of the local CPU is disabled and the entry of _ do_softirq:
asmlinkage void __do_softirq(void){ ......pending = local_softirq_pending();__local_bh_disable((unsigned long)__builtin_return_address(0),SOFTIRQ_OFFSET);restart:/* Reset the pending bitmask before enabling irqs */set_softirq_pending(0);local_irq_enable();h = softirq_vec;do {if (pending & 1) { ......trace_softirq_entry(vec_nr);h->action(h);trace_softirq_exit(vec_nr); ......}h++;pending >>= 1;} while (pending);local_irq_disable();pending = local_softirq_pending();if (pending && --max_restart)goto restart;if (pending)wakeup_softirqd();lockdep_softirq_exit();__local_bh_enable(SOFTIRQ_OFFSET);}
- First, retrieve the pending status;
- Soft interruptions are prohibited mainly to prevent competition with the Soft Interrupt daemon;
- Clear all pending soft interruptions;
- Enable local CPU interruption;
- Execute the Soft Interrupt callback function cyclically;
- If a new soft interrupt is triggered after the loop is completed, restart the loop until the following conditions are met:
- There are no new soft interruptions waiting for execution;
- Max_softirq_restart has reached the maximum number of cycles. The current set value is 10 times;
- If the process has not been completed after the max_softirq_restart cycle, activate the daemon to handle the remaining soft interruptions;
- Resume soft interruptions before release;
3.2 execute in the ksoftirqd process as discussed in the previous sections, we can see that soft interruptions may also be executed by the ksoftirqd daemon, which may occur in the following two cases:
- The Soft Interrupt is executed in irq_exit, but after the max_softirq_restart cycle, the Soft Interrupt has not been processed completely. Although this happens rarely, it is possible after all;
- Other kernel code actively calls raise_softirq, And the daemon will be awakened instead of interrupting the context;
The daemon will eventually call the _ do_softirq to execute Soft Interrupt callback. The specific code is located in the run_ksoftirqd function. When the kernel closes the preemption, it will execute _ do_softirq, the specific process is not discussed here.
4. tasklet because the kernel has already defined 10 Soft Interrupt types, and we do not recommend that you add additional Soft Interrupt. Therefore, the implementation method of Soft Interrupt is mainly for a simple understanding, driver developers do not need to implement their own soft interruptions. However, in some cases, we do not want some operations to be executed directly in the interrupted handler, but want to be processed quickly later, which requires the tasklet mechanism. Tasklet is a delayed execution mechanism built on Soft Interrupt. Its implementation is based on the two Soft Interrupt types tasklet_softirq and hi_softirq. 4.1 at the end of the Soft Interrupt initialization function softirq_init, tasklet_softirq and hi_softirq are registered in the kernel:
void __init softirq_init(void){ ......open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);}
The kernel uses tasklet_struct to represent a tasklet. Its definition is as follows:
struct tasklet_struct{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;};
Next is used to link the tasklet of the same CPU into a linked list. State is used to indicate the current status of the tasklet. Currently, only the lowest two bits are used, it indicates that the instance has been scheduled and executed on another CPU:
enum{TASKLET_STATE_SCHED,/* Tasklet is scheduled for execution */TASKLET_STATE_RUN/* Tasklet is running (SMP only) */};
The atomic variable count is used by tasklet to count tasklet_disable and tasklet_enable. If the value of count is 0, tasklet is allowed. Otherwise, execution is not allowed. When tasklet_disable, this value is added to 1, this value is reduced by 1 when tasklet_enable is used. Func is the callback function pointer when the tasklet is executed, and data is used as the callback function func parameter.
4.2 There are two ways to initialize a tasklet. The first is static initialization. The following two macros are used to define a tasklet_struct structure, initialize fields in the structure with corresponding parameters:
- Declare_tasklet (name, func, data); defines a tasklet named name. The default value is enable, that is, the Count field is equal to 0.
- Declare_tasklet_disabled (name, func, data); defines a tasklet named name. The default value is enable, that is, the Count field is equal to 1.
The second is the dynamic initialization method: first define a tasklet_struct, and then use the tasklet_init function for initialization. By default, the tasklet is in the enable state:
struct tasklet_struct tasklet_xxx;......tasklet_init(&tasklet_xxx, func, data);
4.3 Use the following functions to enable and disable tasklet:
- Tasklet_disable() You can add 1 to the Count field to disable a tasklet. If the tasklet is running, it will not be returned until the execution is complete (using the tasklet_state_run flag ).
- Tasklet_disable_nosync() The asynchronous version of tasklet_disable, which does not wait for the tasklet to finish running.
- Tasklet_enable() To enable tasklet, simply subtract 1 from the count field.
Use the following functions to schedule tasklet execution:
- Tasklet_schedule(Struct tasklet_struct * t) if the tasklet_state_sched flag is 0, set tasklet_state_sched, then attach the tasklet to the tasklet linked list of the CPU waiting for execution, and then issue the tasklet_softirq software interruption request.
- Tasklet_hi_schedule(Struct tasklet_struct * t) the effect is the same as above. The difference is that it sends a hi_softirq software interrupt request.
Destroy tasklet and use the following functions:
- Tasklet_kill(Struct tasklet_struct * t) if the tasklet is in the tasklet_state_sched state or the tasklet is being executed, it will wait until the tasklet is completed and then clear the tasklet_state_sched state.
4.4 tasklet's internal execution mechanism kernel defines a tasklet_head structure for each CPU, which is used to manage the scheduling and execution of tasklet on each CPU:
struct tasklet_head{struct tasklet_struct *head;struct tasklet_struct **tail;};static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
Back to section 4.1, we know that tasklet is implemented using the two soft interrupts tasklet_softirq and hi_softirq. The two soft interrupts only have different priorities. Therefore, we only discuss the implementation of tasklet_softirq, the interrupt callback function of tasklet_softirq is tasklet_action. Let's look at its code:
static void tasklet_action(struct softirq_action *a){struct tasklet_struct *list;local_irq_disable();list = __this_cpu_read(tasklet_vec.head);__this_cpu_write(tasklet_vec.head, NULL);__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);local_irq_enable();while (list) {struct tasklet_struct *t = list;list = list->next;if (tasklet_trylock(t)) {if (!atomic_read(&t->count)) {if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))BUG();t->func(t->data);tasklet_unlock(t);continue;}tasklet_unlock(t);}local_irq_disable();t->next = NULL;*__this_cpu_read(tasklet_vec.tail) = t;__this_cpu_write(tasklet_vec.tail, &(t->next));__raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_enable();}}
Resolution:
- When local interruption is disabled, remove the tasklet linked list of the current CPU to a temporary linked list and clear the tasklet linked list of the current CPU. The reason for this is to process the current tasklet linked list, allow new tasklets to be scheduled into the list to be processed.
- Traverse the temporary linked list and use tasklet_trylock to determine whether the current tasklet is running on another CPU. tasklet is not disabled:
- If the task is not running or disabled, clear the tasklet_state_sched status bit and run the tasklet callback function.
- If the task is already running or disabled, re-adding the tasklet will display the list of tasklets to be processed by the current CPU, and trigger the tasklet_softirq Soft Interrupt, waiting for the next Soft Interrupt to be executed again.
I have a question. I read the above Code. If a tasklet is tasklet_schedule before it is executed, will it lead to the soft interruption of tasklet_softirq?
Through the above analysis, we need to note that tasklet has the following features:
- The same tasklet can only be executed on one CPU at the same time, but different tasklets can be executed on different CPUs at the same time;
- Once tasklet_schedule is called, the kernel will ensure that the tasklet will be executed once on a certain CPU;
- If tasklet_schedule is called and tasklet is not in the running status, it will only be executed once;
- If the tasklet is being executed when tasklet_schedule is called, it will be scheduled to be executed again later;
- If there is a resource conflict between the two tasklets, the spin lock should be used for Synchronous protection;