RTOS #2

홍태준·2026년 1월 13일

RTOS

목록 보기
2/20
post-thumbnail

Week 1 Day 2: FreeRTOS 소개 및 아키텍처

학습 목표

  • FreeRTOS의 내부 구조 이해
  • 커널의 핵심 구성 요소 파악
  • 스케줄러 동작 원리 학습
  • FreeRTOSConfig.h 설정 파일 분석

1. FreeRTOS 아키텍처 개요

FreeRTOS의 계층 구조

계층설명
Application Tasks사용자가 작성하는 Task들로 구성됩니다. LED 제어, 센서 읽기, 통신 처리 등 실제 애플리케이션 로직이 이 계층에 구현됩니다. 각 Task는 독립적으로 동작하며 우선순위를 가집니다.
FreeRTOS API Layer사용자에게 제공되는 API 함수들입니다. task.h(Task 관리), queue.h(Queue 통신), semphr.h(Semaphore/Mutex), timers.h(Software Timer) 등의 헤더 파일로 구성됩니다. 이 계층을 통해 Kernel 기능에 접근합니다.
FreeRTOS KernelRTOS의 핵심 구성요소입니다. Scheduler(Task 스케줄링), Task Manager(Task 생성/삭제/상태 관리), Memory Manager(동적 메모리 할당), IPC 메커니즘(Queue, Semaphore 구현) 등이 포함됩니다. 하드웨어 독립적인 코드로 작성되어 있습니다.
Port Layer특정 하드웨어 아키텍처에 맞춘 포팅 코드입니다. ARM Cortex-M4의 경우 Context Switch 구현, SysTick 타이머 설정, 인터럽트 우선순위 관리 등이 포함됩니다. port.c와 portmacro.h 파일로 구성됩니다.
Hardware (STM32)실제 마이크로컨트롤러 하드웨어입니다. CPU 코어, 메모리, 주변장치(GPIO, UART, Timer 등), 인터럽트 컨트롤러 등이 포함됩니다. FreeRTOS는 이 계층 위에서 동작합니다.

주요 구성 요소

1. Kernel (커널)

  • 시스템의 핵심, Task 스케줄링과 관리 담당
  • Task 상태 전이 처리
  • 시스템 시간 관리 (Tick)

2. Scheduler (스케줄러)

  • 어떤 Task를 실행할지 결정
  • Priority-based Preemptive 방식
  • Context Switch 수행

3. Task Manager

  • Task 생성, 삭제, 우선순위 변경
  • Task 상태 추적

4. IPC (Inter-Process Communication)

  • Queue, Semaphore, Mutex, Event Group
  • Task 간 데이터 교환 및 동기화

5. Memory Manager

  • 동적 메모리 할당 (Heap)
  • 여러 할당 방식 지원 (heap_1 ~ heap_5)

2. FreeRTOS 디렉토리 구조

FreeRTOS/
├── Source/
│   ├── tasks.c              # Task 관리
│   ├── queue.c              # Queue 구현
│   ├── list.c               # 내부 리스트 구조
│   ├── timers.c             # Software Timer
│   ├── event_groups.c       # Event Group
│   ├── stream_buffer.c      # Stream Buffer
│   ├── portable/
│   │   ├── GCC/
│   │   │   └── ARM_CM4F/    # Cortex-M4F 포팅 코드
│   │   │       ├── port.c
│   │   │       └── portmacro.h
│   │   └── MemMang/
│   │       ├── heap_1.c     # 가장 간단한 메모리 관리
│   │       ├── heap_2.c
│   │       ├── heap_3.c
│   │       ├── heap_4.c     # 권장 (조각 모음 지원)
│   │       └── heap_5.c
│   └── include/
│       ├── FreeRTOS.h
│       ├── task.h
│       ├── queue.h
│       ├── semphr.h
│       ├── timers.h
│       └── event_groups.h
└── Demo/                    # 각종 예제 프로젝트

핵심 파일 설명

tasks.c

  • Task 생성, 삭제, 우선순위 관리
  • 스케줄러 구현
  • 가장 중요한 파일 (약 3000줄)

