Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
#한 줄 요약
Scheduler가 하는 일은
pxCurrentTCB를 갱신하는 것뿐입니다. 나머지는 context switch가 알아서 처리합니다.
#Scheduler Entry Points
| 트리거 | 함수 |
|---|---|
| Tick ISR | vTaskSwitchContext() |
Yield (taskYIELD()) | PendSV → vTaskSwitchContext() |
Task block (vTaskDelay 등) | portYIELD() 경로 |
ISR이 task wake → portYIELD_FROM_ISR() | PendSV pending |
xTaskResumeAll() | direct |
핵심은 PendSV 예외가 실제 context switch를 수행한다는 점입니다. scheduler는 어떤 task로 갈지 결정만 합니다.
#FreeRTOS — vTaskSwitchContext()
void vTaskSwitchContext(void) { if (uxSchedulerSuspended) { xYieldPending = pdTRUE; return; } taskSELECT_HIGHEST_PRIORITY_TASK(); // pxCurrentTCB 갱신 traceTASK_SWITCHED_IN();}#Generic Selection (CLZ 없는 시스템)
#define taskSELECT_HIGHEST_PRIORITY_TASK() \{ \ UBaseType_t uxTopPriority = uxTopReadyPriority; \ while (listLIST_IS_EMPTY(&pxReadyTasksLists[uxTopPriority])) { \ --uxTopPriority; \ } \ listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, \ &pxReadyTasksLists[uxTopPriority]); \ uxTopReadyPriority = uxTopPriority; \}uxTopReadyPriority를 캐시로 활용합니다.- 빈 priority list를 만나면 한 단계씩 내려가며 검색합니다.
#Optimized Selection (Cortex-M, ARMv7-M+)
#define portGET_HIGHEST_PRIORITY(uxTopPriority, uxReadyPriorities) \ uxTopPriority = (31UL - (uint32_t) __clz((uxReadyPriorities)))CLZ(Count Leading Zeros)는 단일 cycle에 끝나므로 O(1)입니다. ARMv6-M(Cortex-M0)에는 CLZ가 없어서 generic mode로 떨어집니다.
#Round-Robin in Same Priority
listGET_OWNER_OF_NEXT_ENTRY는 pxIndex를 한 칸 이동시킨 뒤 그 위치의 task를 반환합니다. 같은 priority의 task들이 공평하게 시간을 나눠 갖습니다.
[T1] → [T2] → [T3] 같은 priority ↑pxIndex첫 호출: T1, pxIndex → T2두 번째: T2, pxIndex → T3세 번째: T3, pxIndex → T1configUSE_TIME_SLICING = 1(기본값)이면 tick마다 round-robin이 돌아갑니다.
#Idle Task — 항상 priority 0
다른 모든 task가 Blocked 상태에 있어도 priority 0의 Idle task는 ready 상태를 유지합니다. 덕분에 scheduler는 항상 실행할 task를 찾을 수 있고, 빈 ready list를 만나 무한 루프에 빠지는 일도 없습니다.
#Multi-Core (SMP) Scheduler
FreeRTOS SMP(10.5 이상)는 다음과 같이 동작합니다.
TCB_t *pxCurrentTCBs[configNUMBER_OF_CORES];코어마다 별도로 next-task를 결정하고, 공유 자료구조는 cross-core spin lock으로 보호합니다. Task affinity를 지정하면 특정 코어에서만 실행되게 묶을 수도 있습니다.
vTaskCoreAffinitySet(taskHandle, 0x01); // Core 0만cache locality나 특정 HW peripheral 접근이 중요한 task는 특정 코어에 고정해 두는 편이 유리합니다.
#Cooperative Mode
configUSE_PREEMPTION = 0이면 tick ISR은 카운터만 증가시키고 scheduler는 호출하지 않습니다. task 전환은 명시적으로 yield할 때만 일어납니다.
#Critical Section과 Scheduler
taskENTER_CRITICAL();// ↑ scheduler suspended + IRQ masked (BASEPRI)atomic_work();taskEXIT_CRITICAL();uxCriticalNesting 카운터로 중첩된 critical section을 추적해 안전하게 풉니다.
#Trace Hooks
traceTASK_SWITCHED_IN() // 새 task 시작traceTASK_SWITCHED_OUT() // 이전 task 종료traceTASK_CREATE(task) // 생성Tracealyzer, SystemView, Segger RTT 같은 도구들이 이 hook을 활용합니다.
#자주 하는 실수
⚠️ scheduler가 항상 동작한다고 가정합니다. suspended 상태에서는 ISR을 제외한 task가 멈춥니다.
⚠️ ISR에서
pxCurrentTCB를 직접 변경합니다. list 일관성이 깨지므로 FromISR 계열 API만 사용해야 합니다.
⚠️ time slicing이 기본이라고 가정합니다.
configUSE_TIME_SLICING = 0이면 같은 priority에서도 round-robin이 일어나지 않습니다.
#정리
- scheduler가 하는 일은
pxCurrentTCB를 갱신하는 것뿐입니다. - CLZ와 bitmap을 조합하면 next-task 선택이 O(1)에 끝납니다.
- 같은 priority 안에서는 list cursor로 round-robin을 돌립니다.
- Idle task는 priority 0에서 항상 ready 상태로 대기합니다.
다음 편은 context switch의 원리입니다. 레지스터 저장과 복원을 다룹니다.
#관련 항목
Practical RTOS Internals · 14 of 53
- 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
- 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
- 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
- 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
- 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
- 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
- 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
- 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
- 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
- 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
- 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
- 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
- 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
- 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
- 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
- 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
- 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
- 18RISC-V Context Switch 분석 — ECALL·mret·CSR
- 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
- 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
- 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
- 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
- 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
- 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
- 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
- 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
- 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
- 28Priority Ceiling Protocol — Immediate vs Original 비교
- 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
- 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
- 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
- 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
- 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
- 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
- 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
- 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
- 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
- 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
- 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
- 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
- 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
- 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
- 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
- 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
- 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
- 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
- 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
- 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
- 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
- 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
- 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
- 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
- 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL
관련 글
Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
ISR 종료 → ready task 실행까지의 시간. 측정 방법과 worst-case 추적.
Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
Blocked task의 timeout 관리. Sorted list + tick wraparound 처리. FreeRTOS의 2-list scheme.
Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
Ready 상태 task를 보관하는 자료구조 선택이 곧 스케줄러 latency를 결정합니다. FreeRTOS의 array-of-lists, bitmap + CLZ 최적화, uC/OS의 8×8 LUT까지 한 번에 정리합니다.
이 글을 참조하는 글 (5)
- Zephyr 커널 분석 — k_thread·k_sem·Driver Model— Practical RTOS Internals
- SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing— Practical RTOS Internals
- Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame— Practical RTOS Internals
- Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme— Practical RTOS Internals
- Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler— Practical RTOS Internals