높은 우선순위 태스크가 준비되면 실행 중인 낮은 태스크를 즉시 중단시키고 CPU를 가져가는 방식(preemptive scheduling).
협조형과의 대비
협조형(cooperative)
태스크가 스스로 양보(yield)할 때만 전환
├ 전환 시점이 명확해 경쟁 조건이 적다
└ 태스크 하나가 양보를 안 하면 시스템 전체가 멈춘다
선점형(preemptive)
스케줄러가 언제든 강제로 전환
├ 높은 우선순위의 응답을 보장한다 ← 실시간성의 핵심
└ 어디서든 끊길 수 있으니 공유 데이터 보호가 필수
RTOS가 실시간성을 보장하려면 선점형이어야 한다. 협조형은 "언제 양보할지"를 태스크에 의존하므로 최악 응답을 보장할 수 없다.
언제 전환이 일어나나
- ① 높은 우선순위 태스크가 준비 상태가 됨
-
- ISR이 세마포어를 주거나 큐에 넣음
-
- 지연 시간이 만료됨
-
- ② 현재 태스크가 블로킹 (큐 대기, 뮤텍스 대기, vTaskDelay)
- ③ 시간 분할 (같은 우선순위 간 라운드 로빈)
- ④ 명시적 양보 (taskYIELD)
어떻게 구현되나 — PendSV
SysTick 인터럽트 (1ms 틱)
→ 스케줄러가 더 높은 우선순위 태스크가 준비됐는지 확인
→ 있으면 PendSV 를 pending 으로 설정
→ 모든 인터럽트 처리가 끝난 뒤 PendSV 핸들러 실행 → 실제 컨텍스트 전환
왜 PendSV를 쓰나 — 컨텍스트 전환을 가장 낮은 우선순위에서 하려는 것이다.
-
인터럽트 처리 중에 전환이 일어나면?
-
ISR이 중간에 끊기고 다른 태스크로 갔다가 돌아온다
-
인터럽트 지연이 크게 늘어난다
-
PendSV를 최저 우선순위로 두면
-
모든 ISR이 끝난 뒤에야 전환
-
인터럽트 응답성이 유지된다
"실제 전환은 항상 마지막에" 가 Cortex-M RTOS 포팅의 핵심 설계다.
컨텍스트 전환에서 저장되는 것
하드웨어 자동 (예외 진입 시): R0-R3, R12, LR, PC, xPSR
- PendSV 핸들러가 수동으로 — : R4-R11 (+ FPU 사용 시 S16-S31)
- 그리고 PSP 값을 TCB에 기록
태스크마다 자기 스택(PSP) 을 가지므로, PSP를 바꾸는 것이 곧 전환이다.
태스크 상태
Ready 상태의 태스크 중 우선순위가 가장 높은 것이 Running이 된다.
우선순위 설계 — RMS
Rate Monotonic Scheduling — 주기가 짧은 태스크에 높은 우선순위를 준다.
- 1ms 주기 제어 루프 — → 우선순위 5
- 10ms 센서 읽기 — → 우선순위 4
- 100ms 통신 — → 우선순위 3
- 1s UI 갱신 — → 우선순위 2
- 유휴 작업 — → 우선순위 1
이 규칙이 최적임이 수학적으로 증명되어 있다(고정 우선순위 중에서). CPU 사용률이 약 69%(n→∞) 이하면 모든 데드라인을 지킬 수 있다는 이용률 한계도 함께 알려져 있다.
같은 우선순위 태스크
configUSE_TIME_SLICING = 1 → 틱마다 라운드 로빈
configUSE_TIME_SLICING = 0 → 블로킹할 때까지 계속 실행
같은 우선순위를 여럿 두면 순서를 예측하기 어려워진다. 가능하면 우선순위를 다르게 주는 편이 분석에 유리하다.
선점형이 가져오는 문제들
- ① 경쟁 조건 → 어디서든 끊길 수 있다. 공유 데이터 보호 필수
- ② 우선순위 역전 → 낮은 태스크가 잡은 뮤텍스를 높은 태스크가 기다림
- ③ 데드락 → 여러 뮤텍스를 엇갈린 순서로 잡을 때
- ④ 스택 오버플로 → 태스크마다 스택을 잡아야 한다
"공짜 점심은 없다" — 실시간성을 얻은 대가로 동시성 문제군을 받는다. 이것이 RTOS 도입을 신중히 판단해야 하는 이유다.