queue.c

  • Queue, Semaphore, Mutex 구현
  • 모두 같은 기본 구조 사용

port.c

  • 하드웨어 의존적인 코드
  • Context Switch 구현
  • SysTick 인터럽트 처리

3. 스케줄러 동작 원리

Priority-based Preemptive Scheduling

FreeRTOS는 우선순위 기반 선점형 스케줄링을 사용합니다.

핵심 원칙:
1. 항상 가장 높은 우선순위의 Ready Task가 실행됨
2. 같은 우선순위면 Round-robin으로 실행 (Time Slice)
3. 높은 우선순위 Task가 Ready 되면 즉시 선점

Task 우선순위

// 우선순위 범위: 0 ~ (configMAX_PRIORITIES - 1)
// 숫자가 클수록 높은 우선순위

/* 1. 우선 순위 정의 */
#define IDLE_PRIORITY        0   // 가장 낮음 (Idle Task)
#define LED_PRIORITY         1
#define BUTTON_PRIORITY      2
#define SENSOR_PRIORITY      2   // 같은 우선순위 가능
#define UART_PRIORITY        3   // 가장 높음

/* 태스크 함수 프로토타입 선언 */
void vUART_Task(void *pvParameters);
void vButton_Task(void *pvParameters);
void vSensor_Task(void *pvParameters);
void vLED_Task(void *pvParameters);

int main(void) {
    /* 하드웨어 초기화 코드 (생략) */
    // HAL_Init(); 
    // SystemClock_Config();

    /* 2. xTaskCreate를 이용한 태스크 생성 및 우선순위 부여 */

    // UART 태스크: 우선순위 3 (가장 높음, 중요 데이터 전송)
    xTaskCreate(vUART_Task, "UART", 128, NULL, UART_priority, NULL);

    // 버튼 & 센서 태스크: 우선순위 2 (동일 우선순위, 1ms씩 번갈아 실행)
    xTaskCreate(vButton_Task, "BUTTON", 128, NULL, BUTTON_PRIORITY, NULL);
    xTaskCreate(vSensor_Task, "SENSOR", 128, NULL, SENSOR_priority, NULL);

    // LED 태스크: 우선순위 1 (가장 낮음, 여유 있을 때만 실행)
    xTaskCreate(vLED_Task, "LED", 128, NULL, LED_priority, NULL);

    /* 3. 스케줄러 시작 */
    vTaskStartScheduler();

    while(1); // 스케줄러가 시작되면 여기까지 내려오지 않습니다.
}

/* 태스크 구현 예시 */
void vUART_Task(void *pvParameters) {
    for(;;) {
        // UART 송수신 로직
        vTaskDelay(pdMS_TO_TICKS(10)); // 10ms 블록 상태 (다른 태스크에 기회 넘김)
    }
}

void vLED_Task(void *pvParameters) {
    for(;;) {
        // LED 토글 로직
        vTaskDelay(pdMS_TO_TICKS(500)); 
    }
}

// 나머지 Task 함수들도 유사한 구조로 구현...
}

태스크 우선순위는 #define으로 고정된 값을 정의해두고, 태스크를 생성하는 함수인 xTaskCreate()의 인자로 전달해 부여합니다. 이 때 단순히 숫자를 직접 입력해도 되지만, 가독성과 유지보수성을 위해 #define을 사용하는 것이 관례입니다.

스케줄링 예시

RTOS에서는 태스크가 종료되지 않고 계속 실행돼야 하기 때문에 모든 태스크는 while(1) 무한 루프를 가집니다.

C 버전:

#include "FreeRTOS.h"
#include "task.h"

