인터럽트 서비스 루틴(ISR, Interrupt Service Routine)은 실시간 시스템에서 중요한 역할을 한다. ISR은 특정 이벤트가 발생했을 때 실행되며, 이러한 이벤트는 보통 하드웨어 인터럽트로 인해 발생한다. Preempt RT와 같은 실시간 시스템에서 ISR의 설계 및 구현은 시스템의 전체적인 성능과 실시간성에 직접적인 영향을 미치므로, 주의 깊게 설계되어야 한다.

인터럽트 서비스 루틴의 개념

ISR은 하드웨어 장치나 다른 외부 이벤트에 의해 발생한 인터럽트 신호에 반응하여 실행되는 코드 블록이다. 일반적으로, ISR은 다음과 같은 작업을 수행한다:

ISR은 매우 빠르게 실행되어야 하며, 가능한 한 최소한의 작업만 수행해야 한다. 이는 ISR이 긴 시간 동안 실행되면 다른 중요한 실시간 작업이 지연될 수 있기 때문이다.

ISR의 기본 설계 원칙

  1. 간결성: ISR은 가능한 한 간결해야 한다. ISR 내에서 긴 작업을 수행하면 시스템의 실시간 성능이 저하될 수 있다.

  2. 상태 보호: ISR이 실행되는 동안 인터럽트가 비활성화될 수 있으므로, 중요한 시스템 상태가 손상되지 않도록 주의해야 한다. 필요에 따라 ISR에서는 특정 변수나 메모리 영역에 대해 원자적(atomic) 작업을 수행해야 한다.

  3. ISR과 스케줄링: Preempt RT에서는 ISR이 실행될 때 실시간 스케줄러가 작동하여 더 높은 우선순위의 작업이 즉시 실행될 수 있다. 이 경우 ISR이 너무 오래 실행되면, 실시간 스케줄러의 효과가 저하될 수 있다.

  4. 중복 방지: 동일한 인터럽트가 중첩되어 발생하는 것을 방지하기 위해, ISR이 실행 중일 때 해당 인터럽트를 일시적으로 비활성화할 수 있다.

ISR의 구현 절차

ISR을 구현하는 절차는 다음과 같다:

  1. 인터럽트 소스 식별 및 초기화: ISR이 처리할 인터럽트 소스를 식별하고, 해당 인터럽트를 활성화하기 위해 하드웨어 또는 소프트웨어적으로 초기화한다.

  2. ISR 등록: ISR은 특정 인터럽트 벡터에 등록된다. 이 과정에서는 해당 인터럽트 발생 시 실행될 함수를 시스템에 알려준다.

  3. ISR 구현: ISR 함수는 다음과 같은 구조로 작성된다:

    • 인터럽트 원인 판별
    • 필요한 데이터 수집 또는 처리
    • 후속 작업 트리거
    • 인터럽트 플래그 클리어 및 종료
