커널에서 인터럽트 처리 마스터하기

발행: (2025년 12월 24일 오전 10:02 GMT+9)
12 min read
원문: Dev.to

Source: Dev.to

번역을 진행하려면 번역하고자 하는 전체 텍스트를 제공해 주시겠어요? 텍스트를 주시면 원본 형식과 마크다운을 유지하면서 한국어로 번역해 드리겠습니다.

소개

커널을 처음부터 만드는 것은 한 가지 일입니다. 그 인터럽트 시스템을 마스터하는 것은 또 다른 일입니다. 이 기사에서는 맞춤형 커널을 위한 완전한 기능을 갖춘 인터럽트 처리 서브시스템의 설계, 구현, 그리고 미세 조정을 단계별로 살펴볼 것입니다 – 큐, 우선순위, 통계 수집을 모두 포함하며, 외부 멀티태스킹 프레임워크에 의존하지 않습니다.

주요 목표

#목표
1중요한 ISR(예: PIT)의 즉시 실행
2비중요한 ISR에 대한 유연한 큐잉 및 우선순위 지정
3인터럽트 관련 통계(처리 시간, 트리거 빈도, 드롭 수)
4ISR 등록 및 처리를 위한 간단하고 유지보수 가능한 API

ISR 파라미터 블록

시스템의 핵심은 ISR 파라미터 블록(isrpb_t)입니다.
이 블록은 구성 플래그, 런타임 통계, 핸들러에 대한 포인터, 그리고 선택적인 컨텍스트‑해결 콜백을 저장합니다.

/* kernel_isrpb.h */
struct kernel_isrpb {
    ISR  handler;                                   /* ISR entry point */

    /* Runtime statistics */
    unsigned int avgTimeElapsed;    /* moving average of execution time (ticks) */
    unsigned int avgTrigPerSecond; /* average trigger frequency (Hz)          */
    unsigned int maxTimeElapsed;   /* longest observed execution time          */
    unsigned int minTimeElapsed;   /* shortest observed execution time         */

    unsigned int trigCount;        /* total number of triggers                 */
    unsigned int trigCountTmp;    /* temporary counter for frequency calc.   */
    unsigned int completeCount;   /* number of successful executions         */
    unsigned int trigTimestamp;   /* timestamp of the last trigger            */
    unsigned int droppedCount;     /* how many times the ISR was dropped      */

    unsigned int flags;            /* configuration flags (see below)         */

    /* Optional extra context resolver */
    void* (*additionalContextResolv)(struct kernel_isrpb* param);
} __attribute__((packed));

통합된 블록을 사용하면 모든 인터럽트를 일관되게 관리하고, 향후 기능을 확장할 수 있습니다.

Queue & Prioritisation

Non‑critical ISRs는 FIFO circular buffer에 넣어집니다.
이 큐는 ISR 플래그에서 파생된 three priority levels를 지원합니다:

/* Priority helper */
static inline unsigned int isr_getPriority(const isrpb_t *isr)
{
    if (isr->flags & IsrPriorityCrit)   return 0;          /* highest */
    return (isr->flags & IsrPriorityHigh) ? 1 : 2;      /* medium / low */
}

Auto‑take from the queue

스케줄러는 항상 준비된 가장 높은 우선순위 ISR을 선택합니다:

