RTOS 인터럽트와 ISR 설계: 실시간 응답을 지키는 처리 원칙

RTOS 인터럽트의 처리 흐름과 ISR 설계, 우선순위·마스킹·레이턴시 관리 방법을 실시간 시스템 관점에서 정리한다.

2026-08-14 · 최초 발행 2026-01-12

인터럽트는 태스크 실행을 끊고 이벤트를 먼저 처리한다

RTOS에서 인터럽트는 외부 신호나 내부 이벤트가 발생했을 때 현재 실행 중인 작업을 멈추고 인터럽트 서비스 루틴(ISR)을 실행하는 메커니즘이다. 예측할 수 없는 시점의 이벤트를 처리하며, 대개 태스크보다 높은 우선순위로 실행된다.

실시간 시스템에서는 ISR의 실행 시간이 짧고 예측 가능해야 한다. 그래야 인터럽트 처리 지연도 결정론적으로 다룰 수 있다.

하드웨어·소프트웨어 이벤트와 우선순위

하드웨어 인터럽트는 GPIO 상태 변화나 센서 신호처럼 외부에서 들어오는 이벤트, 시스템 틱과 타임아웃을 위한 타이머 이벤트, UART·SPI·I2C 수신, 메모리 전송 완료를 알리는 DMA 이벤트 등을 포함한다.

하드웨어 인터럽트외부 인터럽트타이머 인터럽트DMA 인터럽트GPIO 변화주기적 이벤트데이터 전송 완료

소프트웨어 인터럽트의 범주에는 운영체제 서비스 요청을 위한 시스템 콜, 0으로 나누기 같은 오류 조건의 예외(Exception), 의도적으로 발생시키는 트랩(Trap), 명시적 인터럽트인 SWI(Software Interrupt)가 있다.

우선순위 인터럽트 유형 예시
최고 NMI(Non-Maskable Interrupt) 전원 실패, 하드웨어 오류
높음 시간 결정적 인터럽트 실시간 제어 신호
중간 통신 인터럽트 네트워크 패킷 수신
낮음 일반 주변장치 키보드, 마우스

신호 수신부터 태스크 전환까지

CPU는 인터럽트 신호를 인지하면 현재 명령을 마친 뒤 컨텍스트를 저장하고, 인터럽트 벡터가 가리키는 ISR로 제어를 넘긴다. ISR은 필요한 최소 작업을 수행한 다음 종료한다. ISR이 태스크를 깨웠다면 스케줄러가 다시 호출되어, 중단됐던 태스크를 재개하거나 다른 태스크로 전환할 수 있다.

태스크RTOSISRCPU하드웨어태스크RTOSISRCPU하드웨어태스크 실행 중인터럽트 신호컨텍스트 저장ISR 진입인터럽트 처리태스크 깨우기 (선택적)ISR 종료스케줄러 호출태스크 재개 또는 전환

인터럽트 지연에는 하드웨어가 신호를 인식하는 시간, 크리티컬 섹션에서 인터럽트가 비활성화된 시간, 컨텍스트를 저장하는 ISR 프롤로그, 더 높은 우선순위 인터럽트의 처리 대기, 실제 ISR 실행 시간이 관여한다.

인터럽트 발생인터럽트 지연ISR 실행태스크 응답하드웨어 지연소프트웨어 지연

ISR은 신호를 전달하고, 무거운 처리는 태스크가 맡는다

ISR 안에서는 최소한의 처리만 하고, 대기나 지연 함수처럼 블로킹될 수 있는 호출은 피해야 한다. 공유 자원은 여러 인터럽트 경로에서 안전해야 하며, 실행 시간도 예측 가능해야 한다.

세마포어 획득 대기, delay()·sleep() 호출, 긴 계산이나 알고리즘, printf, malloc()·free() 같은 동적 할당은 ISR에 넣기 적합하지 않다. 콘솔 출력은 시간이 걸리고 동적 메모리 할당은 비결정적일 수 있다.

하프 앤 바텀 처리에서는 Top Half인 ISR이 플래그 설정이나 데이터 버퍼링처럼 긴급한 작업만 수행한다. 복잡한 처리는 Bottom Half인 태스크가 맡으며, 세마포어나 큐로 태스크에 신호를 보낸다.

