[FreeRTOS] Posix_gcc Demo 구조 분석

심준석·2025년 1월 20일
1

FreeRTOS

목록 보기
2/2

1. FreeRTOS Coding Rule

코드 구조 분석을 진행하기 앞서, FreeRTOS에는 중요한 코딩룰을 먼저 알아보자.

1-1. 변수명

FreeRTOS에서는 변수명 결정시, 타입에 따라 아래와 같은 접두어를 사용한다

  • c : char Type
  • s : int16_t Type (short)
  • i : int32_t Type (int)
  • x : BaseType_t Type, 주로 구조체나 인스턴스 핸들 등 일반적인 타입을 제외하면 대부분 x
  • u : unsigned
  • p : pointer

1-2. 함수명

  • v : void, 반환값이 없는 함수를 의미
  • x : 변수명의 접두어 x와 같은 의미, BaseType_t를 반환하는 함수
  • pv : void* 타입을 반환하는 함수
  • prv : private 함수, 대표적으로 아무런 task도 실행되지 않을 때 실행되는 idle task가 호출하는 callback 함수인 hook가 이 접두어를 사용하고 있음

2. Handle

내부적으로 생성된 객체를 관리하고 식별하기 위한 포인터 타입, 보통 Task, Queue, Semaphore, Timer 등과 같은 RTOS 객체를 가리키는 참조(포인터) 형태의 자료 구조.

  • TaskHandle_t : Task를 식별하는 데 사용되는 핸들
  • QueueHandle_t : Queue를 식별하는 데 사용되는 핸들
  • SemaphoreHandle_t : Semaphore(Mutex 등)를 식별하는 데 사용되는 핸들
  • TimerHandle_t : 소프트웨어 타이머를 식별하는 데 사용되는 핸들

3. Task API

FreeRTOS에서 제공해주는 Task 관련 API가 있다. 대표적으로 몇가지 알아보자면,

3-1. xTaskCreate()

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
						const char * const pcName,
                        unsigned short usStackDepth,
                        void *pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t *pxCreatedTask)
  • pvTaskCode : task의 기능이 선언된 함수의 함수 포인터
  • pcName : 디버깅 용도로 사용하는 문자열이며, task의 이름을 말함
  • usStackDepth : task마다 할당되는 stack 메모리를 말하며, 단위는 WORD. 일반적으로 사용하는 ARM Cortex-M 보드에서는 1WORD == 4Byte
  • pvParameters : task 함수로 전달할 매개변수, 없으면 NULL 사용. 전달할 매개변수를 (void*) 타입으로 캐스팅한 뒤 여기다가 넣어주면 task 함수에서 사용할 수 있음
  • uxPriority :
  • *pxCreatedTask : task를 제어하기 위한 TaskHandle_t 타입 핸들, Task의 우선순위를 바꾸거나, task를 멈추거나 등 task에 대한 설정은 모두 이 핸들을 통해 이루어짐.

(예시)

xTaskCreate( prvQueueReceiveTask,				// Task 함수, 실제 실행될 로직
			 "RX",								// 디버깅 시 확인할 수 있는 이름, 커널 내부에서 직접 쓰이지는 않음
             configMINIMAL_STACK_SIZE,			// 스택 크기
             NULL,								// 입력 파라미터, Task가 필요로 한다면 다른 파라미터를 넣어 사용할 수 있음
             mainQUEUE_RECEIVE_TASK_PROIRITY,	// 우선순위, 어느 Task를 더 자주/빨리 실행할지 결정, 우선순위가 높을수록 CPU 점유 기회가 많아짐
             NULL );							// Null을 넘기면 Task 핸들을 별도로 받지 않음, Task 핸들이 필요하면 변수로 받아서 vTaskDelete()등에 활용 가능

3-2. vTaskDelay()

