리눅스 커널에서 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로 선언됨

+ Recent posts