인터럽트 발생Top Half(ISR)플래그 설정데이터 버퍼링Bottom Half(태스크)복잡한 처리
// 하드웨어 인터럽트 발생 시 호출되는 ISR
void UART_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    char receivedChar;

    // 데이터 읽기 (Top Half)
    receivedChar = UART_ReadData();

    // 큐에 데이터 전송 (태스크 깨우기)
    xQueueSendFromISR(uart_queue, &receivedChar,
                      &xHigherPriorityTaskWoken);

    // 인터럽트 플래그 클리어
    UART_ClearInterruptFlag();

    // 필요 시 컨텍스트 스위칭
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// 데이터 처리 태스크 (Bottom Half)
void uart_processing_task(void *pvParameters) {
    char data;

    while(1) {
        // 큐에서 데이터 대기
        if(xQueueReceive(uart_queue, &data, portMAX_DELAY)) {
            // 복잡한 데이터 처리
            process_received_data(data);
        }
    }
}

중첩·역전·마스킹을 함께 다룬다

높은 우선순위 인터럽트는 낮은 우선순위 ISR을 중단할 수 있다. RTOS는 보통 이런 중첩을 지원하지만, 중첩 깊이에 따른 스택 오버플로와 최대 중첩 레벨은 설계에서 관리해야 한다.

태스크 실행낮은 우선순위 ISR높은 우선순위 ISR높은 우선순위 ISR 완료낮은 우선순위 ISR 재개태스크 재개

우선순위 역전은 높은 우선순위 태스크가 낮은 우선순위 태스크가 가진 자원을 기다릴 때 발생한다. Priority Inheritance와 Priority Ceiling이 대응 방법이며, 대부분의 RTOS는 우선순위 상속을 제공한다. ISR은 일반적으로 모든 태스크보다 높은 우선순위를 가진다.

기법 설명 사용 시나리오
전역 비활성화 모든 인터럽트 차단 극히 짧은 크리티컬 섹션
우선순위 마스킹 특정 우선순위 이하 차단 선택적 인터럽트 제어
개별 마스킹 특정 인터럽트만 차단 세밀한 제어 필요 시
// ARM Cortex-M: 전역 인터럽트 비활성화/활성화
__disable_irq();
// 크리티컬 섹션
shared_variable++;
__enable_irq();

// FreeRTOS: 태스크 레벨 크리티컬 섹션
taskENTER_CRITICAL();
// 보호된 코드
taskEXIT_CRITICAL();

RTOS마다 제공하는 ISR 연계 방식

FreeRTOS는 ISR에서 안전하게 호출할 수 있는 FromISR API, configMAX_SYSCALL_INTERRUPT_PRIORITY를 통한 임계 우선순위 설정, portYIELD_FROM_ISR의 컨텍스트 스위칭 요청 방식을 제공한다. 경량 ISR로 오버헤드를 줄이는 것이 전제다.

VxWorks에는 최소 지연 처리를 위한 Fast Interrupt, C++ 기반으로 구현할 수 있는 ISR 객체, 동적 등록·해제가 가능한 인터럽트 벡터 테이블, ISR과 태스크 컨텍스트를 분리하는 이중 컨텍스트가 있다.

QNX Neutrino의 인터럽트 핸들러는 커널 모드에서 동작한다. 인터럽트 이벤트를 태스크에 전달하고 자동 우선순위 관리를 수행하며, ISR에서 태스크로 펄스 메시지를 보낼 수 있다.

Zephyr RTOS에서는 인터럽트를 등록하고 활성화한 뒤 ISR에서 세마포어로 태스크를 깨울 수 있다.

// Zephyr 인터럽트 등록
IRQ_CONNECT(IRQ_NUM, IRQ_PRIORITY, isr_handler, NULL, 0);
irq_enable(IRQ_NUM);

// ISR 구현
void isr_handler(void *arg) {
    // 인터럽트 처리
    k_sem_give(&my_sem);  // 세마포어로 태스크 깨우기
}

레이턴시와 WCET를 검증하는 방법

