Comprensión profunda de las notas del kernel de LINUX Capítulo 4 Interrupciones y excepciones

Ejecución anidada de controladores de interrupciones y excepciones.

https://blog.csdn.net/denglin12315/article/details/121703669

1. Historia

En versiones anteriores del kernel de Linux, las interrupciones se dividían en dos tipos:

1. Interrupción rápida, al aplicar, marque IRQF_DISABLED y no se permiten nuevas interrupciones en IRQ HANDLER;

2. Las interrupciones lentas, sin el indicador IRQF_DISABLED cuando se aplica, permiten que se aniden otras interrupciones nuevas en IRQ HANDLER.

En el antiguo kernel de Linux, si una rutina de servicio de interrupción no quiere ser interrumpida por otras interrupciones, podemos ver el siguiente código:

request_irq(FLOPPY_IRQ, disquete_interrupt,- IRQF_DISABLED, "disquete", NULL)

2. Ahora

IRQF_DISABLED quedó obsoleto en la siguiente confirmación en 2010:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e58aa3d2d0cc

Su registro de confirmación explica claramente algunos riesgos que puede introducir la interrupción del anidamiento, como el desbordamiento de la pila, etc. En otras palabras, a partir de esta confirmación, Linux real ya no admite el anidamiento de interrupciones, no existe el concepto de interrupciones rápidas y lentas, y el indicador IRQF_DISABLED también se invalida . En IRQ HANDLER, ya sea que se establezca una interrupción o no se establezca IRQF_DISABLED, el kernel no habilitará la respuesta de la CPU a las interrupciones:

Este indicador IRQF_DISABLED invalidado no tiene significado en el kernel. Más tarde, la marca en sí fue eliminada del kernel y pasó a ser cosa del pasado:

3. Hardware

Después de que ocurre una interrupción, el hardware general bloqueará automáticamente la respuesta de la CPU a la interrupción y, a nivel de software, la interrupción no se volverá a habilitar hasta que se complete IRQ HANDLER. Por ejemplo, para los procesadores ARM, cuando llega una excepción, el hardware enmascarará automáticamente la interrupción:

Es decir, cuando el procesador ARM recibe una interrupción, ingresa al modo de interrupción y el hardware configura el bit IRQ del registro CPSR del procesador ARM para enmascarar la IRQ.

El kernel de Linux reabrirá la respuesta de CPSR a IRQ en los dos momentos siguientes:

1. Devuelve el SOFTIRQ de la mitad inferior de la interrupción de IRQ HANDLER

2. Devolver un contexto de hilo desde IRQ HANDLER

Como puede ver en 1, SOFTIRQ puede responder a las interrupciones.

Manejo de excepciones

Registro de aprendizaje del proceso de llamada del sistema Linux arm64

Manejo de interrupciones

El proceso es similar al manejo de excepciones.

el1_irq
->el1_interrupt_handler handle_arch_irq //handle_arch_irq = gic_handle_irq
	->irq_handler	\handler
		->gic_handle_irq
			->handle_domain_irq
				->irq_enter;
					->->preempt_count_add(HARDIRQ_OFFSET);
				->generic_handle_irq_desc;
					-> desc->handle_irq = handle_edge_irq //handle_irq = handle_edge_irq or handle_level_irq ...
						->handle_irq_event
							->handle_irq_event_percpu
							->__handle_irq_event_percpu
								->res = action->handler(irq, action->dev_id);
				->irq_exit;
	->arm64_preempt_schedule_irq //内核抢占

Estructura de datos IRQ

No se encontró el hw_interrupt_type correspondiente a arm64.

Guardar valor de registro para el controlador de interrupciones

Guarde los registros de recuperación kernel_entry y kernel_exit a través de
kernel_entry y kernel_exit

Para los procesadores ARM, cuando ocurre una excepción, el hardware enmascarará automáticamente la interrupción:

Es decir, cuando el procesador ARM recibe una interrupción, ingresa al modo de interrupción y el hardware configura el bit IRQ del registro CPSR del procesador ARM para enmascarar la IRQ.

Aquí nos centramos en ERET. Tomando arm64 como ejemplo, después de llamar a esta instrucción, PSTATE restaura el valor de SPSR_ELn y la PC restaura el valor de ELR_ELn.
https://blog.csdn.net/weixin_42135087/article/details/107227624

Estado del proceso ARMv8-A, introducción a PSTATE
https://blog.csdn.net/longwang155069/article/details/105204547

hacer_IRQ

__do_IRQ

Estas dos secciones corresponden a los siguientes procesos

-> desc->handle_irq = handle_edge_irq //handle_irq = handle_edge_irq or handle_level_irq ...
	->handle_irq_event
		->handle_irq_event_percpu
			->__handle_irq_event_percpu
				->res = action->handler(irq, action->dev_id);