void vTaskHigh(void *pvParameters) {
    while(1) {
        // 높은 우선순위 작업
        process_urgent_data();
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vTaskMedium(void *pvParameters) {
    while(1) {
        // 중간 우선순위 작업
        process_normal_data();
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void vTaskLow(void *pvParameters) {
    while(1) {
        // 낮은 우선순위 작업
        background_task();
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

int main(void) {
    xTaskCreate(vTaskHigh, "High", 128, NULL, 3, NULL);
    xTaskCreate(vTaskMedium, "Med", 128, NULL, 2, NULL);
    xTaskCreate(vTaskLow, "Low", 128, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

#include "FreeRTOS.h"
#include "task.h"

class PriorityTask : public Task {
protected:
    uint32_t delayMs;
    
public:
    PriorityTask(const char* name, UBaseType_t priority, uint32_t delay)
        : Task(name, 128, priority), delayMs(delay) {}
};

class HighPriorityTask : public PriorityTask {
public:
    HighPriorityTask() : PriorityTask("High", 3, 100) {}
    
    void run() override {
        while(1) {
            processUrgentData();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
        }
    }
    
private:
    void processUrgentData() {
        // 높은 우선순위 작업
    }
};

class MediumPriorityTask : public PriorityTask {
public:
    MediumPriorityTask() : PriorityTask("Medium", 2, 200) {}
    
    void run() override {
        while(1) {
            processNormalData();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
        }
    }
    
private:
    void processNormalData() {
        // 중간 우선순위 작업
    }
};

class LowPriorityTask : public PriorityTask {
public:
    LowPriorityTask() : PriorityTask("Low", 1, 500) {}
    
    void run() override {
        while(1) {
            backgroundTask();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
        }
    }
    
private:
    void backgroundTask() {
        // 낮은 우선순위 작업
    }
};

int main(void) {
    HighPriorityTask highTask;
    MediumPriorityTask mediumTask;
    LowPriorityTask lowTask;
    
    highTask.create();
    mediumTask.create();
    lowTask.create();
    
    vTaskStartScheduler();
    while(1);
}

실행 시나리오

시간축 →
0ms   : High, Med, Low 모두 Ready
        → High 실행 (우선순위 3)
        
100ms : High가 vTaskDelay() → Blocked
        → Med 실행 (우선순위 2)
        
200ms : High Ready (100ms 지남)
        → High가 Med를 선점하여 실행
        
300ms : High가 다시 Blocked
        Med가 vTaskDelay() → Blocked
        → Low 실행 (우선순위 1)

4. Task 상태 (State)

4가지 기본 상태

        ┌─────────────┐
        │   Running  │  ← 현재 실행 중
        └─────────────┘
              ↕
        ┌─────────────┐
        │    Ready   │  ← 실행 준비됨, CPU 대기
        └─────────────┘
              ↕
        ┌─────────────┐
        │   Blocked  │  ← 이벤트/시간 대기
        └─────────────┘
              ↕
        ┌─────────────┐
        │  Suspended │  ← 명시적으로 정지됨
        └─────────────┘

상태 전이 조건

Running → Blocked

  • vTaskDelay() 호출
  • Queue/Semaphore 대기
  • Event 대기

Blocked → Ready

  • Delay 시간 만료
  • Queue에 데이터 도착
  • Semaphore 수신

Ready → Running

  • 스케줄러가 선택
  • 가장 높은 우선순위

Running → Ready

  • 더 높은 우선순위 Task가 Ready
  • Time Slice 만료 (같은 우선순위)

Any → Suspended

  • vTaskSuspend() 호출

Suspended → Ready

  • vTaskResume() 호출

상태 전이 예제

C 버전:

void vTask1(void *pvParameters) {
    while(1) {
        // Running 상태
        do_work();
        
        // Blocked 상태로 전이 (100ms)
        vTaskDelay(pdMS_TO_TICKS(100));
        // → 100ms 후 Ready 상태
        // → 스케줄러가 선택하면 Running 상태
    }
}

TaskHandle_t xTask2Handle;

void vTask2(void *pvParameters) {
    while(1) {
        do_something();
        
        // Task2를 Suspended 상태로
        vTaskSuspend(NULL);  // NULL = 자기 자신
        
        // vTaskResume()이 호출될 때까지 여기서 멈춤
        do_after_resume();
    }
}

void vTask3(void *pvParameters) {
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // Task2를 깨움 (Suspended → Ready)
        vTaskResume(xTask2Handle);
    }
}

int main(void) {
    xTaskCreate(vTask1, "Task1", 128, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 128, NULL, 2, &xTask2Handle);
    xTaskCreate(vTask3, "Task3", 128, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

class Task1 : public Task {
public:
    Task1() : Task("Task1", 128, 1) {}
    
    void run() override {
        while(1) {
            // Running 상태
            doWork();
            
            // Blocked 상태로 전이 (100ms)
            vTaskDelay(pdMS_TO_TICKS(100));
            // → 100ms 후 Ready 상태
            // → 스케줄러가 선택하면 Running 상태
        }
    }
    
private:
    void doWork() {
        // 작업 수행
    }
};

class Task2 : public Task {
public:
    Task2() : Task("Task2", 128, 2) {}
    
    void run() override {
        while(1) {
            doSomething();
            
            // Task2를 Suspended 상태로
            vTaskSuspend(taskHandle);
            
            // vTaskResume()이 호출될 때까지 여기서 멈춤
            doAfterResume();
        }
    }
    
private:
    void doSomething() { }
    void doAfterResume() { }
};

class Task3 : public Task {
private:
    Task2& task2;
    
public:
    Task3(Task2& t2) : Task("Task3", 128, 1), task2(t2) {}
    
    void run() override {
        while(1) {
            vTaskDelay(pdMS_TO_TICKS(1000));
            
            // Task2를 깨움 (Suspended → Ready)
            task2.resume();
        }
    }
};

// Task 클래스에 resume 메서드 추가 필요
class Task {
    // ... 기존 코드 ...
public:
    void resume() {
        if(taskHandle != nullptr) {
            vTaskResume(taskHandle);
        }
    }
};

int main(void) {
    Task1 task1;
    Task2 task2;
    Task3 task3(task2);
    
    task1.create();
    task2.create();
    task3.create();
    
    vTaskStartScheduler();
    while(1);
}

5. Tick과 Time Slice

System Tick

Tick이란?

  • FreeRTOS의 시간 단위
  • 일반적으로 1ms (설정 가능)
  • SysTick 타이머 인터럽트로 생성

configTICK_RATE_HZ 설정:

// FreeRTOSConfig.h
#define configTICK_RATE_HZ    1000   // 1000Hz = 1ms

Tick Interrupt 동작

매 1ms마다:
1. SysTick 인터럽트 발생
2. Tick 카운터 증가
3. Blocked Task 중 Timeout된 Task를 Ready로 전환
4. 필요하면 Context Switch

Time Slice (시간 할당량)

같은 우선순위의 Task들이 여러 개 있을 때:

C 버전:

// configUSE_TIME_SLICING이 1로 설정되어 있으면
// 같은 우선순위 Task들은 Round-robin으로 실행

void vTaskA(void *pvParameters) {
    while(1) {
        // CPU 집중 작업 (Delay 없음)
        for(int i = 0; i < 1000000; i++);
    }
}

void vTaskB(void *pvParameters) {
    while(1) {
        // CPU 집중 작업
        for(int i = 0; i < 1000000; i++);
    }
}

// 같은 우선순위로 생성
xTaskCreate(vTaskA, "A", 128, NULL, 2, NULL);
xTaskCreate(vTaskB, "B", 128, NULL, 2, NULL);

// 결과: TaskA와 TaskB가 매 Tick마다 번갈아 실행

C++ 버전:

class CPUIntensiveTask : public Task {
protected:
    char taskId;
    
public:
    CPUIntensiveTask(char id, const char* name)
        : Task(name, 128, 2), taskId(id) {}
    
    void run() override {
        while(1) {
            // CPU 집중 작업 (Delay 없음)
            for(volatile int i = 0; i < 1000000; i++);
        }
    }
};

class TaskA : public CPUIntensiveTask {
public:
    TaskA() : CPUIntensiveTask('A', "TaskA") {}
};

class TaskB : public CPUIntensiveTask {
public:
    TaskB() : CPUIntensiveTask('B', "TaskB") {}
};

int main(void) {
    TaskA taskA;
    TaskB taskB;
    
    // 같은 우선순위로 생성
    taskA.create();
    taskB.create();
    
    vTaskStartScheduler();
    
    // 결과: TaskA와 TaskB가 매 Tick마다 번갈아 실행
    while(1);
}

6. FreeRTOSConfig.h 설정 파일

주요 설정 항목

C 버전:

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/* ========== 기본 설정 ========== */
// CPU 클럭 주파수 (Hz)
#define configCPU_CLOCK_HZ              ( SystemCoreClock )

// Tick 주파수 (Hz) - 보통 1000 (1ms)
#define configTICK_RATE_HZ              1000

// 최대 우선순위 (0 ~ 이 값-1)
#define configMAX_PRIORITIES            5

// Idle Task Stack 크기 (words)
#define configMINIMAL_STACK_SIZE        128

// 전체 Heap 크기 (bytes)
#define configTOTAL_HEAP_SIZE           ( 10 * 1024 )

/* ========== 스케줄링 관련 ========== */
// Preemption 사용 (1 = 선점형)
#define configUSE_PREEMPTION            1

// Time Slicing 사용 (같은 우선순위 Round-robin)
#define configUSE_TIME_SLICING          1

// Idle Task Yield (Idle Task가 자발적으로 양보)
#define configIDLE_SHOULD_YIELD         1

/* ========== 기능 활성화 ========== */
// Queue 사용
#define configUSE_QUEUE_SETS            0

// Mutex 사용
#define configUSE_MUTEXES               1

// Recursive Mutex 사용
#define configUSE_RECURSIVE_MUTEXES     1

// Counting Semaphore 사용
#define configUSE_COUNTING_SEMAPHORES   1

// Software Timer 사용
#define configUSE_TIMERS                1
#define configTIMER_TASK_PRIORITY       3
#define configTIMER_QUEUE_LENGTH        10
#define configTIMER_TASK_STACK_DEPTH    128

/* ========== Hook 함수 ========== */
// Idle Hook
#define configUSE_IDLE_HOOK             0

// Tick Hook
#define configUSE_TICK_HOOK             0

// Malloc 실패 Hook
#define configUSE_MALLOC_FAILED_HOOK    1

// Stack Overflow 검사
#define configCHECK_FOR_STACK_OVERFLOW  2

/* ========== Cortex-M 특화 설정 ========== */
// 인터럽트 우선순위 비트 수 (STM32F4는 4비트)
#define configPRIO_BITS                 4

// Kernel 인터럽트 우선순위
#define configKERNEL_INTERRUPT_PRIORITY         ( 15 << (8 - configPRIO_BITS) )

// 최대 Syscall 인터럽트 우선순위
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( 5 << (8 - configPRIO_BITS) )

/* ========== API 함수 포함 ========== */
#define INCLUDE_vTaskPrioritySet        1
#define INCLUDE_uxTaskPriorityGet       1
#define INCLUDE_vTaskDelete             1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil         1
#define INCLUDE_vTaskDelay              1

#endif /* FREERTOS_CONFIG_H */

C++ 버전:

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

// C++에서도 동일한 설정 사용
// 하지만 추가적인 타입 안정성을 위해 constexpr 사용 가능

namespace FreeRTOSConfig {
    // 컴파일 타임 상수로 정의
    constexpr uint32_t CPU_CLOCK_HZ = 84000000;
    constexpr uint32_t TICK_RATE_HZ = 1000;
    constexpr uint32_t MAX_PRIORITIES = 5;
    constexpr uint32_t MINIMAL_STACK_SIZE = 128;
    constexpr uint32_t TOTAL_HEAP_SIZE = 10 * 1024;
    
    // Task 우선순위를 enum class로 정의
    enum class Priority : UBaseType_t {
        Idle = 0,
        Low = 1,
        Normal = 2,
        High = 3,
        Critical = 4
    };
}

// FreeRTOS C 매크로도 여전히 필요
#define configCPU_CLOCK_HZ              ( SystemCoreClock )
#define configTICK_RATE_HZ              1000
#define configMAX_PRIORITIES            5
#define configMINIMAL_STACK_SIZE        128
#define configTOTAL_HEAP_SIZE           ( 10 * 1024 )

#define configUSE_PREEMPTION            1
#define configUSE_TIME_SLICING          1
#define configIDLE_SHOULD_YIELD         1

#define configUSE_MUTEXES               1
#define configUSE_RECURSIVE_MUTEXES     1
#define configUSE_COUNTING_SEMAPHORES   1

#define configUSE_TIMERS                1
#define configTIMER_TASK_PRIORITY       3
#define configTIMER_QUEUE_LENGTH        10
#define configTIMER_TASK_STACK_DEPTH    128

#define configUSE_IDLE_HOOK             0
#define configUSE_TICK_HOOK             0
#define configUSE_MALLOC_FAILED_HOOK    1
#define configCHECK_FOR_STACK_OVERFLOW  2

#define configPRIO_BITS                 4
#define configKERNEL_INTERRUPT_PRIORITY         ( 15 << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( 5 << (8 - configPRIO_BITS) )

#define INCLUDE_vTaskPrioritySet        1
#define INCLUDE_uxTaskPriorityGet       1
#define INCLUDE_vTaskDelete             1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil         1
#define INCLUDE_vTaskDelay              1

#endif /* FREERTOS_CONFIG_H */

중요 설정 설명

configCPU_CLOCK_HZ

  • CPU 동작 주파수
  • 정확한 Delay 계산에 필요

configTICK_RATE_HZ

  • Tick 발생 빈도
  • 1000 = 1ms마다 Tick
  • 높을수록 정밀하지만 오버헤드 증가

configMAX_PRIORITIES

  • 사용 가능한 우선순위 개수
  • 0 ~ (이 값 - 1)
  • 너무 크면 메모리 낭비

configTOTAL_HEAP_SIZE

  • 동적 메모리 총량
  • Task Stack, Queue 등에 사용
  • 부족하면 Task 생성 실패

configCHECK_FOR_STACK_OVERFLOW

  • 0: 검사 안 함
  • 1: 간단한 검사
  • 2: 더 철저한 검사 (권장)

7. Context Switch 메커니즘

Context Switch란?

현재 실행 중인 Task를 멈추고 다른 Task로 전환하는 과정

우선순위에 따라 선점(Preemption, 강제 전환), 또는 순차적/자발적 전환(Yield/Block)으로 나뉘지만 태스크 종료 후 다음 태스크로 전환되는 일련의 동작을 컨텍스트 스위칭이라고 합니다.

저장해야 할 것:

  • CPU 레지스터 (R0-R12, LR, PC, PSR)
  • Stack Pointer (SP)

Context Switch 발생 시점

  1. Tick Interrupt 발생
  2. Task가 Blocked 상태로 전환
  3. 더 높은 우선순위 Task가 Ready
  4. vTaskDelay() 호출

여기서 Task 우선순위를 각각 0,1,2,3,4로 부여한 태스크들이 있다고 했을 때 4의 우선순위를 가진 태스크의 실행 시간이 100ms로 매우 짧고, 따라서 하위 우선순위를 가진 태스크들이 실행될 만큼 충분한 blocked-time을 가지지 못 한다면 프로그램 실행 내내 4의 우선순위를 가진 태스크만 실행되고 나머지 태스크는 실행될 시간을 갖지 못하는 기아상태(Starvation)에 빠질 수 있습니다.

일반적으로 기아상태(Starvation)를 유발하지 않도록 태스크 매니징을 하는 것이 설계자로써의 역할이지만 때에 따라 Fail-Safe나 비상 정지(Emergency Stop) 로직을 설계하기 위해 의도적으로 기아상태를 시스템 제어 도구로 활용할 수 있습니다

PendSV(Pended Service Call)를 사용하는 이유

PendSV는 컨텍스트 스위칭을 안전하게 처리하기 위한 전용 인터럽트이며, 로직은 다음과 같습니다.
1. 스케쥴러가 태스크 전환이 필요하다고 판단하면 PendSV를 Pending 상태로 만듦.
2. 현재 실행 중인 모든 하드웨어 인터럽트(UART, DMA, Timer 등)가 끝날 때까지 기다림.
3. 더 이상 실행할 인터럽트가 없을 때, 가장 낮은 순위인 PendSV가 실행되며 안전하게 컨텍스트 스위칭

C 버전 설명:

// SysTick Interrupt Handler
void SysTick_Handler(void) {
    // Tick 증가 및 스케줄링 필요 여부 확인
    if(xTaskIncrementTick() != pdFALSE) {
        // Context Switch 필요
        // 하지만 여기서 바로 하지 않고 PendSV에 요청
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
    }
}

// PendSV Interrupt Handler (가장 낮은 우선순위)
void PendSV_Handler(void) {
    // 여기서 실제 Context Switch 수행
    // 1. 현재 Task의 레지스터를 Stack에 저장
    // 2. 다음 실행할 Task 선택
    // 3. 새 Task의 레지스터를 복원
}

C++ 버전 - Context Switch 래퍼:

class ContextSwitcher {
public:
    static void requestSwitch() {
        // PendSV 요청
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
    }
    
    static void disableInterrupts() {
        taskENTER_CRITICAL();
    }
    
    static void enableInterrupts() {
        taskEXIT_CRITICAL();
    }
};

// 사용 예
class CriticalTask : public Task {
public:
    CriticalTask() : Task("Critical", 128, 3) {}
    
    void run() override {
        while(1) {
            // Critical Section 시작
            ContextSwitcher::disableInterrupts();
            
            // 공유 자원 접근
            sharedData++;
            
            // Critical Section 종료
            ContextSwitcher::enableInterrupts();
            
            vTaskDelay(pdMS_TO_TICKS(100));
        }
    }
    
private:
    static volatile int sharedData;
};

C vs C++ 비교 정리

C 방식의 특징

  • 함수 포인터를 Task 진입점으로 사용
  • 전역 변수나 파라미터로 상태 관리
  • FreeRTOS 공식 예제와 동일한 스타일
  • 메모리 오버헤드 최소화

C++ 방식의 특징

  • 클래스 상속으로 Task 추상화
  • 멤버 변수로 Task 상태 캡슐화
  • 가상 함수를 통한 다형성 활용
  • 타입 안정성 향상 (enum class 등)
  • 코드 재사용성과 유지보수성 향상

실전 적용 권장사항

  • 소규모 프로젝트: C 방식 (간결함)
  • 중대형 프로젝트: C++ 방식 (구조화)
  • 메모리 제약 심함: C 방식
  • 팀 협업: C++ 방식 (명확한 인터페이스)

요약

오늘 배운 핵심 내용

  1. FreeRTOS 아키텍처: Kernel, Scheduler, Task Manager, IPC, Memory Manager
  2. 스케줄러: Priority-based Preemptive, 가장 높은 우선순위 Task 실행
  3. Task 상태: Running, Ready, Blocked, Suspended
  4. Tick: 시스템 시간 단위, 보통 1ms
  5. FreeRTOSConfig.h: 시스템 전체 설정 파일
  6. Context Switch: Task 전환 메커니즘

핵심 용어 정리

용어설명
KernelRTOS의 핵심, Task 관리 및 스케줄링 담당
Scheduler실행할 Task를 선택하는 구성요소
Preemptive높은 우선순위가 낮은 우선순위를 선점
TickRTOS의 시간 단위, 보통 1ms
Time Slice같은 우선순위 Task에게 할당되는 시간
Context SwitchTask 전환 과정
Ready실행 준비된 상태, CPU 대기 중
Blocked이벤트나 시간을 대기하는 상태
Suspended명시적으로 정지된 상태

복습 문제

  1. FreeRTOS의 5가지 주요 구성 요소는 무엇인가요?
  2. Priority-based Preemptive Scheduling의 동작 원리를 설명하세요.
  3. Task의 4가지 상태와 전이 조건을 설명하세요.
  4. Tick이란 무엇이며, 왜 필요한가요?
  5. Context Switch는 언제 발생하나요?
  6. configTICK_RATE_HZ를 2000으로 설정하면 어떻게 되나요?
  7. C와 C++로 FreeRTOS를 사용할 때의 차이점은 무엇인가요?
  8. 같은 우선순위를 가진 3개의 Task가 있을 때 어떻게 실행되나요?

학습 일자: 2026년 1월 10일
작성자: RTOS 강의 시리즈
다음 강의: Day 3 - 개발 환경 구축

profile
당신의 코딩 메이트

0개의 댓글