02. RTOS , 어디가서 '니 쫌 아네' 소리 한번 들어보자 (ESP32 & freeRTOS실습)

owljun·2025년 7월 24일
4
post-thumbnail

RTOS

RealTime Operationg System

Windows , Linux 와 같은 일반적인 운영체제와 달리, 시간 제약 조건을 엄격하게 준수하여, 예측가능성안정성을 보장한다.


어떻게 위 3가지를 만족시키는 걸까?

RTOS를 이해하기 위해서는 꼭 이해하고 있어야할 키워드가 몇가지 존재한다.

"스케쥴링" , "인터럽트"

이 두 키워드는 RTOS의 결정성과 관련된 핵심 설계 개념이다.

  • 스케쥴링

    • 어떤 프로그램(Task)을 CPU에 할당시켜 실행시킬 것인지 스케쥴링 하는 것.
  • 인터럽트

    • 광의의 인터럽트 : 어떤 이벤트가 발생한 상황 (인터럽트 + Exception 의 개념)
    • 인터럽트 : 외부에서 어떤 신호가 발생하여 이벤트가 발생하는 상황.

RTOS가 시간 제약과 예측가능성 및 안정성을 보장하기 위해 포기한 것들

  • 기존 OS의 사용자 편의를 위한 시스템(가상메모리 , 동적메모리할당 등 ...)을 과감히 버림.

  • 스케쥴링 방식

    일반적인 OS가 복잡한 로직을 통해 우선순위를 산정하고 공정하게 스케쥴링을 시도했다면
    RTOS는 철저하게 사용자가 설정한 우선순위를 따르고, 정해진 시간동안만 실행 하는 것을 보장한다.

  • 시스템의 복잡함

    일반적인 OS가 해주던 파일시스템, GUI 등등 많은 기능을 제공하기 위해 복잡함을 도입했다면,
    RTOS는 모듈형으로 필요한 기능만 삽입하여 사용이 가능하게 설계 되었다.

  • 메모리 관리방식

    가상메모리를 지원하고 동적 메모리 할당을 해주어 사용자가 리소스 관리에 신경을 덜 쓸 수 있도록 해주던 일반적인 OS와 달리
    RTOS는 주로 정적 메모리 할당을 사용하고, 동적 할당은 최소화 한다.
    일부 RTOS는 heap_1 ~ heap_5 등 단순한 메모리 할당기만 제공하며, 가상메모리가 없기 때문에, 메모리 보호 및
    조각화 관리를 직접 설계해야한다.
    즉 , 메모리가 동적으로 할당되고 빠지면서 생기는 fragmentation 문제를 회피, 최소화 할 수 있다.

  • 디바이스 드라이버

    I/O 작업을 필요로 할 때, 일반적인 OS는 Block I/O Burst 가 있는 작업을 허용한다. (왜? 프로세스도 많고 멀티태스킹이 잘 되어있음)
    RTOS는 block이 되는 조그마한 시간도 예측 가능해야한다, RTOS는 프로세스가 없기 때문에 Task가 Block 상태로 대기, 이 Task의 우선순위가 가장 높다면 전체 시스템이 Block된다, 이는 곧 실시간성이 무너진다는 소리이다.

    RTOS의 해법 -> Non-Block , Event-driven 설계 , 선점형 스케쥴링 , 타임아웃 기반 I/O 대기
    이는 모두 예외상황을 사용자가 예측하고 정의해두어야한다는 뜻이다.

  • ISR (인터럽트 처리)

    위에서 말했듯이, I/O 지연이 있어서는 안된다. 그렇기 때문에 Event-driven 설계를 통해, ISR은 최소화 시킨다.
    예시) 인터럽트 수신 → ISR 진입 → Task 알림 전송 (vTaskNotifyGiveFromISR()) → 빠르게 종료 → 컨텍스트 전환 (portYIELD_FROM_ISR())

  • 요약

RTOS는 멀티태스킹 환경을 제공하지만, MMU가 없어 모든 Task가 단일 주소 공간을 공유하며, 하나의 Task가 Block 상태가 되면 스케줄러가 우선순위 Task를 실행하지 못할 수 있기 때문에, 모든 입출력은 비동기적이고 예측 가능한 방식으로 처리되어야 한다.

예제로 보는 흐름 (ESP-IDF freeRTOS)

  • 예제 (ESP32 , 애노드 타입 LED 활용한 Blink led 예제)

  • 전역변수 및 define 정의

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

#define LED_PIN GPIO_NUM_2
gpio_config_t io_conf;
  • RTOS가 사용할 Task 를 정의한다.