인터럽트 지연을 줄이려면 ISR에서 불필요한 코드를 제거하고 인라인 함수를 사용하며, 자주 사용하는 ISR 코드를 캐시에 배치할 수 있다. 인터럽트 비활성화 구간은 줄이고, 중요한 인터럽트에는 높은 우선순위를 부여한다.

WCET 분석에서는 ISR 실행 시간, 인터럽트 중첩, 인터럽트 비활성화 시간을 함께 본다. 코드 경로를 분석해 최대 시간을 계산하는 정적 분석과 타이머로 실제 시간을 재는 실측 분석을 병행할 수 있다. 예상 시간보다 여유 있게 설계하고, 코드가 바뀔 때마다 다시 측정한다.

WCET 분석ISR 실행 시간인터럽트 중첩비활성화 시간실시간 보장
// 인터럽트 레이턴시 측정 예제
volatile uint32_t interrupt_timestamp;
volatile uint32_t latency;

void GPIO_IRQHandler(void) {
    uint32_t current_time = TIMER_GetCount();

    // 외부 신호 발생 시각과 현재 시각 차이
    latency = current_time - interrupt_timestamp;

    // 인터럽트 처리
    handle_interrupt();

    GPIO_ClearInterruptFlag();
}

문제를 관측하고 진단한다

ISR에서의 블로킹과 긴 연산, 중첩 인터럽트에 따른 스택 부족, ISR과 태스크가 공유하는 자원의 경쟁 조건, 빠르게 연속 발생하는 인터럽트 폭주는 대표적인 문제다.

오실로스코프는 인터럽트 타이밍을 시각화하고, 로직 분석기는 여러 신호를 동시에 분석한다. RTOS 추적 도구로는 SystemView와 Tracealyzer가 있으며, JTAG 디버거는 실시간 중단점과 변수 확인에 사용한다.

초기 확인에는 ISR에서 LED를 토글하거나 호출 카운터를 증가시키는 방법을 쓸 수 있다. 발생 시각을 타임스탬프로 기록하고, 이벤트는 순환 로그 버퍼에 남기면 흐름을 추적하기 쉽다.

처리량과 지연 요구에 따라 방식을 고른다

인터럽트 통합(Coalescing)은 여러 이벤트를 모아 한 번에 처리하는 방식이다. ISR 진입과 종료 횟수를 줄이고 데이터를 배치로 처리할 수 있지만, 레이턴시보다 처리량을 중시하는 경우에 맞는다.

항목 폴링 인터럽트
CPU 사용률 높음 (지속 확인) 낮음 (필요 시만)
응답 시간 느림 (폴링 주기 의존) 빠름 (즉각 응답)
구현 복잡도 낮음 높음
적용 빠른 이벤트, 여러 소스 느린 이벤트, 낮은 빈도

DMA는 데이터 전송을 맡아 CPU 부하를 낮추고, 전송이 끝나면 인터럽트로 완료를 알릴 수 있다. 따라서 CPU는 다른 작업을 수행하면서 대용량 데이터를 효율적으로 전송할 수 있다.

DMA 전송CPU 비개입전송 완료DMA 인터럽트ISR에서 처리

안정적인 인터럽트 경로를 위한 기준

Top Half는 극도로 짧게 유지하고, 복잡한 처리는 Bottom Half 태스크에 위임한다. 중요도와 긴급도에 맞춰 우선순위를 정하고, 크리티컬 섹션과 세마포어로 공유 자원을 보호하며, ISR이 여러 번 호출되어도 안전하도록 재진입성을 고려한다.

성능 측면에서는 인라인 함수로 함수 호출 오버헤드를 줄이고, 빠른 접근을 위해 레지스터 변수를 활용할 수 있다. ISR 내부의 반복문은 지양하고 컴파일러 최적화 옵션인 -O2, -O3도 검토 대상이다.

ISR 실행 시간은 타임아웃으로 모니터링하고, 워치독으로 멈춤을 감지해 복구한다. 예외 상황을 처리할 수 있도록 준비하며, 경계 조건과 스트레스 테스트까지 포함해 검증한다.

RTOS인터럽트ISR실시간 시스템임베디드