본문으로 건너뛰기
Practical RTOS Internals · 18/53

RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ

· Hawk · 4분 읽기

#한 줄 요약

Tick은 RTOS의 심장 박동에 해당합니다. 주기적인 IRQ가 scheduler, timeout, preemption의 토대가 됩니다.

#Cortex-M SysTick

Cortex-M에는 24-bit down-counter가 내장되어 있습니다. 대부분의 RTOS가 이를 기본 tick source로 사용합니다.

FreeRTOSConfig.h
#define configTICK_RATE_HZ ((TickType_t) 1000) // 1 kHz
#define configCPU_CLOCK_HZ ((unsigned long) 168000000) // 168 MHz

내부 구현은 다음과 같습니다.

void vPortSetupTimerInterrupt(void) {
/* SysTick 설정 */
portNVIC_SYSTICK_LOAD_REG = (configCPU_CLOCK_HZ / configTICK_RATE_HZ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = (portNVIC_SYSTICK_CLK_BIT |
portNVIC_SYSTICK_INT_BIT |
portNVIC_SYSTICK_ENABLE_BIT);
}

168 MHz를 1 kHz로 나누면 168,000 cycle입니다. 이 카운트마다 SysTick 인터럽트가 한 번씩 발생합니다.

#SysTick Handler

void xPortSysTickHandler(void) {
vPortRaiseBASEPRI(); // IRQ mask
if (xTaskIncrementTick() != pdFALSE) {
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; // preempt 필요
}
vPortClearBASEPRIFromISR();
}

xTaskIncrementTick()이 하는 일은 다음과 같습니다.

  • xTickCount를 1 증가시킵니다.
  • delayed list에서 만료된 task를 wake해 ready로 옮깁니다.
  • 같은 priority 안에서 round-robin rotation을 진행합니다.
  • preemption이 필요하면 pdTRUE를 반환합니다.

#Tick Rate Trade-off

RatePeriod장단점
100 Hz10 ms저전력, 낮은 정밀도
500 Hz2 ms균형
1 kHz1 ms표준 — 대부분 RTOS 기본
10 kHz100 µs정밀, ISR overhead 큼

vTaskDelay(1)은 1 tick 뒤에 wake합니다. tick rate에 따라 실제 시간이 달라지므로, 100 Hz면 10 ms, 1 kHz면 1 ms로 의미가 달라집니다.

#Tick Overhead

SysTick ISR @ 1 kHz:
- HW enter: 12 cycle
- BASEPRI set: 2 cycle
- xTaskIncrementTick: 100-500 cycle (delayed list 처리에 따라)
- BASEPRI clear: 2 cycle
- HW exit: 6 cycle (tail-chain)
Total ≈ 120-520 cycle/tick
@ 168 MHz: 0.7-3 µs/tick = 0.07-0.3% CPU

부담이 작은 편입니다. 10 kHz로 올려도 0.7~3% CPU 정도라 여전히 견딜 만합니다.

#Cortex-A Generic Timer

Cortex-A에는 SysTick이 없습니다. 대신 Generic Timer(ARM Architecture Timer)를 제공합니다.

// CNTFRQ_EL0 — 시스템 frequency (보통 24 MHz)
// CNTPCT_EL0 — 64-bit physical counter (count up)
// CNTP_CVAL_EL0 — compare value (interrupt trigger)
uint64_t now = read_cntpct();
uint64_t deadline = now + ticks_per_ms;
write_cntp_cval(deadline);

64-bit counter라 wraparound가 사실상 일어나지 않습니다. 24 MHz로 돌려도 64-bit를 다 채우려면 약 24,000년이 걸립니다.

#RISC-V — mtime / mtimecmp

// PLIC·CLINT의 mtime memory-mapped
#define MTIME (*(volatile uint64_t *)0x0200BFF8)
#define MTIMECMP (*(volatile uint64_t *)0x02004000)
// Tick 설정
MTIMECMP = MTIME + (FREQ_HZ / TICK_HZ);

mtime은 64-bit입니다. mtimecmp ≤ mtime 조건이 성립하면 mip.MTIP가 set되어 interrupt가 pending됩니다. wraparound는 사실상 없습니다.

#Time Slicing

configUSE_TIME_SLICING = 1(기본값)이면 매 tick마다 같은 priority 안에서 round-robin이 발생합니다. 다른 priority에는 영향을 주지 않습니다.

// vTaskSwitchContext
if (uxTopReadyPriority == pxCurrentTCB->uxPriority) {
// 다른 same-priority task가 있으면 yield
listGET_OWNER_OF_NEXT_ENTRY(...);
}

configUSE_TIME_SLICING = 0으로 두면 같은 priority의 task끼리는 명시적으로 yield할 때만 전환됩니다.

#vTaskDelay vs vTaskDelayUntil

// Relative delay
vTaskDelay(100); // 지금부터 100 tick 대기
// Absolute periodic delay
TickType_t xLastWake = xTaskGetTickCount();
while (1) {
do_work();
vTaskDelayUntil(&xLastWake, 100); // 정확한 100 tick 주기
}

vTaskDelayUntil은 주기를 정확히 유지합니다. do_work()가 50 ms를 잡아먹어도 다음 wake는 여전히 xLastWake + 100 tick입니다.

#Tickless Idle

configUSE_TICKLESS_IDLE = 1을 켜면 다음과 같이 동작합니다.

Idle 동안 SysTick을 멈춰 두고 CPU를 더 오래 sleep시킵니다. 깨어날 때 그동안 놓친 tick을 한 번에 보충합니다.

void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime) {
/* SysTick 다음 wake까지 reprogram */
uint32_t ulReloadValue = ulTimerCountsForOneTick * xExpectedIdleTime;
portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
__WFI(); /* sleep */
/* 깨어났을 때 — 실제 경과 tick 보충 */
TickType_t ulCompleteTickPeriods =
(configSYSTICK_CLOCK_HZ - portNVIC_SYSTICK_CURRENT_VALUE_REG)
/ ulTimerCountsForOneTick;
vTaskStepTick(ulCompleteTickPeriods);
}

