스케줄링은 어느 Task를 언제 실행할지 결정하는 과정입니다.
베어메탈 방식:
1. 무한루프 내 순차적 실행 2. 인터럽트 기반 실행(Foreground/Background) 3. 상태 머신 기반 실행 4. 타이머 폴링(Timer-based Polling)RTOS 방식:
1. 우선순위 기반 스케쥴링(Priority-based): 선점형(Preemptive) / 협력형(Cooperative) 2. 시간 기반 스케쥴링: 라운드 로빈(Round Robin) / 시분할(Time Slicing) 3. 알고리즘 기반 스케쥴링: RMS(Rate Monotic Scheduling) / EDF(Eartliest Deadline First)
| 특성 | 설명 |
|---|---|
| Priority-based | 우선순위가 높은 Task가 먼저 실행 |
| Preemptive | 높은 우선순위 Task가 낮은 우선순위를 선점 |
| Fixed Priority | Task의 우선순위는 생성 시 결정 (동적 변경 가능) |
| Time-slicing | 같은 우선순위 Task는 시간을 나눠 사용 (Round-robin) |
위의 알고리즘 기반 스케쥴링은 예제에 추가해넣지 않았습니다. 해당 내용은 추후에 공부해서 추가할 계획입니다.
규칙 1: 항상 Ready 상태의 Task 중 가장 높은 우선순위를 실행
규칙 2: 같은 우선순위가 여러 개면 Round-robin 방식으로 교대 실행
규칙 3: 현재 Task보다 높은 우선순위가 Ready 되면 즉시 선점
규칙 4: Blocked 상태의 Task는 스케줄링 대상이 아님
기본 원리:
현재 실행 중인 Task의 우선순위 < 새로 Ready된 Task의 우선순위
→ 즉시 Context Switch 발생
C 버전:
void vTaskLow(void *pvParameters) {
while(1) {
printf("Low priority task running\n");
// 낮은 우선순위 작업 수행
for(volatile int i = 0; i < 1000000; i++);
}
}
void vTaskHigh(void *pvParameters) {
while(1) {
printf("High priority task running\n");
// 높은 우선순위 작업
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
// 1초 대기 (Blocked 상태)
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
// 우선순위 1 (낮음)
xTaskCreate(vTaskLow, "Low", 128, NULL, 1, NULL);
// 우선순위 3 (높음)
xTaskCreate(vTaskHigh, "High", 128, NULL, 3, NULL);
vTaskStartScheduler();
while(1);
}
실행 흐름:
시간 →
0ms : High Task Ready, Low Task Ready
→ High 실행 (우선순위 3 > 1)
10ms : High가 vTaskDelay() 호출
→ High Blocked
→ Low 실행 시작
1010ms : High Task Ready (Delay 끝)
→ Low를 즉시 선점
→ High 실행
1020ms : High가 다시 Blocked
→ Low 재개
C++ 버전:
class Task {
protected:
TaskHandle_t taskHandle;
const char* taskName;
uint16_t stackSize;
UBaseType_t priority;
virtual void run() = 0;
static void taskEntry(void* pvParameters) {
Task* task = static_cast<Task*>(pvParameters);
task->run();
}
public:
Task(const char* name, uint16_t stack, UBaseType_t prio)
: taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
void create() {
xTaskCreate(taskEntry, taskName, stackSize, this, priority, &taskHandle);
}
UBaseType_t getPriority() const { return priority; }
virtual ~Task() {}
};
class LowPriorityTask : public Task {
public:
LowPriorityTask() : Task("Low", 128, 1) {}
void run() override {
while(1) {
printf("Low priority task running\n");
// CPU 집중 작업
for(volatile int i = 0; i < 1000000; i++);
}
}
};
class HighPriorityTask : public Task {
public:
HighPriorityTask() : Task("High", 128, 3) {}
void run() override {
while(1) {
printf("High priority task running\n");
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
vTaskDelay(pdMS_TO_TICKS(1000)); //High 실행 완료 후 1000ms동안 block
}
}
};
int main(void) {
LowPriorityTask lowTask;
HighPriorityTask highTask;
lowTask.create();
highTask.create();
vTaskStartScheduler();
while(1);
}
// 케이스 1: Delay 종료
void vTask1(void *pvParameters) { // 우선순위 1
while(1) {
// 계속 실행 중...
}
}
void vTask2(void *pvParameters) { // 우선순위 2
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000)); // Blocked
// ← Delay가 끝나면 Task1을 선점하고 실행됨
}
}
// 케이스 2: ISR에서 높은 우선순위를 가진 Task 깨우기
QueueHandle_t xQueue;
void UART_IRQHandler(void) {
char data = UART_ReceiveData();
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// ISR에서 Queue에 데이터 전송
xQueueSendFromISR(xQueue, &data, &xHigherPriorityTaskWoken);
// 더 높은 우선순위 Task가 깨어났다면
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void vUARTTask(void *pvParameters) { // 우선순위 3 (높음)
char receivedData;
while(1) {
// Queue 대기 (Blocked)
xQueueReceive(xQueue, &receivedData, portMAX_DELAY);
// ISR에서 데이터가 오면 즉시 깨어나서 실행
// 낮은 우선순위 Task를 선점
process_data(receivedData);
}
}
// 케이스 3: 우선순위 변경
void vControlTask(void *pvParameters) {
TaskHandle_t xWorkerHandle;
while(1) {
// 긴급 상황 발생
if(emergency_detected()) {
// Worker Task의 우선순위를 높임
vTaskPrioritySet(xWorkerHandle, 5);
// → 현재 Task(우선순위 3)를 선점하고 Worker 실행
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
같은 우선순위를 가진 여러 Task가 시간을 나눠서 사용하는 방식입니다.
// FreeRTOSConfig.h
#define configUSE_TIME_SLICING 1 // 1 = 활성화, 0 = 비활성화
같은 우선순위 Task들이 매 Tick마다 교대로 실행
예: Priority 2인 Task A, B, C가 있을 때
Tick 0: Task A 실행
Tick 1: Task B 실행
Tick 2: Task C 실행
Tick 3: Task A 실행
...
C 버전:
void vTaskA(void *pvParameters) {
int count = 0;
while(1) {
printf("Task A: %d\n", count++);
// Delay 없이 계속 실행
// Time Slicing 덕분에 CPU 독점하지 않음
for(volatile int i = 0; i < 100000; i++);
}
}
void vTaskB(void *pvParameters) {
int count = 0;
while(1) {
printf("Task B: %d\n", count++);
for(volatile int i = 0; i < 100000; i++);
}
}
void vTaskC(void *pvParameters) {
int count = 0;
while(1) {
printf("Task C: %d\n", count++);
for(volatile int i = 0; i < 100000; i++);
}
}
int main(void) {
// 모두 같은 우선순위 2
xTaskCreate(vTaskA, "A", 128, NULL, 2, NULL);
xTaskCreate(vTaskB, "B", 128, NULL, 2, NULL);
xTaskCreate(vTaskC, "C", 128, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
// 출력 예:
// Task A: 0
// Task B: 0
// Task C: 0
// Task A: 1
// Task B: 1
// Task C: 1
// ...
C++ 버전:
class RoundRobinTask : public Task {
private:
char taskId;
int count;
public:
RoundRobinTask(char id, const char* name)
: Task(name, 128, 2), taskId(id), count(0) {}
void run() override {
while(1) {
printf("Task %c: %d\n", taskId, count++);
// CPU 집중 작업
for(volatile int i = 0; i < 100000; i++);
}
}
};
int main(void) {
RoundRobinTask taskA('A', "TaskA");
RoundRobinTask taskB('B', "TaskB");
RoundRobinTask taskC('C', "TaskC");
/* 태스크 A, B, C가 순차적으로 실행됨(인터럽트 핸들러 X) */
taskA.create();
taskB.create();
taskC.create();
vTaskStartScheduler();
while(1);
}
// Time Slicing 활성화 (configUSE_TIME_SLICING = 1)
void vTask1(void *pvParameters) {
while(1) {
work(); // Delay 없어도 매 Tick마다 다른 Task에게 양보
}
}
// Time Slicing 비활성화 (configUSE_TIME_SLICING = 0)
void vTask2(void *pvParameters) {
while(1) {
work();
taskYIELD(); // 명시적으로 양보해야 함
}
}
Tick은 FreeRTOS의 시간 기준이자 스케줄링 트리거입니다.
틱 인터럽트는 하드웨어 타이머에서 설정한 시간 간격마다 주기적으로 발생하는 인터럽트입니다.
// SysTick Handler (매 1ms마다 호출, configTICK_RATE_HZ = 1000)
void SysTick_Handler(void) {
// FreeRTOS에 Tick 증가 알림
if(xTaskIncrementTick() != pdFALSE) {
// Context Switch 필요 (더 높은 우선순위 Task가 Ready됨)
// PendSV 인터럽트 요청
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
// xTaskIncrementTick() 내부 동작
BaseType_t xTaskIncrementTick(void) {
// 1. Tick Count 증가
xTickCount++;
// 2. Delay 중인 Task 확인
// vTaskDelay()로 Blocked된 Task들의 타이머 감소
// 3. Timeout된 Task를 Ready로 전환
for(each blocked task) {
if(task->timeout == xTickCount) {
move_to_ready_list(task);
if(task->priority > current_task->priority) {
return pdTRUE; // Context Switch 필요
}
}
}
// 4. Time Slicing
if(configUSE_TIME_SLICING && same_priority_tasks_exist()) {
return pdTRUE; // 같은 우선순위 Task 교체
}
return pdFALSE;
}
void vTaskExample(void *pvParameters) {
while(1) {
printf("Task running\n");
// 500ms = 500 Ticks (configTICK_RATE_HZ = 1000일 때)
vTaskDelay(pdMS_TO_TICKS(500));
// 내부적으로:
// 1. Task를 Blocked 리스트로 이동
// 2. wakeup_time = current_tick + 500
// 3. Context Switch 발생
// 4. 500 Tick 후 Ready 리스트로 복귀
}
}
// FreeRTOSConfig.h
// 케이스 1: 1000Hz (1ms)
#define configTICK_RATE_HZ 1000
// 장점: 정밀한 타이밍 (1ms 단위)
// 단점: 오버헤드 높음 (1ms마다 인터럽트)
// 케이스 2: 100Hz (10ms)
#define configTICK_RATE_HZ 100
// 장점: 오버헤드 낮음
// 단점: 정밀도 낮음 (10ms 단위)
// 케이스 3: 10000Hz (0.1ms)
#define configTICK_RATE_HZ 10000
// 장점: 매우 정밀
// 단점: 오버헤드 매우 높음 (권장하지 않음)
// 권장: 1000Hz (대부분의 애플리케이션에 적합)
틱 레이트(Tick Rate) 주파수가 너무 높으면 CPU가 본업보다 관리 업무에 부하가 더 걸리기 때문에 오버헤드가 발생합니다. 이 경우의 오버헤드는 태스크 딜레이가 너무 길어 생기는 문제와는 다소 차이가 있습니다(태스크 딜레이로 인한 오버헤드는 관리 및 검색의 부하, 틱 레이트로 인한 부하는 연산 및 문맥 교환의 부하입니다 )
현재 실행 중인 Task를 멈추고 다른 Task로 전환하는 과정
| 상황 | 설명 |
|---|---|
| Tick Interrupt | 매 Tick마다 스케줄러 실행 |
| vTaskDelay() | Task가 자발적으로 Blocked |
| Queue/Semaphore 대기 | 데이터 대기 중 Blocked |
| 높은 우선순위 Ready | 선점 발생 |
| taskYIELD() | 명시적 양보 |
1. 현재 Task의 Context 저장
- CPU 레지스터 (R0-R12, LR, PC, PSR)
- Stack Pointer (PSP)
2. 스케줄러 실행
- Ready List에서 가장 높은 우선순위 Task 선택
3. 새 Task의 Context 복원
- 저장된 레지스터 값 복원
- Stack Pointer 전환
4. 새 Task 실행 재개
PendSV(Pendable Service Call)는 RTOS에서 컨텍스트 스위칭(문맥 교환)을 위해 사용하는 전용 대기 장치입니다. 주된 작동 방식은 "지연처리"로 예약 -> 양보 -> 실행 -> 교체의 순서로 문맥 교체(태스크 전환 과정)가 진행됩니다.
인터럽트(버튼/강제 진입한 이벤트)가 항상 태스크(데이터 연결)보다 우선순위가 높기 때문에 인터럽트가 발생하면 우선 인터럽트가 다 처리될 때까지 기다렸다가 다시 우선순위에 따라 스케쥴러가 작동하도록 하는게 PendSV입니다
// SysTick Handler (높은 우선순위)
void SysTick_Handler(void) {
// Tick 처리만 하고
xTaskIncrementTick();
// Context Switch는 PendSV에 위임
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
// PendSV Handler (가장 낮은 우선순위)
void PendSV_Handler(void) {
// 실제 Context Switch 수행
// 1. 현재 Task의 레지스터 저장
// 2. 다음 Task 선택
// 3. 새 Task의 레지스터 복원
}
// 왜 PendSV를 사용하는가?
// 1. 다른 인터럽트 처리 완료 후 Context Switch
// 2. Nested Interrupt 문제 방지
// 3. 더 안전하고 효율적인 전환
// STM32F4 (84MHz) 기준 Context Switch 시간
// 측정 방법
void vTask1(void *pvParameters) {
while(1) {
GPIO_PIN_HIGH();
vTaskDelay(1);
GPIO_PIN_LOW();
}
}
// 오실로스코프로 측정 결과:
// Context Switch 시간: 약 2-5 us (마이크로초)
//
// 시간 구성:
// - 레지스터 저장/복원: 1-2 us
// - 스케줄러 실행: 1-2 us
// - PendSV 오버헤드: 0.5-1 us
모든 Task가 Blocked 상태일 때 실행되는 최저 우선순위 Task입니다.
// Idle Task는 FreeRTOS가 자동으로 생성
// - 우선순위: 0 (가장 낮음)
// - 절대 Blocked 되지 않음
// - CPU가 항상 할 일이 있도록 보장
void prvIdleTask(void *pvParameters) {
while(1) {
// 1. 삭제된 Task의 메모리 정리
vTaskCleanUpResources();
// 2. Idle Hook 호출 (사용자 정의 가능)
#if configUSE_IDLE_HOOK
vApplicationIdleHook();
#endif
// 3. Tickless Idle (저전력 모드)
#if configUSE_TICKLESS_IDLE
portSUPPRESS_TICKS_AND_SLEEP();
#endif
}
}
C 버전:
// FreeRTOSConfig.h
#define configUSE_IDLE_HOOK 1
// 사용자 정의 Idle Hook 함수
void vApplicationIdleHook(void) {
// CPU가 한가할 때 실행할 작업
// 예 1: LED 깜빡임 (시스템 동작 표시)
static uint32_t idleCounter = 0;
if(++idleCounter > 1000000) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
idleCounter = 0;
}
// 예 2: 저전력 모드 진입
// __WFI(); // Wait For Interrupt
// 예 3: Watchdog 리셋
// HAL_IWDG_Refresh(&hiwdg);
// 주의: Idle Hook은 절대 Blocked 되면 안 됨!
// vTaskDelay(), Queue 대기 등 금지!
}
C++ 버전:
class IdleTaskMonitor {
private:
static uint32_t idleCounter;
static GPIO& statusLED;
public:
static void init(GPIO& led) {
statusLED = led;
}
static void idleHook() {
// CPU 유휴 시간 모니터링
if(++idleCounter > 1000000) {
statusLED.toggle();
idleCounter = 0;
}
}
};
// C 인터페이스 (FreeRTOS가 호출)
extern "C" void vApplicationIdleHook(void) {
IdleTaskMonitor::idleHook();
}
// 권장 우선순위 할당
// 최고 우선순위 (4): 긴급하고 짧은 작업
// - 인터럽트에서 깨워지는 Task
// - 실시간 제어 Task
#define PRIORITY_CRITICAL 4
// 높은 우선순위 (3): 중요한 주기적 작업
// - 통신 프로토콜 처리
// - 센서 데이터 수집
#define PRIORITY_HIGH 3
// 보통 우선순위 (2): 일반 애플리케이션
// - 데이터 처리
// - UI 업데이트
#define PRIORITY_NORMAL 2
// 낮은 우선순위 (1): 백그라운드 작업
// - 로깅
// - 통계 계산
#define PRIORITY_LOW 1
// 최저 우선순위 (0): Idle Task만 사용
// RMS 원칙: 주기가 짧은 Task에 높은 우선순위 할당
// 10ms 주기 Task → 우선순위 4
void vFastTask(void *pvParameters) {
while(1) {
process_fast();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// 100ms 주기 Task → 우선순위 3
void vMediumTask(void *pvParameters) {
while(1) {
process_medium();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 1000ms 주기 Task → 우선순위 2
void vSlowTask(void *pvParameters) {
while(1) {
process_slow();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
xTaskCreate(vFastTask, "Fast", 128, NULL, 4, NULL);
xTaskCreate(vMediumTask, "Med", 128, NULL, 3, NULL);
xTaskCreate(vSlowTask, "Slow", 128, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
// FreeRTOSConfig.h
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
void vStatsTask(void *pvParameters) {
char statsBuffer[512];
while(1) {
vTaskDelay(pdMS_TO_TICKS(5000));
printf("\n=== Task Runtime Stats ===\n");
vTaskGetRunTimeStats(statsBuffer);
printf("%s\n", statsBuffer);
}
}
// 출력 예:
// Task Abs Time % Time
// LED1 1250 5%
// LED2 2500 10%
// Sensor 12500 50%
// IDLE 8750 35%
//
// CPU 사용률 = 100% - IDLE% = 65%
요구사항:
C 버전:
// UART Task (최고 우선순위)
void vUARTTask(void *pvParameters) {
char rxData;
while(1) {
// Queue에서 데이터 대기 (Blocked)
if(xQueueReceive(uartQueue, &rxData, portMAX_DELAY) == pdTRUE) {
// 데이터 수신 즉시 처리
process_uart_data(rxData);
}
}
}
// 온도 센서 Task (높은 우선순위)
void vTempSensorTask(void *pvParameters) {
while(1) {
float temp = read_temperature();
if(temp > THRESHOLD) {
// 긴급 처리
trigger_alarm();
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 압력 센서 Task (보통 우선순위)
void vPressureSensorTask(void *pvParameters) {
while(1) {
float pressure = read_pressure();
store_pressure_data(pressure);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 디스플레이 Task (낮은 우선순위)
void vDisplayTask(void *pvParameters) {
while(1) {
update_display();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
uartQueue = xQueueCreate(10, sizeof(char));
xTaskCreate(vUARTTask, "UART", 256, NULL, 4, NULL);
xTaskCreate(vTempSensorTask, "Temp", 128, NULL, 3, NULL);
xTaskCreate(vPressureSensorTask, "Pressure", 128, NULL, 2, NULL);
xTaskCreate(vDisplayTask, "Display", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
C++ 버전:
class UARTTask : public Task {
private:
QueueHandle_t& queue;
public:
UARTTask(QueueHandle_t& q) : Task("UART", 256, 4), queue(q) {}
void run() override {
char rxData;
while(1) {
if(xQueueReceive(queue, &rxData, portMAX_DELAY) == pdTRUE) {
processData(rxData);
}
}
}
private:
void processData(char data) {
// 데이터 처리
}
};
class SensorTask : public Task {
protected:
uint32_t samplePeriod;
public:
SensorTask(const char* name, uint32_t period, UBaseType_t prio)
: Task(name, 128, prio), samplePeriod(period) {}
};
class TemperatureSensorTask : public SensorTask {
public:
TemperatureSensorTask() : SensorTask("Temp", 100, 3) {}
void run() override {
while(1) {
float temp = readTemperature();
if(temp > THRESHOLD) {
triggerAlarm();
}
vTaskDelay(pdMS_TO_TICKS(samplePeriod));
}
}
private:
float readTemperature() {
// ADC 읽기
return 25.0f;
}
void triggerAlarm() {
// 알람 발생
}
};
class PressureSensorTask : public SensorTask {
public:
PressureSensorTask() : SensorTask("Pressure", 500, 2) {}
void run() override {
while(1) {
float pressure = readPressure();
storeData(pressure);
vTaskDelay(pdMS_TO_TICKS(samplePeriod));
}
}
private:
float readPressure() { return 101.3f; }
void storeData(float data) { }
};
class DisplayTask : public Task {
public:
DisplayTask() : Task("Display", 256, 1) {}
void run() override {
while(1) {
updateDisplay();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
private:
void updateDisplay() {
// LCD 업데이트
}
};
int main(void) {
QueueHandle_t uartQueue = xQueueCreate(10, sizeof(char));
UARTTask uartTask(uartQueue);
TemperatureSensorTask tempTask;
PressureSensorTask pressureTask;
DisplayTask displayTask;
uartTask.create();
tempTask.create();
pressureTask.create();
displayTask.create();
vTaskStartScheduler();
while(1);
}
시간 (ms) →
0 100 200 300 400 500 600 700 800 900 1000
|----|----|----|----|----|----|----|----|----|----|
UART: ████─────██──────────────████───────────── (우선순위 4)
Temp: ──██──██──██──██──██──██──██──██──██──██── (우선순위 3)
Pres: ────────████────────────████────────────██ (우선순위 2)
Disp: ──────────────────────────────────────████ (우선순위 1)
IDLE: ──────────────██████████────────████████── (우선순위 0)
█ = 실행 중
─ = Blocked 또는 대기
| 측면 | C 방식 | C++ 방식 |
|---|---|---|
| 우선순위 관리 | #define으로 상수 정의 | enum class로 타입 안전하게 정의 |
| Task 그룹화 | 함수 prefix로 구분 | 클래스 계층으로 그룹화 |
| 우선순위 변경 | vTaskPrioritySet() 직접 호출 | 멤버 함수로 캡슐화 |
| 통계 수집 | 전역 변수 사용 | 클래스 멤버로 관리 |
C 방식 우선순위 정의:
#define PRIORITY_UART 4
#define PRIORITY_SENSOR 3
#define PRIORITY_DISPLAY 1
C++ 방식 우선순위 정의:
enum class Priority : UBaseType_t {
Low = 1,
Normal = 2,
High = 3,
Critical = 4
};
class Task {
protected:
Priority priority;
public:
Task(const char* name, Priority prio)
: priority(prio) {}
void setPriority(Priority newPrio) {
priority = newPrio;
vTaskPrioritySet(taskHandle, static_cast<UBaseType_t>(newPrio));
}
};
목표: Priority Inversion 문제를 직접 재현하고 관찰
요구사항:
힌트:
volatile int sharedResource = 0;
void vLowTask(void *pvParameters) {
while(1) {
taskENTER_CRITICAL();
// 공유 자원 사용 (오래 걸리는 작업)
for(int i = 0; i < 1000000; i++) {
sharedResource++;
}
taskEXIT_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
목표: 각 Task의 CPU 사용률을 측정하고 출력
요구사항:
목표: 상황에 따라 Task 우선순위를 동적으로 변경
요구사항:
| 용어 | 설명 |
|---|---|
| Preemptive | 높은 우선순위 Task가 낮은 우선순위를 선점 |
| Time Slicing | 같은 우선순위 Task의 시간 분할 실행 |
| Tick | 시스템 시간 단위, 스케줄링 기준점 |
| Context Switch | Task 전환, 레지스터 저장/복원 |
| Idle Task | 우선순위 0, 항상 Ready 상태 유지 |
| RMS | Rate Monotonic Scheduling, 주기 기반 우선순위 |