RealTime Operationg System
Windows , Linux 와 같은 일반적인 운영체제와 달리, 시간 제약 조건을 엄격하게 준수하여, 예측가능성과 안정성을 보장한다.
RTOS를 이해하기 위해서는 꼭 이해하고 있어야할 키워드가 몇가지 존재한다.
"스케쥴링" , "인터럽트"
이 두 키워드는 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를 실행하지 못할 수 있기 때문에, 모든 입출력은 비동기적이고 예측 가능한 방식으로 처리되어야 한다.
예제 (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;
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));
}
}
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 구성
메서드 | 파라미터 | 설명 |
---|---|---|
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 삭제 |
메서드 | 파라미터 | 설명 |
---|---|---|
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) | 조건 기반 알림 대기 |
메서드 | 파라미터 | 설명 |
---|---|---|
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) | 자원 반환 |
메서드 | 파라미터 | 설명 |
---|---|---|
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) | 타이머 리셋 |
메서드 | 파라미터 | 설명 |
---|---|---|
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) | 메모리 해제 |