void vTaskDelay( TickType_t xTicksToDelay );
  • Task의 상태를 Running에서 Blocked(waiting)으로 변경하는 함수
  • 설정된 시간 xTicksToDelay 동안 Task는 blocked Task가 되며, 다음 우선순위를 가진 task가 실행됨
  • 확정성 및 이식성 좋은 코드를 만들기 위해서 tick 단위 시간을 사용하는 것 보다 pdMS_TO_TICKS() 매크로를 사용해서 ms 단위를 tick으로 변환해서 사용하는 것이 좋음

3-3. vTaskDelayUntil()

  • vTaskDelay()와 똑같은 기능을 수행하지만, vTaskDelay()는 호출 시점부터 지정된 시간만큼 blocked 되는 반면,
  • vTaskDelayUntile()은 호출 시점과 관계없이 목표 절대 시간 주기에 맞춰 blocked 됨

3-4. vTaskSuspend(), vTaskResume()

  • Delay 함수들은 Task를 일정 시간동안 blocked state로 만들지만, suspend 함수는 task를 기약없이 blocked state로 만듬 (resume 함수를 사용할 때 까지)
  • Task의 우선순위를 바꿀때, Task를 일단 blocked state로 만들기 위해 사용하며, 인자값으로 task의 handle이 들어감

3-5. vTaskPrioritySet()

  • Task의 우선순위를 바꿀때 사용하는 API

4. Posix_gcc Demo : main_blinky()

드디어 main_blinky()를 분석해보자.

main_blinky()는 Posix_gcc Demo의 main() 함수에 정의된 mainSELECTTED_APPLICATION = 0 일 때, 호출되는 함수이다.

4-1. Signal 처리 함수 호출

main 함수가 호출됨가 동시에, 먼저 시그널 처리 함수를 등록한다.

signal( SIGINT, handle_sight);
  • signal 함수는 시그널 처리 함수를 등록하는 함수로, SIGINT는 일반적으로 프로그램 실행 중, 키보드로부터 인터럽트가 들어왔을 때 발생하는 시그널

  • 프로그램 실행 중, 사용자가 Ctrl+C를 눌러 종료 신호를 보냈을 때 바로 종료되는 대신 handlo_sight 함수에서 필요한 동작(자원 정리 및 로그 출력 등)을 수행하도록 설정해주는 코드

4-2. Mutex(뮤텍스) 생성

그 다음으로는 콘솔(또는 표준 입출력) 사용을 위한 뮤텍스(Mutex)를 생성하는 함수 console_init()을 호출한다.

console_init();

해당 함수에서는 FreeRTOS에서 제공하는 xSemaphtoreCreateMutex() 또는 xSemaphtoreCreateMutexStatic() 함수를 사용해, 멀티태스킹 환경에서 여러 Task가 동시에 콘솔 입출력을 수행할 때 충돌이 일어나지 않도록 보호한다.

void console_init(void)
{
	#if (configSUPPORT_STATIC_ALLOCATION == 1)
    {
    	xStdioMutex = xSemaphoreCreateMutexStatic( &xStdioMutexBuffer );
    }
    #else
    {
    	xStdioMutex = xSemaphoreCreateMutex();
    }
    #endif
}

4-3. main_blinky()

mainSELECTED_APPLICATION = 0이라는 가정하에, 이제 main_blinky() 함수로 진입한다. 해당 함수 안에는 Timer 및 Task, Scheduler 등이 선언되어, main 함수에서 실질적인 동작을 수행한다.

4-3-1. 타이머 동작 주기 설정

먼저, 변수 타이머 동작 주기 변수 xTimerPeriod는 다음과 같이 선언된다.

const TickType_t xTimerPeriod = mainTIMER_SEND_FREQUENCY_MS;

이때, mainTIMER_SEND_FREQUENCY_MS

#define mainTIMER_SEND_FREQUENCY_MS		pdMS_TO_TICKS(2000UL)

로 정의된다.

즉, 매크로 pdMS_TO_TICKS를 통해 1 Tick을 2000ms으로 정의하고 (pdMS_TO_TICKS(2000UL)),

타이머 동작 주기를 1 Tick, 즉 2000ms로 설정해주는 과정이다.