5년 이상 동작해야 하는 배터리 기반 IoT 디바이스의 핵심 기술입니다.

#Tick 정확도 — Drift

내부 RC oscillator(HSI)는 ±1~2%의 drift를 보입니다. 외부 crystal(HSE)은 ±50 ppm 수준으로 훨씬 안정적입니다.

HSI @ 168 MHz, ±1.5%:
1 day = 86400 sec × 0.015 = 1296 sec drift = 21분 오차
HSE @ 168 MHz, ±50 ppm:
1 day × 0.005% = 4.3 sec drift

장시간 동작하거나 NTP, time-stamp처럼 동기화에 민감한 시스템에는 외부 crystal이 필수입니다.

#RTC와의 차이

RTC (Real-Time Clock)RTOS Tick
용도Wall clock (date, time)Scheduler·timeout
정밀도초 단위µs 단위
저전력 모드동작 (battery)멈춤 (tickless 제외)
정확도32 kHz crystal시스템 클럭

대부분의 시스템은 RTC와 Tick을 동시에 사용합니다. RTC가 시간을 추적하고, Tick이 제어를 담당합니다.

#자주 하는 실수

⚠️ Tick rate를 임의로 가정합니다

vTaskDelay(10)을 10 ms라고 가정하기 쉽지만 실제로는 configTICK_RATE_HZ에 따라 달라집니다. pdMS_TO_TICKS() 매크로로 명시 변환을 거쳐야 합니다.

vTaskDelay(pdMS_TO_TICKS(10)); // 명시적

⚠️ Tickless에서 wake source를 빠뜨립니다

WFI 이후 깨울 수단이 SysTick뿐인데 그것까지 꺼 버리면 영원히 sleep 상태에 머뭅니다. RTC alarm이나 GPIO IRQ 같은 다른 wake source를 반드시 마련해 둬야 합니다.

⚠️ HSI로 장시간 sync를 시도합니다

±1% drift만으로도 하루에 14분이 어긋납니다. 장시간 동기화가 필요하다면 HSE가 필수입니다.

⚠️ vTaskDelay 대신 busy-wait를 씁니다

짧은 delay라도 busy-wait를 쓰면 CPU가 100%에 머물러 다른 task가 굶게 됩니다. 항상 vTaskDelay를 사용해야 합니다.

#정리

  • Cortex-M의 SysTick, Cortex-A의 Generic Timer, RISC-V의 mtime이 tick source입니다.
  • 1 kHz가 RTOS의 표준 tick rate입니다.
  • Tick ISR overhead는 약 0.1% CPU 수준이라 부담이 작습니다.
  • Tickless mode가 배터리 동작의 핵심입니다.
  • pdMS_TO_TICKS()로 절대 시간을 명시해 tick rate 의존을 피합니다.

다음 편은 tickless 모드 구현을 자세히 다룹니다.

#관련 항목

Practical RTOS Internals · 19 of 53

  1. 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
  2. 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
  3. 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
  4. 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
  5. 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
  6. 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
  7. 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
  8. 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
  9. 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
  10. 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
  11. 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
  12. 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
  13. 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
  14. 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
  15. 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
  16. 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
  17. 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
  18. 18RISC-V Context Switch 분석 — ECALL·mret·CSR
  19. 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
  20. 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
  21. 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
  22. 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
  23. 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
  24. 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
  25. 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
  26. 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
  27. 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
  28. 28Priority Ceiling Protocol — Immediate vs Original 비교
  29. 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
  30. 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
  31. 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
  32. 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
  33. 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
  34. 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
  35. 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
  36. 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
  37. 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
  38. 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
  39. 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
  40. 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
  41. 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
  42. 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
  43. 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
  44. 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
  45. 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
  46. 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
  47. 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
  48. 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
  49. 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
  50. 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
  51. 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
  52. 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
  53. 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL