본문 바로가기

# RTEMS

RTEMS 'rtems_rate_monotonic_period'는 정말 "주기적"일까?

728x90
반응형

파헤치기: RTEMS rtems_rate_monotonic_period는 정말 "주기적"일까?

실시간 운영체제(RTOS)에서 주기적인 태스크를 다루는 것은 시스템의 심장과도 같습니다. 우리는 rtems_rate_monotonic_period(100)을 호출하면, 태스크가 t=0, 100, 200...이라는 절대적인 격자(Grid)에 맞춰 깨어날 것이라 기대합니다.

하지만 GR740 시뮬레이터에서 일련의 정밀한 실험을 수행한 결과, 이 믿음이 틀렸음을 발견했습니다.

이 글은 rtems_rate_monotonic_period가 실제로는 어떻게 동작하는지, 이 함수의 핵심 메커니즘인 "롤링 앵커(Rolling Anchor)" 모델을 완벽하게 분석하고, 이를 이용해 여러 태스크를 동기화하는 영리한 방법까지 소개합니다.

period()에 대한 가장 큰 오해

period() 함수는 "공용 벽시계"가 아닙니다. 이 함수는 태스크마다 할당된 "개인 알람시계" 처럼 동작합니다.

  • 틀린 생각: period(100)을 호출하면, t=100, 200...에 울리는 공용 알람에 등록된다.
  • 진짜 동작: "내 개인 알람시계의 다음 알람 시각을 이전 알람 시각 + 100으로 갱신해 줘"라고 요청하는 것과 같습니다.

이 "개인 알람시계"의 다음 알람 시각이 바로, 우리가 LRT (Last Release Time) 이라고 부르는 "롤링 앵커" 의 실체입니다.

period() 함수의 3단계 동작 원리

period_id 객체는 이 LRT 값 하나만 기억합니다. 그리고 이 LRT 값은 매번, 성공/실패 여부와 관계없이 갱신됩니다.

1. 최초 호출 (i=0): 알람을 "미래"에 설정하기

태스크가 period()를 생애 처음 호출할 때의 동작은 아주 특별합니다.

  • 동작:
    1. LRT = 현재 시각(now) + 방금 입력한 주기 값(period)
    2. RTEMS_SUCCESSFUL (OK)을 즉시 반환합니다. (절대 대기하지 않습니다!)
  • 실험 증거 ([0] success: 0 로그):
    • t=1period(9999)를 호출.
    • LRT1 + 9999 = 10000으로 설정됩니다.
    • 함수는 즉시 OK를 반환하고, start_time1로 기록됩니다.

2. 이후 호출 (SUCCESS): 알람 대기

태스크가 LRT 시각보다 일찍 도착한 경우입니다.

  • 조건: 현재 시각(now) < LRT
  • 동작:
    1. LRT 시각이 될 때까지 태스크를 대기(Block)시킵니다.
    2. 태스크가 깨어나면, LRT 값을 이전 LRT + period갱신(Rolling)합니다.
  • 실험 증거 ([9] 로그):
    • t=85period(10) 호출. (이전 LRT는 81)
    • LRT 읽기 (값: 81).
    • 새로운 데드라인 = 81 + 10 = 91.
    • now(85) < 91이므로 OK. t=91까지 6틱(1틱 오차) 동안 대기.
    • LRT91로 갱신됨.
    • start_time[9]90으로 기록됩니다.

3. 이후 호출 (MISS): 지연 누적

태스크가 LRT 시각보다 늦게 도착한 경우입니다.

  • 조건: 현재 시각(now) >= LRT
  • 동작:
    1. RTEMS_TIMEOUT (MISS)을 즉시 반환합니다. (대기 시간 0)
    2. LRT 값은 이전 LRT + period똑같이 갱신(Rolling)됩니다.
  • 실험 증거 ([1] 로그):
    • t=12period(10) 호출. (이전 LRT는 1)
    • LRT 읽기 (값: 1).
    • 새로운 데드라인 = 1 + 10 = 11.
    • [갱신] LRT11로 덮어써집니다.
    • [판정] now(12) >= 11이므로 MISS. 즉시 반환.
    • start_time[1]12로 기록됩니다.

이 "MISS가 나도 앵커가 굴러가는" 특성 때문에, 작업 시간($C$)이 주기($T$)보다 길면(C=11, T=10), LRT1, 11, 21, 31...로 계속 굴러가고, now1, 12, 23, 34...로 더 빨리 증가하며 지연(diff)이 영원히 누적됩니다.


💡 응용: 12개의 태스크를 '동시'에 시작시키는 법

이 "롤링 앵커" 모델은 태스크를 동기화하기 어렵게 만듭니다. T1이 t=1period(100)을 호출하면 T1의 격자는 101, 201...이 되고, T2가 t=2에 호출하면 102, 202...가 되어 영원히 어긋납니다.

하지만 우리는 "최초 호출" 의 특별한 동작을 역이용하여 모든 태스크를 t=10000이라는 절대 격자에 정렬시킬 수 있습니다.

솔루션: 첫 번째 주기를 "미래"로 설정하기

12개의 모든 태스크(T1~T12)가 아래와 같은 로직을 수행하도록 합니다.

```c
#define SYNC_TIME 10000
#define PERIOD 100

static void synced_task(rtems_task_argument arg) {
rtems_status_code sc;
rtems_id period_id;
sc = rtems_rate_monotonic_create(..., &period_id);

// --- 1. 동기화 (최초 호출) ---
// (예: t=10에 도착)
// first_period = 10000 - 10 = 9990
int first_period = SYNC_TIME - rtems_clock_get_ticks_since_boot();

// 1. 최초 호출: 
// LRT = now(10) + period(9990) = 10000
// 즉시 OK 반환.
sc = rtems_rate_monotonic_period(period_id, first_period);

// --- 2. 동기화된 메인 루프 ---
for (int i = 0; ; i++) {
    // 2. 두 번째 호출 (i=0)
    // LRT(10000)을 읽음. 
    // 현재 시각(now)은 10000보다 한참 전이므로 OK.
    // t=10000이 될 때까지 여기서 잠든다.
    // 깨어난 후 LRT는 10000 + 100 = 10100으로 갱신됨.
    sc = rtems_rate_monotonic_period(period_id, PERIOD);

    // --- (t=10000 땡!) ---
    // 12개 태스크가 모두 여기서 깨어남

    // 3. 실제 작업 수행
    busy_work(ctx->execution_time);

    // 4. 세 번째 호출 (i=1)
    // LRT(10100)을 읽음.
    // t=10100까지 잠든다.
    // 깨어난 후 LRT는 10100 + 100 = 10200으로 갱신됨.
}

}

728x90
반응형