4-3-2. Queue 생성

xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ));

xQueueCreate 함수로, Queue를 생성해주는 과정이다.
생성된 Queue인 xQueue는 길이가 mainQUEUE_LENGTH이고, 각 element의 크기는 32bit 정수 크기인 형태로 생성된다(sizeof( uint32_t)))

4-3-3. Task 생성

해당 데모에서는 2개의 Task를 생성한다. Task를 생성하기 위해선, xTaskCreate API를 사용한다.

하나는 Queue로부터 데이터를 받아 메시지를 생성해주며,

xTaskCreate( prvQueueReceiveTask,				// Task 함수, 실제 실행될 로직
			 "RX",								// 디버깅 시 확인할 수 있는 이름, 커널 내부에서 직접 쓰이지는 않음
             configMINIMAL_STACK_SIZE,			// 스택 크기
             NULL,								// 입력 파라미터, Task가 필요로 한다면 다른 파라미터를 넣어 사용할 수 있음
             mainQUEUE_RECEIVE_TASK_PROIRITY,	// 우선순위, 어느 Task를 더 자주/빨리 실행할지 결정, 우선순위가 높을수록 CPU 점유 기회가 많아짐
             NULL );							// Null을 넘기면 Task 핸들을 별도로 받지 않음, Task 핸들이 필요하면 변수로 받아서 vTaskDelete()등에 활용 가능

그리고, 다른 하나는 Tick을 Count한 값을 Queue에 값을 전송하는 Task이다.

xTaskCreate( prvQueueSendTask,
			 "TX",
             configMINIMAL_STACK_SIZE,
             NULL,
             mainQUEUE_SEND_TASK_PRIORITY,
             NULL );

4-3-4. 소프트웨어 타이머 생성

그리고 그 다음으로 소프트웨어 타이머를 생성해준다.

xTimer = xTimerCreate( "Timer",							//타이머 이름, 디버깅용
						xTimerPeriod,					//타이머 주기
                        pdTRUE,							//pdTRUE면, 타이머가 만료될 떄마다 자동으로 다시 시작, pdFALSE면 한번 만료된 후 자동으로 멈춤
                        NULL,							//타이머 ID, NULL이므로 별도로 ID를 저장하진 않음. 여러 타이머를 관리할 때 식별이 필요하다면 다른 값을 넣을 수 있음
                        prevQueueSendTimerCallback	);	//콜백 함수 : prvQueueSendTimerCallback이 타이머가 만료될 때마다 실행하는 함수를 가리킴

4-3-5. 타이머 시작

타이머가 정상적으로 생성된 경우, xTimerStart() 함수를 통해 타이머 시작

if( xTimer!=NULL )
{
	xTimerStart( xTimer, 0 );
}

4-3-6. 스케줄러 시작

vTaskStartScheduler();

모든 Task와 타이머가 생성된 뒤, RTOS 스케줄러를 시작하면, 등록된 Task들이 우선순위와 준비 상태(Ready State)에 따라 CPU를 할당받아 실행된다.

등록된 소프트웨어 타이머도 FreeRTOS 타이머 서비스(Task)에 주기적으로 콜백이 호출되고, 스케줄러가 시작된 후에는 main() 함수로는 다시 돌아오지 않는 것이 일반적이다.

*참고 : RTOS 스케줄러가 시작되면, 내부적으로 무한 반복 구조로 동작한다.
따라서, 일반적인 C프로그램처럼 While(1) 루프를 직접쓰지 않아도, RTOS 스케줄러가 각 Task를 무기한으로 스케줄링하고 실행해준다.

5. 마치며

가장 기초적인 예제를 통해, FreeRTOS에서 Task를 생성하는 방법, API를 사용하는 방법 등을 알아봤다. 다음 글에는 Posix_gcc에서 두번째 실행 함수 main_full() 함수를 분석하겠다.

profile
Developer & Publisher 심준석 입니다.

0개의 댓글

관련 채용 정보