/* Return the next ISR, or NULL if the queue is empty */
isrpb_t* isr_queue_autotake(void)
{
    for (unsigned int priority = 0; priority trigCount++;
    isr->trigCountTmp++;
    isr->trigTimestamp = pit_get();          /* current timer tick */

    /* Re‑entrancy protection */
    if ((isr->flags & IsrActive) && !(isr->flags & IsrReentrant))
        return;                              /* already running – ignore */

    isr->flags |= IsrActive;                 /* mark as active */

    u32 start = 0;
    int collectStats = (isr->flags & IsrDoStats) && !(isr->flags & IsrPriorityCrit);
    if (collectStats) start = isr->trigTimestamp;

    /* Call the actual handler */
    ISR handler = isr->handler;
    handler(reg);

    /* Update statistics if requested */
    if (collectStats) isr_doStats(isr, start);

    isr->flags &= ~IsrActive;               /* clear active flag */
    isr->completeCount++;                   /* successful execution */
}

Queue processing & IRQ entry point

/* Process the next ISR from the queue */
static void isr_processQueue(void)
{
    isrpb_t *isr = isr_queue_autotake();
    if (isr) isr_dispatch(isr, NULL, 1);
}

/* Central IRQ handler (called by the CPU on every interrupt) */
void isr_irqHandler(REGISTERS *reg)
{
    /* ... decode the interrupt, locate the matching ISR block ... */
    unsigned int priority = isr_getPriority(isr);

    /* Critical ISR or empty queue → run immediately */
    if (isr_canRunNow(priority))
        goto run_now;

    /* Otherwise enqueue */
    isr_queue_push(isr);
    return;

run_now:
    isr_dispatch(isr, reg, 1);
    isr_processQueue();    /* keep the system responsive */
}

Critical ISRs(예: PIT)는 큐를 우회하고 즉시 실행되어 가능한 가장 짧은 지연 시간을 보장합니다.

Source:

통계 수집

각 ISR에 대해 작은 런타임 메트릭 집합을 유지합니다.
droppedCount를 제외한 모든 통계는 비‑중요 ISR에 대해서만, 그리고 IsrDoStats 플래그가 설정된 경우에만 업데이트됩니다.

실행 시간 통계

/* Called from isr_doStats() after an ISR finishes */
static void isr_updateTimeStats(isrpb_t *isr, u32 elapsed)
{
    if (isr->completeCount > 0) {
        /* Moving average */
        isr->avgTimeElapsed = ((isr->avgTimeElapsed * isr->completeCount) + elapsed)
                               / (isr->completeCount + 1);

        if (elapsed > isr->maxTimeElapsed) isr->maxTimeElapsed = elapsed;
        if (elapsed minTimeElapsed) isr->minTimeElapsed = elapsed;
    } else {
        /* First measurement */
        isr->avgTimeElapsed = isr->minTimeElapsed = isr->maxTimeElapsed = elapsed;
    }
}

드롭 카운트 처리

원형 버퍼가 가득 차면 ISR을 큐에 넣을 수 없으며 드롭된 것으로 카운트됩니다:

/* Inside isr_queue_push() */
if (next == queue->head) {
    /* Queue full – overload condition */
    isr->droppedCount++;
    return;
}

트리거 빈도 통계

/* Update average trigger frequency (Hz) */
static void isr_updateFreqStats(isrpb_t *isr, u32 now)
{
    u32 elapsed = now - isr->trigTimestamp;
    if (elapsed == 0) elapsed = 1;               /* avoid division by zero */

    if (elapsed >= PIT_FREQUENCY) {
        isr->avgTrigPerSecond = (isr->trigCountTmp * PIT_FREQUENCY) / elapsed;
        isr->trigCountTmp = 0;                    /* reset temporary counter */
    }
}

데이터가 알려주는 것

MetricInsight
avgTimeElapsed / min / max어떤 ISR이 가장 많은 CPU 시간을 소비하는지, 그리고 변동 폭이 얼마나 되는지.
avgTrigPerSecond각 인터럽트의 발생 빈도 – runaway 타이머를 찾는 데 유용.
trigCount vs. completeCount차이는 ISR에 진입했지만 끝까지 수행되지 않은 횟수를 나타냄(예: 선점됨).
droppedCount과부하 상황 – 큐 크기를 정하거나 ISR 지연 시간을 최적화하는 데 도움.

이 숫자들을 분석하면 “핫” 인터럽트를 식별하고, 우선순위를 조정하며, 커널을 결정론적 실시간 동작에 맞게 튜닝할 수 있습니다.

요약

  • 단일, 패킹된 ISR 파라미터 블록 (isrpb_t)이 모든 설정 및 런타임 데이터를 저장합니다.
  • 3단계 우선순위 (critical → high → low)는 즉시 실행과 큐 정렬을 모두 제어합니다.
  • 원형 FIFO 큐는 우선순위를 고려하면서 시스템을 반응성 있게 유지합니다.
  • 디스패치 로직은 통계를 업데이트하고 재진입을 방지하며 IsrDoStats 플래그를 준수합니다.
  • 통계(실행 시간, 빈도, 드롭 카운트)는 인터럽트 동작에 대한 깊은 통찰을 제공하여 데이터 기반 최적화를 가능하게 합니다.

이 기반을 통해 견고하고 확장 가능한 인터럽트 서브시스템을 확보하게 되며, 커널을 가볍고 유지보수하기 쉬운 상태로 유지하면서도 (중첩 인터럽트, CPU별 큐 등) 보다 고급 기능을 추가할 수 있습니다.

개요

패치와 등록 메커니즘이 구현되었을 때, 시스템은 원활하게 실행되었습니다.
아직 멀티태스킹을 구현하지 않았지만, 이 설계는 커널이 다음을 할 수 있게 합니다:

  • 예측 가능하고 측정 가능한 동작을 유지한다.
  • 디바이스 드라이버 디버깅을 더 쉽게 만든다.

인터럽트 동작을 더 잘 이해하고 디버깅하기 위해, 커널에 IRQ Lookup 유틸리티를 추가했습니다.

Source:

사용법

1. 등록된 모든 인터럽트 핸들러 목록 보기

irqlookup          # no arguments

이 유틸리티는 등록된 모든 인터럽트 핸들러를 순회하면서 각 ISR에 대한 핵심 정보를 다음 형식으로 출력합니다:

IRQ_BASE+ -> (Handler address (hex)) (Handler function symbol name):
    -> avgTps=X avgTE=X minTE=X maxTE=X, lastTrig=X cc=X tc=X dc=X flags=X
  • avgTps – 초당 평균 트리거 수
  • avgTE – 평균 경과 시간 (µs)
  • minTE / maxTE – 최소 / 최대 경과 시간 (µs)
  • lastTrig – 마지막 트리거의 타임스탬프
  • cc – 확인 횟수
  • tc – 완료 횟수
  • dc – 드롭 횟수
  • flags – 플래그 비트 (정수 형태 그대로 표시)

2. 특정 IRQ에 대한 상세 정보 표시

irqlookup --show-irq <IRQ_NUMBER>

이 명령은 ISR 파라미터 블록의 상세 요약을 출력합니다. 포함 내용:

필드설명
IRQ no.인터럽트 번호
Handler address & symbol주소 (hex)와 함수 이름
Context‑resolution function address & symbol추가 컨텍스트를 해결하는 헬퍼 함수의 주소 (hex)와 이름
Stats
– acknowledge count (trigCount)IRQ가 발생한 횟수
– complete count (completeCount)ISR이 정상적으로 완료된 횟수
– drop count (dropCount)ISR이 중단된 횟수
– last timestamp가장 최근 트리거 시점
– average / minimal / maximal time elapsedµs 단위로 측정
– total time elapsed (time_tot)avgTimeElapsed * trigCount (u64)
– average frequency전체 수명 동안 평균 초당 트리거 수
– flags인간이 읽을 수 있는 플래그 이름 (Flag decoding 아래 참고)

총 경과 시간 계산

u64 time_tot = ((p->flags & IsrDoStats) && !(p->flags & IsrPriorityCrit) && p->avgTimeElapsed != 0)
               ? (p->completeCount * p->avgTimeElapsed)
               : 0;

3. 원시 플래그 정수 디코딩

irqlookup --decode-flags <FLAGS_INTEGER>

이 유틸리티는 정수를 공백으로 구분된 플래그 이름 목록으로 변환합니다.

플래그‑이름 헬퍼

const char* flagName(unsigned int bit) {
    switch (bit) {
        case IsrActive:      return "IsrActive";
        // …
        case IsrWakeup:      return "IsrWakeup";
        default:             return "Unknown";
    }
}

설정된 플래그 출력

int first = 1;
for (unsigned int bit = 1; bit != 0; bit flags & bit) {
        if (!first) callback_stdout("\n                         ");
        callback_stdout((char*)flagName(bit));
        first = 0;
    }
}

Design Rationale

Mastering interrupt handling in a kernel isn’t just about making ISRs run; it’s also about observing, controlling, and prioritizing them effectively.

  • Queue‑based priority system – Guarantees that critical interrupts are serviced immediately, while less critical ones wait their turn, preserving system stability and predictability.
  • IRQ Lookup utility – Provides real‑time visibility into the interrupt subsystem, enabling developers to:
    • Inspect which ISRs are registered and active.
    • Monitor execution statistics (latency, frequency, counts).
    • Verify flags and handler states to ensure correct behavior.

Together, these mechanisms deliver both robustness and transparency for kernel developers.

Back to Blog

관련 글

더 보기 »