커널에서 인터럽트 처리 마스터하기
Source: Dev.to
번역을 진행하려면 번역하고자 하는 전체 텍스트를 제공해 주시겠어요? 텍스트를 주시면 원본 형식과 마크다운을 유지하면서 한국어로 번역해 드리겠습니다.
소개
커널을 처음부터 만드는 것은 한 가지 일입니다. 그 인터럽트 시스템을 마스터하는 것은 또 다른 일입니다. 이 기사에서는 맞춤형 커널을 위한 완전한 기능을 갖춘 인터럽트 처리 서브시스템의 설계, 구현, 그리고 미세 조정을 단계별로 살펴볼 것입니다 – 큐, 우선순위, 통계 수집을 모두 포함하며, 외부 멀티태스킹 프레임워크에 의존하지 않습니다.
주요 목표
| # | 목표 |
|---|---|
| 1 | 중요한 ISR(예: PIT)의 즉시 실행 |
| 2 | 비중요한 ISR에 대한 유연한 큐잉 및 우선순위 지정 |
| 3 | 인터럽트 관련 통계(처리 시간, 트리거 빈도, 드롭 수) |
| 4 | ISR 등록 및 처리를 위한 간단하고 유지보수 가능한 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 */
}
}
데이터가 알려주는 것
| Metric | Insight |
|---|---|
| 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.