37th issue - interruption of ARM Linux kernel

Author: Luo Yuzhe, intelligent software research center, Institute of software, Chinese Academy of Sciences

In the last issue, we introduced the common interrupt related functions in the ARM Linux kernel. In this issue, we will introduce the bottom half mechanism in the ARM Linux kernel.

1, ARM Linux kernel bottom half mechanism

In order to avoid complex nesting of interrupts, Linux kernel usually adopts the strategy of turning off interrupts, such as using the local mentioned in the previous issue_ irq_ Disable () and local_irq_save() function. However, the interruption time of system shutdown cannot be too long, because the response time of the interruption of peripheral devices such as mouse and keyboard is related to the user experience. In order to reduce the time of shutting down interrupt, the kernel of Linux system divides the processing of interrupt into two parts according to the importance: the upper part and the lower part.

The upper part of the interrupt is also called hard interrupt. The Linux kernel processes the work of the upper part when the interrupt is turned off, which includes the parts with high response time requirements or hardware related; the lower part of the interrupt includes soft interrupt (softirq), small task (tasklet) and work queue (work) There are three types of interrupts. The Linux kernel processes the second half of an interrupt when it is on.

The differences between the three types of interrupt bottom half are shown in the table below 1:

Sleep or not Whether reentry is required Whether to add and delete dynamically
Soft interrupt no yes no
Small tasks no no yes
Work queues yes no yes

Sleep is not allowed in the processing functions of soft interrupts and small tasks, while the processing functions of work queues run on the kernel thread and can sleep. Reentrant function means that different copies of the function can be run by multiple threads at the same time. The function either does not use shared resources to have separate function stacks, or uses locks to protect shared resources in the critical area. The processing function of the same soft interrupt may be executed on multiple processors, thus requiring that the processing function be reentrant. The same small task can only be executed on one processor, so it does not require the function to be reentrant. As we will see later, when adding work items to the work queue, the running work will be left in the original work pool to ensure the non reentrant nature of the task. The type of soft interrupt is determined during static compilation, so it cannot be added or deleted dynamically. Small tasks and work items in work queues can be added and deleted dynamically.

2, The processing flow of soft interrupt in ARM Linux kernel

The soft interrupt is executed in the second half of the interrupt and can be preempted by the hard interrupt. There are 10 kinds of soft interrupts defined in the openEuler kernel. The definition code can be found in the openeuler/kernel/blob/kernel-4.19/include/linux/interrupt.h file of the openEuler source repository 23:

enum

{

HI_SOFTIRQ=0, /\* High priority tasklet \*/

TIMER_SOFTIRQ, /\* Clock related soft interrupt \*/

NET_TX_SOFTIRQ, /\* Soft interrupt of network stack sending message \*/

NET_RX_SOFTIRQ, /\* Soft interrupt of receiving message in network stack \*/

BLOCK_SOFTIRQ, /\* Soft interrupt of block device \*/

IRQ_POLL_SOFTIRQ, /\* support IO Soft interrupt of device polling \*/

TASKLET_SOFTIRQ, /\* Low priority tasklet \*/

SCHED_SOFTIRQ, /\* Scheduling soft interrupt for load balancing between processors \*/

HRTIMER_SOFTIRQ, /\* High precision timer, not used \*/

RCU_SOFTIRQ, /\* RCU Soft interrupt, which should always be the last soft interrupt\*/

NR_SOFTIRQS /\* Number of soft interrupts, is10 \*/

};

The number of soft interrupts in enumeration type also shows their priority, and those with smaller number have higher priority.

Most of the key code related to soft interrupt can be found in the openeuler/kernel/blob/kernel-4.19/kernel/softirq.c file of the openEuler source repository. The following parts of the source code that are not specifically described can be found in the softirq. C file.

Firstly, the soft interrupt registry is defined to record the corresponding relationship between soft interrupt number and soft interrupt processing function. It is a softirq_ Array of action structs:

softirq_ The action structure is defined in the openeuler/kernel/blob/kernel-4.19/include/linux/interrupt.h file:

The member action is a function pointer to the soft interrupt handling function. The function pointer is registered in open_ In the softirq() function:

To trigger the soft interrupt corresponding to the soft interrupt number, raise can be called_ Softirq() function.