void my_isr_handler(void) {
    // 인터럽트 원인 판별
    if (check_interrupt_source()) {
        // 데이터 수집 및 처리
        process_data();

        // 후속 작업 트리거
        trigger_next_task();

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

ISR의 시간 제한 및 실시간성

ISR의 설계에서 중요한 요소 중 하나는 시간 제한이다. ISR이 너무 오래 실행되면 실시간 시스템의 응답 시간이 길어져 실시간성을 보장할 수 없게 된다.

실시간 시스템에서 ISR의 실행 시간을 제어하는 방법으로는 다음과 같은 것들이 있다:

인터럽트 지연(Latency) 분석

실시간 시스템에서 인터럽트 지연(Latency)은 중요한 성능 지표이다. 인터럽트 지연은 인터럽트가 발생한 시점부터 ISR이 실제로 실행되기까지의 시간이다. 이 지연은 시스템의 다른 부하에 의해 발생할 수 있으며, 실시간 애플리케이션의 응답성을 결정짓는 중요한 요소이다.

인터럽트 지연은 다음과 같은 요소들에 의해 영향을 받을 수 있다:

  1. 인터럽트 우선순위: 시스템에서 발생하는 다양한 인터럽트는 서로 다른 우선순위를 갖는다. 우선순위가 높은 인터럽트가 낮은 우선순위의 ISR을 선점하게 되므로, 낮은 우선순위의 ISR은 더 긴 지연을 겪을 수 있다.

  2. 중첩된 인터럽트: 만약 시스템이 중첩된 인터럽트를 허용한다면, 실행 중인 ISR이 더 높은 우선순위의 인터럽트에 의해 중단될 수 있다. 이 경우 낮은 우선순위의 ISR은 재개될 때까지 지연을 겪게 된다.

  3. 커널의 인터럽트 처리 시간: 커널이 인터럽트를 처리하는 시간 자체도 지연을 발생시킬 수 있다. 예를 들어, 커널이 현재 다른 중요한 작업을 수행 중일 때 인터럽트 처리 시간이 늘어날 수 있다.

수학적 모델링

인터럽트 지연을 수학적으로 모델링할 수 있다. ISR의 지연 시간을 L이라고 하고, 다음과 같은 변수들을 고려해 볼 수 있다:

이를 통해 인터럽트 지연은 다음과 같이 정의할 수 있다:

L = T_{start} - T_{arrival}

지연을 최소화하기 위해서는 T_{delay}를 줄이는 것이 중요하다. 이를 위해서는 커널 수준에서의 최적화와 ISR 설계 최적화가 필요하다.

데이터 공유 및 동기화

ISR과 다른 스레드 간의 데이터 교환은 매우 주의 깊게 이루어져야 한다. 실시간 시스템에서 이러한 데이터 교환이 잘못되면 시스템이 불안정해질 수 있다. 일반적으로 ISR은 데이터를 전역 변수 또는 메모리 버퍼에 저장하고, 이를 실시간 스레드에서 읽습니다. 그러나 이 과정에서 동기화 문제가 발생할 수 있다.

데이터 경합 방지

ISR이 공유 자원에 접근하는 동안 다른 스레드가 같은 자원에 접근하려고 할 때 데이터 경합이 발생할 수 있다. 이를 방지하기 위해 다음과 같은 방법을 사용할 수 있다:

예제 코드

다음은 ISR과 실시간 스레드 간에 데이터를 안전하게 교환하는 간단한 예제이다:

volatile int shared_data = 0;
volatile bool data_ready = false;

void my_isr_handler(void) {
    shared_data = read_hardware_data();
    data_ready = true;  // 데이터가 준비되었음을 알림
}

void realtime_thread(void) {
    while (1) {
        if (data_ready) {
            process_data(shared_data);
            data_ready = false;  // 데이터 처리가 완료되었음을 알림
        }
        sleep_until_next_period();
    }
}

이 예제에서 shared_datadata_ready 변수는 volatile 키워드를 사용하여 컴파일러 최적화를 방지한다. 이로 인해 ISR과 실시간 스레드 간의 데이터 교환이 올바르게 이루어질 수 있다.

최적의 ISR 설계 전략

  1. 빠른 실행: ISR 내에서 가능한 한 적은 양의 코드를 실행하고, 복잡한 작업은 실시간 스레드로 넘깁니다.

  2. 효율적인 데이터 처리: ISR에서 중요한 데이터를 빠르게 처리하고, 나머지 작업은 후속 작업으로 처리한다.

  3. 선점 방지: ISR의 실행 중에는 가능한 한 다른 인터럽트가 발생하지 않도록 설계한다.

  4. 타이밍 제어: ISR의 실행 시간을 측정하고, 필요에 따라 최적화한다.

  5. 하드웨어 지원 활용: 현대의 많은 하드웨어는 ISR 실행을 지원하는 기능을 제공한다. 이러한 기능을 활용하여 성능을 최적화할 수 있다.