[LINUX] Interrupt processing flow seen in source code

Interrupt processing flow seen in source code

Previous article explained the flow of interrupt registration, but this article will explain the flow of interrupt processing. After the interrupt is notified to the cpu, check the processing until the interrupt is executed.

The flow of interrupt processing is roughly as follows.

  1. IDT reference
  2. Executing an interrupt handler

In addition, this article does not deal with the mechanism until the interrupt is notified to the cpu. For example, there is Local APIC as an interrupt controller attached to cpu, but please refer to the article here for that. please.

environment

--Architecture: x86 --Kernel: Linux 4.16

1. IDT reference

When the device raises an interrupt, the cpu is notified of the interrupt vector number. The cpu then looks up the IDTR and accesses the IDT entry that corresponds to the vector number. IDTR is a register with the size and address of IDT, and IDT is a table of gate descriptors. Each IDT entry contains the address of the interrupt handler. However, this handler is a kernel-preconfigured handler and is different from the device-configured interrupt handler.

In the kernel, IDTs are represented by an array called idt_table [].

\arch\x86\kernel\idt.c


gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

idt_table [] is set when the OS is initialized. For example, the idt_table [] corresponding to an external interrupt is set as follows.

\arch\x86\kernel\idt.c


void __init idt_setup_apic_and_irq_gates(void)
{
	int i = FIRST_EXTERNAL_VECTOR;
	void *entry;

	idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true);

	for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) {
		entry = irq_entries_start + 8 * (i - FIRST_EXTERNAL_VECTOR);
		set_intr_gate(i, entry);
	}
    ...
}

The address of the interrupt handler is set in entry. irq_entries_start is the start address of the interrupt handler table, and each entry is aligned with 8 bytes. The address set in entry is reflected in the descriptor by set_intr_gate ().

2. Execution of interrupt handler

irq_entries_start contains a handler set by the kernel. irq_entries_start is defined in the following assembly.

\arch\x86\entry\entry_64.S


ENTRY(irq_entries_start)
    vector=FIRST_EXTERNAL_VECTOR
    .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)
	UNWIND_HINT_IRET_REGS
	pushq	$(~vector+0x80)			/* Note: always in signed byte range */
	jmp	common_interrupt
	.align	8
	vector=vector+1
    .endr
END(irq_entries_start)

Here we push ~ vector + 0x80 onto the stack and jump to common_interrupt. vector corresponds to the vector number notified to cpu.

The common_interrupt is as follows.

\arch\x86\entry\entry64_S


common_interrupt:
	addq	$-0x80, (%rsp)			/* Adjust vector to [-256, -1] range */
	call	interrupt_entry
	UNWIND_HINT_REGS indirect=1
	call	do_IRQ	/* rdi points to pt_regs */
	/* 0(%rsp): old RSP */

Subtract 0x80 from the top value of the stack (~ vector + 0x80), then call interrupt_entry. In interrupt_entry, adjust the stack and so on. Then call do_IRQ.

do_IRQ () performs the following processing.

\arch\x86\kernel\irq.c


__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc * desc;
	unsigned vector = ~regs->orig_ax;

	entering_irq();

	RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

	desc = __this_cpu_read(vector_irq[vector]);

	if (!handle_irq(desc, regs)) { ...

First, retrieve the vector number with ~ regs-> orig_ax. Next, search the interrupt descriptor from vector_irq [] and execute the interrupt handler with handle_irq (). The interrupt handler set by the device is executed here.

In handle_irq (), a function called __handle_irq_event_percpu () is called to execute the handler. The process is as follows (some codes are omitted).

\kernel\irq\handle.c


irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
	for_each_action_of_desc(desc, action) {
		res = action->handler(irq, action->dev_id);

		switch (res) {
		case IRQ_WAKE_THREAD:
			__irq_wake_thread(desc, action);

		case IRQ_HANDLED:
			*flags |= action->flags;
			break;

		default:
			break;
		}
		retval |= res;
	}
	return retval;
}

action-> handler is a handler registered by the device. If you do not set a handler during registration, the kernel sets a default handler. When registering an interrupt, a thread that processes interrupts is registered separately from the handler. If you need to start a thread for interrupt processing after executing the handler, start the thread with __irq_wake_thread ().

The above is the flow from the notification of the interrupt to the cpu to the execution of the interrupt.

Recommended Posts

Interrupt processing flow seen in source code
Interrupt registration procedure as seen in the source code
View the implementation source code in iPython
Flow from source code to creating executable
Syntax highlighting source code in PowerPoint / Keynote
File processing in Python
Multithreaded processing in python
Text processing in Python
Queue processing in Python
Remove one-line comments containing Japanese from source code in Python
Always check PEP8 while editing Python source code in Emacs