void blink_test(void* pvParameter)
{
	while(1)
    {
    	gpio_set_level(LED_PIN, 0); // 애노드 타입 -> 0 : LED ON
        vTaskDelay(pdMS_TO_TICKS(500));
        gpio_set_level(LED_PIN, 1); // 1 : LED OFF
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}
  • 진입점 함수 app_main 정의하기
void app_main(void)
{
	// GPIO 설정 및 적용
	io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = (1ULL << LED_PIN);
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);
    
    // Task 등록 및 스케쥴러 실행
    xTaskCreate(blink_test, "blink_task", 2048, NULL, 5, NULL);
    vTaskStartScheduler();
}
  • build를 위한 기초 CMake 구성

    • 디렉터리 구성

      root/
         main/
            main.c
            CMakeLists.txt
         CMakeLists.txt
    • root/CMakeLists.txt

      
      cmake_minimum_required(VERSION 3.16)	#Cmake 버전 명시
      
      include($ENV{IDF_PATH}/tools/cmake/project.cmake)
      project(blink_led)
    • root/main/CmakeLists.txt

      idf_component_register(
       SRCS "main.c"
       REQUIRES driver freertos
      			)
  • H/W 구성

요약

  • 흐름
    스케쥴러 실행 -> Task 가 하나밖에없음. 즉, 우선순위 상관X , 0.5초 마다 Block 상태 -> Idle Task 실행
  • 결과
    LED의 Blue 색상 0.5 초 간격으로 깜빡거리며 점등함.

부록, freeRTOS 메서드 정리

Task 제어

메서드파라미터설명
xTaskCreate()(TaskFunction_t pxTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask)Task 생성
vTaskDelay()(TickType_t xTicksToDelay)지정한 Tick 수만큼 현재 Task 지연
vTaskDelayUntil()(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement)주기적인 Task 실행 유지
vTaskStartScheduler()(void)스케줄러 시작
vTaskSuspend() / vTaskResume()(TaskHandle_t xTaskToSuspend)Task 일시정지 / 재개
vTaskDelete()(TaskHandle_t xTaskToDelete)Task 삭제

Task 알림

메서드파라미터설명
xTaskNotifyGive()(TaskHandle_t xTaskToNotify)Task에 알림 전송 (ISR에서도 사용 가능)
ulTaskNotifyTake()(BaseType_t xClearCountOnExit, TickType_t xTicksToWait)알림 대기 (수신 시 자동 감소)
xTaskNotify()(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction)Task에 값 전달 및 알림
xTaskNotifyWait()(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait)조건 기반 알림 대기

Queue

메서드파라미터설명
xQueueCreate()(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)Queue 생성
xQueueSend() / xQueueSendToBack()(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)Queue에 메시지 전송
xQueueReceive()(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)Queue에서 메시지 수신
xQueuePeek()(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)제거 없이 메시지 확인
uxQueueMessagesWaiting()(QueueHandle_t xQueue)대기 중인 메시지 수 확인

세마포 / 뮤텍스

메서드파라미터설명
xSemaphoreCreateBinary()(void)Binary Semaphore 생성
xSemaphoreCreateMutex()(void)Mutex 생성
xSemaphoreTake()(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)자원 점유 시도
xSemaphoreGive()(SemaphoreHandle_t xSemaphore)자원 반환

Timer

메서드파라미터설명
xTimerCreate()(const char * const pcTimerName, TickType_t xTimerPeriodInTicks, UBaseType_t uxAutoReload, void *pvTimerID, TimerCallbackFunction_t pxCallbackFunction)Timer 생성
xTimerStart() / xTimerStop()(TimerHandle_t xTimer, TickType_t xTicksToWait)Timer 시작 / 정지
xTimerChangePeriod()(TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xTicksToWait)주기 변경
xTimerReset()(TimerHandle_t xTimer, TickType_t xTicksToWait)타이머 리셋

ISR 전용

메서드파라미터설명
xQueueSendFromISR()(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken)ISR 내에서 Queue 전송
xSemaphoreGiveFromISR()(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken)ISR 내에서 Semaphore 해제
vTaskNotifyGiveFromISR()(TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken)ISR 내에서 Task 알림 전송
portYIELD_FROM_ISR()(BaseType_t xHigherPriorityTaskWoken)ISR 종료 시 컨텍스트 전환 유도

메모리 관리

메서드파라미터설명
pvPortMalloc()(size_t xSize)동적 메모리 할당
vPortFree()(void *pv)메모리 해제
profile
Embedded S/W Developer :)

0개의 댓글