[LINUX] Flux de traitement d'interruption vu dans le code source

Flux de traitement d'interruption vu dans le code source

Article précédent a expliqué le flux d'enregistrement des interruptions, mais cet article explique le flux de traitement des interruptions. Une fois l'interruption notifiée au processeur, vérifiez le traitement jusqu'à ce que l'interruption soit exécutée.

Le déroulement du traitement d'interruption est à peu près le suivant.

  1. Référence IDT
  2. Exécution du gestionnaire d'interruption

De plus, cet article ne traite pas du mécanisme tant que l'interruption n'est pas notifiée au processeur. Par exemple, il existe un APIC local en tant que contrôleur d'interruption attaché au processeur, mais veuillez vous référer à l'article ici pour cela. S'il vous plaît.

environnement

--Architecture: x86 --Kernel: Linux 4.16

1. Référence IDT

Lorsque le périphérique déclenche une interruption, le processeur est informé du numéro du vecteur d'interruption. Le processeur recherche alors l'IDTR et accède à l'entrée de l'IDT qui correspond au numéro de vecteur. IDTR est un registre avec la taille et l'adresse d'IDT, et IDT est une table de descripteurs de portes. Chaque entrée IDT contient l'adresse du gestionnaire d'interruption. Cependant, ce gestionnaire est un gestionnaire prédéfini par le noyau et est différent du gestionnaire d'interruption défini par le périphérique.

Dans le noyau, IDT est représenté par un tableau appelé idt_table [].

\arch\x86\kernel\idt.c


gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

idt_table [] est défini lorsque le système d'exploitation est initialisé. Par exemple, idt_table [] correspondant à une interruption externe est défini comme suit.

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

L'adresse du gestionnaire d'interruption est définie dans l'entrée. irq_entries_start est l'adresse de début de la table du gestionnaire d'interruption, avec chaque entrée alignée sur 8 octets. L'adresse définie dans l'entrée est reflétée dans le descripteur par set_intr_gate ().

2. Exécution du gestionnaire d'interruption

irq_entries_start contient des gestionnaires définis par le noyau. irq_entries_start est défini dans l'assemblage suivant.

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

Ici, nous poussons ~ vector + 0x80 sur la pile et passons à common_interrupt. vector correspond au numéro de vecteur notifié au cpu.

Le common_interrupt ressemble à ceci:

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

Soustrayez 0x80 de la valeur supérieure de la pile (~ vector + 0x80), puis appelez interrupt_entry. Dans interrupt_entry, ajustez la pile et ainsi de suite. Appelez ensuite do_IRQ.

do_IRQ () effectue le traitement suivant.

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

Tout d'abord, récupérez le numéro de vecteur avec ~ regs-> orig_ax. Recherchez ensuite le descripteur d'interruption dans vector_irq [] et exécutez le gestionnaire d'interruption avec handle_irq (). Le gestionnaire d'interruption défini par l'appareil est exécuté ici.

Dans handle_irq (), une fonction appelée __handle_irq_event_percpu () est appelée pour exécuter le gestionnaire. Le processus est le suivant (certains codes sont omis).

\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 est un gestionnaire enregistré par l'appareil. Si vous ne définissez pas de gestionnaire lors de l'enregistrement, le noyau définit un gestionnaire par défaut. Lors de l'enregistrement d'une interruption, un thread qui effectue un traitement d'interruption est enregistré séparément du gestionnaire. Si vous avez besoin de démarrer un thread pour interrompre le traitement après avoir exécuté le gestionnaire, démarrez le thread avec __irq_wake_thread ().

Ce qui précède est le flux de la notification de l'interruption au processeur jusqu'à l'exécution de l'interruption.

Recommended Posts

Flux de traitement d'interruption vu dans le code source
Comment enregistrer une interruption comme vu dans le code source
Afficher le code source de l'implémentation dans iPython
Flux du code source à la création du format d'exécution
La syntaxe met en évidence le code source dans PowerPoint / Keynote
Traitement de fichiers en Python
Traitement multithread en python
Traitement de texte avec Python
Traitement des requêtes en Python
Supprimer les commentaires sur une ligne, y compris le japonais du code source en Python
Vérifiez toujours PEP8 lors de l'édition du code source Python dans Emacs