You can also call raise directly in the scenario where interrupt has been forbidden_ softirq_ Irqoff() function. In the lower part of the interrupt handler, IRQ is called_ Exit() exits the interrupt context and processes the soft interrupt if necessary and possible:

This function calls the function invoke_softirq () to handle soft interrupts. invoke_ The softirq() function first determines whether the soft interrupt thread is ready or running. If so, it calls the soft interrupt thread to execute the soft interrupt 4 . No force to interrupt threading_ Irqthreads variable control, see the following for details), according to whether config is defined_ HAVE_ IRQ_ EXIT_ ON_ IRQ_ Stack has two situations:

  • The soft interrupt runs on the interrupt stack. When there is no complex nesting of interrupts, the function call of the interrupt handler is not complex, and the interrupt stack is almost empty. Therefore, the soft interrupt can run safely and call__ do_softirq() function;
  • irq_ The exit() function is called on the current task stack. The current task may make complex function calls. Therefore, the stack may be very deep. In order to prevent overload, the soft interrupt is processed on its own stack and do is called_ softirq_ own_ Stack() function;

Call wakeup if forced interrupt threading_ The softirqd() function wakes up the thread handling the soft interrupt.

static inline void invoke_softirq(void)

{

if (ksoftirqd_running(local_softirq_pending()))

return;

if (!force_irqthreads) {

\#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK

/\*

\* We can safely execute softirq on the current stack if

\* it is the irq stack, because it should be near empty

\* at this stage.

\*/

\__do_softirq();

\#else

/\*

\* Otherwise, irq_exit() is called on the task stack that can

\* be potentially deep already. So call softirq in its own stack

\* to prevent from any overrun.

\*/

do_softirq_own_stack();

\#endif

} else {

wakeup_softirqd();

}

}

Force in function_ Irqthreads is a variable of type bool. Its definition can be found in the interrupt.h file:

When config is not used_ IRQ_ FORCED_ This variable defaults to false in threading macro, otherwise it will be set up in openeuler/kernel/blob/kernel-4.19/kernel/irq/manage.c file_ forced_ Irqthread() set to true:

When force_irqthreads is set to true and action - > thread_ Irqtf of flags_ FORCED_ IRQ in the manage.c file when thread bit is 1_ The thread() function sets the interrupt handler to irq_forced_thread_fn and call it to handle the interrupt:

/\*

\* Interrupt handler thread

\*/

static int irq_thread(void \*data)

{

struct callback_head on_exit_work;

struct irqaction \*action = data;

struct irq_desc \*desc = irq_to_desc(action-\>irq);

irqreturn_t (\*handler_fn)(struct irq_desc \*desc,

struct irqaction \*action);

if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,

&action-\>thread_flags))

handler_fn = irq_forced_thread_fn;

else

handler_fn = irq_thread_fn;

init_task_work(&on_exit_work, irq_thread_dtor);

task_work_add(current, &on_exit_work, false);

irq_thread_check_affinity(desc, action);

while (!irq_wait_for_interrupt(action)) {

irqreturn_t action_ret;

irq_thread_check_affinity(desc, action);

action_ret = handler_fn(desc, action);

if (action_ret == IRQ_WAKE_THREAD)

irq_wake_secondary(desc, action);

wake_threads_waitq(desc);

}

/\*

\* This is the regular exit path. \__free_irq() is stopping the

\* thread via kthread_stop() after calling

\* synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the

\* oneshot mask bit can be set.

\*/

task_work_cancel(current, irq_thread_dtor);

return 0;

}

IRQTF_ FORCED_ The value of thread is 3, which is used to identify whether the interrupt processing is forced to thread. Its definition can be found in the openeuler/kernel/blob/kernel-4.19/kernel/irq/internals.h file:

irq_ forced_ thread_ The fn() function calls the interrupt processing function thread when the interrupt is threaded_ FN, in the thirty sixth issue, we mentioned thread_ The FN function is generated by request_ threaded_ Registered by the irq() function. irq_ forced_ thread_ The code for fn() can be found in the manage.c file:

The main work of dealing with soft interrupt is in the function__ Do_ Completed in softirq()__ do_softirq() uses the variable pending to save the currently triggered soft interrupt. The trigger of the soft interrupt is through the raise described earlier_ When the softirq() function is completed, the n-th bit in the pending variable is set to 1, which means that the soft interrupt with N number is triggered__ do_softirq() traverses the pending variable from low to high when the interrupt is turned on, and calls the soft interrupt processing function to process each triggered soft interrupt. And then__ Do_ The softirq() function turns off the interrupt and gets the current soft interrupt trigger to check whether a new soft interrupt occurs when processing the soft interrupt. If a new soft interrupt occurs, there are two situations:

  • If the execution time of the current soft interrupt processing process does not exceed Max_ SOFTIRQ_ The time limit is set by time and does not need to be rescheduled, and the number of soft interrupts executed does not exceed max_ SOFTIRQ_ For the number of times set by restart, skip to restart and perform the above traversal to trigger the soft interrupt and process the soft interrupt. This is because the soft interrupt processing process cannot always occupy the CPU;
  • Otherwise call wakeup_softirqd() wakes up the soft interrupt thread to handle the soft interrupt;
asmlinkage \__visible void \__softirq_entry \__do_softirq(void)

{

unsigned long end = jiffies + MAX_SOFTIRQ_TIME;

unsigned long old_flags = current-\>flags;

int max_restart = MAX_SOFTIRQ_RESTART;

struct softirq_action \*h;

bool in_hardirq;

\__u32 pending;

int softirq_bit;

/\*

\* Mask out PF_MEMALLOC s current task context is borrowed for the

\* softirq. A softirq handled such as network RX might set PF_MEMALLOC

\* again if the socket is related to swap

\*/

current-\>flags &= \~PF_MEMALLOC;

pending = local_softirq_pending();

account_irq_enter_time(current);

\__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);

in_hardirq = lockdep_softirq_start();

restart:

/\* Reset the pending bitmask before enabling irqs \*/

set_softirq_pending(0);

local_irq_enable();

h = softirq_vec;

while ((softirq_bit = ffs(pending))) {

unsigned int vec_nr;

int prev_count;

h += softirq_bit - 1;

vec_nr = h - softirq_vec;

prev_count = preempt_count();

kstat_incr_softirqs_this_cpu(vec_nr);

trace_softirq_entry(vec_nr);

h-\>action(h);

trace_softirq_exit(vec_nr);

if (unlikely(prev_count != preempt_count())) {

pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with
%08x?\\n",

vec_nr, softirq_to_name[vec_nr], h-\>action,

prev_count, preempt_count());

preempt_count_set(prev_count);

}

h++;

pending \>\>= softirq_bit;

}

rcu_bh_qs();

local_irq_disable();

pending = local_softirq_pending();

if (pending) {

if (time_before(jiffies, end) && !need_resched() &&

\--max_restart)

goto restart;

wakeup_softirqd();

}

lockdep_softirq_end(in_hardirq);

account_irq_exit_time(current);

\__local_bh_enable(SOFTIRQ_OFFSET);

WARN_ON_ONCE(in_interrupt());

current_restore_flags(old_flags, PF_MEMALLOC);

}

Max in openEuler_ SOFTIRQ_ Time is 2 ms, MAX_SOFTIRQ_RESTART is 10.

Each processor has a soft interrupt thread, wakeup_ The softirqd() function can wake up this thread:

The core function run of soft interrupt thread_ Ksoftirqd() by calling__ Do_ The softirq() function handles soft interrupts:

It should be noted that the difference between soft interrupt and software generated interrupt (SGI) is that the former is a way to deal with non urgent tasks in the second half of the interrupt processing process, and the latter is also called inter processor interrupt, which is usually used for communication between processors.

3, Conclusion

In this issue, we introduced the basic situation of ARM Linux kernel bottom half mechanism and the processing flow of soft interrupt. In the next issue, we will continue to introduce the workflow of small tasks and work queues.

  1. Deep analysis of Linux kernel, by Yu Huabing, 2019 ↩︎

  2. Deep analysis of Linux kernel, by Yu Huabing, 2019 ↩︎

  3. https://blog.csdn.net/yhb1047818384/article/details/63687126 ↩︎

  4. Deep analysis of Linux kernel, by Yu Huabing, 2019 ↩︎

Tags: Linux network socket

Posted on Thu, 04 Jun 2020 11:14:59 -0400 by Zamees