void handle_edge_irq(struct irq_desc *desc)
{
    
    
	raw_spin_lock(&desc->lock);

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

	if (!irq_may_run(desc)) {
    
    
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

	/*
	 * If its disabled or no action available then mask it and get
	 * out of here.
	 */
	if (irqd_irq_disabled(&desc->irq_data) || !desc->action) {
    
    
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

	kstat_incr_irqs_this_cpu(desc);

	/* Start handling the irq */
	desc->irq_data.chip->irq_ack(&desc->irq_data);

	do {
    
    
		if (unlikely(!desc->action)) {
    
    
			mask_irq(desc);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		if (unlikely(desc->istate & IRQS_PENDING)) {
    
    
			if (!irqd_irq_disabled(&desc->irq_data) &&
			    irqd_irq_masked(&desc->irq_data))
				unmask_irq(desc);
		}

		handle_irq_event(desc);

	} while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));

out_unlock:
	raw_spin_unlock(&desc->lock);
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    
    
	irqreturn_t ret;

	desc->istate &= ~IRQS_PENDING;
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	raw_spin_unlock(&desc->lock);

	ret = handle_irq_event_percpu(desc);

	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    
    
	irqreturn_t retval;
	unsigned int flags = 0;

	retval = __handle_irq_event_percpu(desc, &flags);

	add_interrupt_randomness(desc->irq_data.irq, flags);

	if (!noirqdebug)
		note_interrupt(desc, retval);
	return retval;
}

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
    
    
	irqreturn_t retval = IRQ_NONE;
	unsigned int irq = desc->irq_data.irq;
	struct irqaction *action;

	record_irq_time(desc);

	for_each_action_of_desc(desc, action) {
    
    
		irqreturn_t res;

		/*
		 * If this IRQ would be threaded under force_irqthreads, mark it so.
		 */
		if (irq_settings_can_thread(desc) &&
		    !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
			lockdep_hardirq_threaded();

		trace_irq_handler_entry(irq, action);
		res = action->handler(irq, action->dev_id);
		trace_irq_handler_exit(irq, action, res);

		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
			      irq, action->handler))
			local_irq_disable();

		switch (res) {
    
    
		case IRQ_WAKE_THREAD:
			/*
			 * Catch drivers which return WAKE_THREAD but
			 * did not set up a thread function
			 */
			if (unlikely(!action->thread_fn)) {
    
    
				warn_no_thread(irq, action);
				break;
			}

			__irq_wake_thread(desc, action);

			fallthrough;	/* to add to randomness */
		case IRQ_HANDLED:
			*flags |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
	}

	return retval;
}

Rescatar interrupciones perdidas

núcleo/irq/chip.c

irq_startup
->irq_enable
->check_irq_resend

int check_irq_resend(struct irq_desc *desc, bool inject)
{
    
    
	int err = 0;

	/*
	 * We do not resend level type interrupts. Level type interrupts
	 * are resent by hardware when they are still active. Clear the
	 * pending bit so suspend/resume does not get confused.
	 */
	if (irq_settings_is_level(desc)) {
    
    
		desc->istate &= ~IRQS_PENDING;
		return -EINVAL;
	}

	if (desc->istate & IRQS_REPLAY)
		return -EBUSY;

	if (!(desc->istate & IRQS_PENDING) && !inject)
		return 0;

	desc->istate &= ~IRQS_PENDING;

	if (!try_retrigger(desc))
		err = irq_sw_resend(desc);

	/* If the retrigger was successfull, mark it with the REPLAY bit */
	if (!err)
		desc->istate |= IRQS_REPLAY;
	return err;
}

Interrupciones suaves y tasklets

ksoftirqd

El código de esta sección corresponde al siguiente smpboot_thread_fn

static struct smp_hotplug_thread softirq_threads = {
    
    
	.store			= &ksoftirqd,
	.thread_should_run	= ksoftirqd_should_run,
	.thread_fn		= run_ksoftirqd,
	.thread_comm		= "ksoftirqd/%u",
};

static __init int spawn_ksoftirqd(void)
{
    
    
	cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
				  takeover_tasklets);
	BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

	return 0;
}

static int smpboot_thread_fn(void *data)
{
    
    
	struct smpboot_thread_data *td = data;
	struct smp_hotplug_thread *ht = td->ht;

	while (1) {
    
    
		set_current_state(TASK_INTERRUPTIBLE);
		preempt_disable();
		
		...
		
		if (!ht->thread_should_run(td->cpu)) {
    
    
			preempt_enable_no_resched();
			schedule();
		} else {
    
    
			__set_current_state(TASK_RUNNING);
			preempt_enable();
			ht->thread_fn(td->cpu);
		}
	}

static void run_ksoftirqd(unsigned int cpu)
{
    
    
	local_irq_disable();
	if (local_softirq_pending()) {
    
    
		/*
		 * We can safely run softirq on inline stack, as we are not deep
		 * in the task stack here.
		 */
		__do_softirq();
		local_irq_enable();
		cond_resched();
		return;
	}
	local_irq_enable();
}

Supongo que te gusta

Origin blog.csdn.net/a13821684483/article/details/127983465
Recomendado
Clasificación