리눅스 커널에서 preemptive scheduling이 일어나는 지점은 다음과 같다:
- 인터럽트를 처리하고 난 시점
- 시스템 콜을 처리하고 난 시점
- 시그널을 처리하고 난 시점
이 동작은 아키텍처에 의존적인 동작이었다. 즉, Arm64나 RISCV 별로 루틴이 달랐다.
최근에 common한 path로 처리가 됐다.
관련 커밋 메시지와 커밋 아이디는 아래와 같다:
f0bddf50586da81360627a772be0e3
riscv: entry: Convert to generic entry
'21 Feb 2023'에 머지됐으니, v6.3 버전부터 이와 같은 path로 처리된다.
RISCV 관점 분석
RISCV에서 인터럽트는 do_irq() 함수 (위치: arch/riscv/kernel/traps.c)에서 핸들링한다.
arch/riscv/kernel/traps.c
asmlinkage void noinstr do_irq(struct pt_regs *regs)
{
irqentry_state_t state = irqentry_enter(regs);
if (IS_ENABLED(CONFIG_IRQ_STACKS) && on_thread_stack())
call_on_irq_stack(regs, handle_riscv_irq);
else
handle_riscv_irq(regs);
irqentry_exit(regs, state);
}
handle_riscv_irq() 함수에서 인터럽트 디스크립터를 읽어서 인터럽트 핸들러를 호출한다. 이 과정을
마무리하면 결국 irqentry_exit() 함수가 호출된다.
Arm64 관점 분석
이번에는 Arm64 아키텍처 코드이다.
exit_to_kernel_mode() 함수에서 irqentry_exit() 함수를 호출한다.
arch/arm64/kernel/entry-common.c
static void noinstr exit_to_kernel_mode(struct pt_regs *regs,
irqentry_state_t state)
{
mte_check_tfsr_exit();
irqentry_exit(regs, state);
}
참고로, exit_to_kernel_mode() 함수는 __el1_irq() 함수에서 호출된다.
arch/arm64/kernel/entry-common.c
static __always_inline void __el1_irq(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
irqentry_state_t state;
state = enter_from_kernel_mode(regs);
irq_enter_rcu();
do_interrupt_handler(regs, handler);
irq_exit_rcu();
exit_to_kernel_mode(regs, state);
}
코드 리뷰:
- do_interrupt_handler() 함수: 인터럽트 핸들러 호출
- exit_to_kernel_mode() 함수 호출
정리하면 다음과 같다:
RISCV: do_irq -> exit_to_kernel_mode
Arm64: __el1_irq -> exit_to_kernel_mode
irqentry_exit() 함수의 구현부다.
kernel/entry/common.c
noinstr void irqentry_exit(struct pt_regs *regs, irqentry_state_t state)
{
lockdep_assert_irqs_disabled();
[...]
instrumentation_begin();
if (IS_ENABLED(CONFIG_PREEMPTION))
irqentry_exit_cond_resched();
/* Covers both tracing and lockdep */
trace_hardirqs_on();
instrumentation_end();
irqentry_exit_cond_resched() 함수를 호출한다.
irqentry_exit_cond_resched() 함수는 매크로 타입으로 dynamic_irqentry_exit_cond_resched() 함수로
치환된다. 따라서 dynamic_irqentry_exit_cond_resched() 함수를 분석하자.
dynamic_irqentry_exit_cond_resched -> raw_irqentry_exit_cond_resched
(where) #define irqentry_exit_cond_resched() dynamic_irqentry_exit_cond_resched()
void dynamic_irqentry_exit_cond_resched(void)
{
if (!static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched))
return;
raw_irqentry_exit_cond_resched();
}
raw_irqentry_exit_cond_resched() 함수를 호출한다.
raw_irqentry_exit_cond_resched() 함수의 구현부이다.
kernel/entry/common.c
void raw_irqentry_exit_cond_resched(void)
{
if (!preempt_count()) {
/* Sanity check RCU and thread stack */
rcu_irq_exit_check_preempt();
if (IS_ENABLED(CONFIG_DEBUG_ENTRY))
WARN_ON_ONCE(!on_thread_stack());
if (need_resched() && arch_irqentry_exit_need_resched())
preempt_schedule_irq();
}
}
need_resched() 함수와 arch_irqentry_exit_need_resched() 함수가 true이면 preempt_schedule_irq() 함수를
호출한다.
- TIF_NEED_RESCHED가 task_struct.thread_info.flags에 포함된 경우
- 아키텍처마다 선언된 arch_irqentry_exit_need_resched() 매크로가 true를 리턴하는 경우: 대부분 true로 선언됨
'리눅스 커널의 구조와 원리 > 인터럽트와 인터럽트 후반부' 카테고리의 다른 글
| [리눅스커널] IRQ 스레드를 생성하는 시점 확인하기 (0) | 2024.06.05 |
|---|---|
| [리눅스커널] 인터럽트 후반부 처리 기법 종류 및 소개 (0) | 2024.05.28 |
| [리눅스커널] 인터럽트 후반부 - Top Half/Bottom Half 란 무엇일까? (0) | 2024.05.27 |
| [리눅스 커널] 리눅스 커널에서의 인터럽트 처리 흐름 (0) | 2024.01.02 |
| ftrace와 커널 로그로 인터럽트 컨텍스트 확인해보기 (0) | 2024.01.01 |
