LINUX 커널 노트에 대한 심층적인 이해 4장 인터럽트 및 예외

인터럽트 및 예외 핸들러의 중첩 실행

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

1. 연혁

이전 Linux 커널 버전에서는 인터럽트가 두 가지 유형으로 구분되었습니다.

1. 빠른 인터럽트의 경우 적용 시 IRQF_DISABLED 표시를 추가하고 IRQ HANDLER에는 새로운 인터럽트가 허용되지 않습니다.

2. 느린 인터럽트는 IRQF_DISABLED 표시 없이 적용되며 새로운 다른 인터럽트는 IRQ HANDLER에 중첩될 수 있습니다.

이전 Linux 커널에서는 인터럽트 서비스 루틴이 다른 인터럽트에 의해 중단되기를 원하지 않는 경우 다음과 같은 코드를 볼 수 있습니다.

request_irq(FLOPPY_IRQ, floppy_interrupt,- IRQF_DISABLED, "플로피", NULL)

2. 지금

2010년 다음 커밋에서는 IRQF_DISABLED가 더 이상 사용되지 않습니다.

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

커밋 로그는 스택 오버플로와 같이 인터럽트 중첩으로 인해 발생할 수 있는 몇 가지 위험을 명확하게 설명합니다. 즉, 이 커밋부터 실제 Linux는 더 이상 인터럽트 중첩을 지원하지 않으며 빠르고 느린 인터럽트의 개념도 없으며 IRQF_DISABLED 플래그도 유효하지 않습니다 . IRQ HANDLER에서는 인터럽트가 IRQF_DISABLED로 설정되었는지 여부에 관계없이 커널은 CPU가 인터럽트에 응답하도록 활성화하지 않습니다.

이 무효화된 IRQF_DISABLED 플래그는 커널에서 의미가 없습니다. 나중에 마크 자체는 커널에서 삭제되어 과거의 일이 되었습니다.

3. 하드웨어

인터럽트가 발생한 후 일반 하드웨어는 인터럽트에 대한 CPU의 응답을 자동으로 차단하고 소프트웨어 수준에서는 IRQ HANDLER가 완료될 때까지 인터럽트가 다시 활성화되지 않습니다. 예를 들어 ARM 프로세서의 경우 예외가 발생하면 하드웨어가 자동으로 인터럽트를 차단합니다.

즉, ARM 프로세서가 인터럽트를 수신하면 인터럽트 모드로 진입하고 동시에 ARM 프로세서 CPSR 레지스터의 IRQ 비트가 하드웨어에 의해 IRQ를 마스크하도록 설정됩니다.

Linux 커널은 다음 두 번 IRQ에 대한 CPSR의 응답을 다시 엽니다.

1. IRQ HANDLER에서 인터럽트의 아래쪽 절반에 대한 SOFTIRQ를 반환합니다.

2. IRQ HANDLER에서 스레드 컨텍스트를 반환합니다.

1에서 볼 수 있듯이 SOFTIRQ는 인터럽트에 응답할 수 있습니다.

예외 처리

Linux arm64 시스템 호출 프로세스 학습 기록

인터럽트 처리

프로세스는 예외 처리와 유사합니다.

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 //内核抢占

IRQ 데이터 구조

arm64에 해당하는 hw_interrupt_type을 찾을 수 없습니다.

인터럽트 핸들러의 레지스터 값 저장

kernel_entry 및 kernel_exit를 통해 kernel_entry 및 kernel_exit 레지스터 저장 및 복원

ARM 프로세서의 경우 예외가 발생하면 하드웨어가 자동으로 인터럽트를 차단합니다.

즉, ARM 프로세서가 인터럽트를 수신하면 인터럽트 모드로 진입하고 동시에 ARM 프로세서 CPSR 레지스터의 IRQ 비트가 하드웨어에 의해 IRQ를 마스크하도록 설정됩니다.

여기서는 ERET에 중점을 두었습니다. arm64를 예로 들어 이 명령을 호출한 후 PSTATE는 SPSR_ELn 값을 복원하고, PC는 ELR_ELn 값을 복원합니다.
https://blog.csdn.net/weixin_42135087/article/details/107227624

ARMv8-A 프로세스 상태, PSTATE 소개
https://blog.csdn.net/longwang155069/article/details/105204547

do_IRQ

__do_IRQ

이 두 섹션은 다음 프로세스에 해당합니다.

-> 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;
}

손실된 인터럽트를 구출하세요

커널/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;
}

소프트 인터럽트 및 태스크릿

ksoftirqd

이 섹션의 코드는 아래 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();
}

추천

출처blog.csdn.net/a13821684483/article/details/127983465