[LINUX] Ablauf der Interrupt-Verarbeitung im Quellcode

Ablauf der Interrupt-Verarbeitung im Quellcode

Vorheriger Artikel erläuterte den Ablauf der Interrupt-Registrierung, dieser Artikel erläutert jedoch den Ablauf der Interrupt-Verarbeitung. Nachdem die CPU über den Interrupt benachrichtigt wurde, überprüfen Sie die Verarbeitung, bis der Interrupt ausgeführt wird.

Der Ablauf der Interrupt-Verarbeitung ist ungefähr wie folgt.

  1. IDT-Referenz
  2. Ausführen des Interrupt-Handlers

Darüber hinaus befasst sich dieser Artikel nicht mit dem Mechanismus, bis die Unterbrechung der CPU gemeldet wird. Beispielsweise ist Local APIC als Interrupt-Controller an die CPU angeschlossen. Weitere Informationen hierzu finden Sie im Artikel hier. Bitte.

Umgebung

--Architektur: x86

1. IDT-Referenz

Wenn das Gerät einen Interrupt auslöst, wird die CPU über die Interruptvektornummer informiert. Die CPU sucht dann die IDTR und greift auf den Eintrag für die IDT zu, der der Vektornummer entspricht. IDTR ist ein Register mit der Größe und Adresse von IDT, und IDT ist eine Tabelle von Gate-Deskriptoren. Jeder IDT-Eintrag enthält die Adresse des Interrupt-Handlers. Dieser Handler ist jedoch ein vom Kernel voreingestellter Handler und unterscheidet sich von dem vom Gerät festgelegten Interrupt-Handler.

Im Kernel wird IDT durch ein Array namens idt_table [] dargestellt.

\arch\x86\kernel\idt.c


gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

idt_table [] wird festgelegt, wenn das Betriebssystem initialisiert wird. Beispielsweise wird idt_table [], das einem externen Interrupt entspricht, wie folgt gesetzt.

\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);
	}
    ...
}

Die Adresse des Interrupt-Handlers wird im Eintrag festgelegt. irq_entries_start ist die Startadresse der Interrupt-Handler-Tabelle, wobei jeder Eintrag in 8 Bytes ausgerichtet ist. Die im Eintrag festgelegte Adresse wird im Deskriptor von set_intr_gate () wiedergegeben.

2. Ausführung des Interrupt-Handlers

irq_entries_start enthält vom Kernel festgelegte Handler. irq_entries_start wird in der folgenden Assembly definiert.

\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)

Hier schieben wir ~ vector + 0x80 auf den Stack und springen zu common_interrupt. Der Vektor entspricht der der CPU mitgeteilten Vektornummer.

Der common_interrupt sieht folgendermaßen aus:

\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 */

Subtrahieren Sie 0x80 vom obersten Wert des Stapels (~ vector + 0x80) und rufen Sie dann interrupt_entry auf. Passen Sie in interrupt_entry den Stapel usw. an. Rufen Sie dann do_IRQ auf.

do_IRQ () führt die folgende Verarbeitung durch.

\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)) { ...

Rufen Sie zuerst die Vektornummer mit ~ regs-> orig_ax ab. Suchen Sie dann den Interrupt-Deskriptor aus vector_irq [] und führen Sie den Interrupt-Handler mit handle_irq () aus. Hier wird der vom Gerät eingestellte Interrupt-Handler ausgeführt.

In handle_irq () wird eine Funktion namens __handle_irq_event_percpu () aufgerufen, um den Handler auszuführen. Der Vorgang ist wie folgt (einige Codes werden weggelassen).

\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 ist ein vom Gerät registrierter Handler. Wenn Sie während der Registrierung keinen Handler festlegen, legt der Kernel einen Standardhandler fest. Wenn Sie einen Interrupt registrieren, wird ein Thread, der die Interrupt-Verarbeitung ausführt, separat vom Handler registriert. Wenn Sie nach dem Ausführen des Handlers einen Thread für die Interrupt-Verarbeitung starten müssen, starten Sie den Thread mit __irq_wake_thread ().

Das Obige ist der Fluss von der Benachrichtigung des Interrupts an die CPU bis zur Ausführung des Interrupts.

Recommended Posts

Ablauf der Interrupt-Verarbeitung im Quellcode
So registrieren Sie einen Interrupt wie im Quellcode angegeben
Zeigen Sie den Implementierungsquellcode in iPython an
Fluss vom Quellcode zum Erstellen des Ausführungsformats
Syntax hebt den Quellcode in PowerPoint / Keynote hervor
Dateiverarbeitung in Python
Multithread-Verarbeitung in Python
Textverarbeitung mit Python
Verarbeitung in Python beenden
Entfernen Sie einzeilige Kommentare einschließlich Japanisch aus dem Quellcode in Python
Überprüfen Sie immer PEP8, während Sie den Python-Quellcode in Emacs bearbeiten