1) Mutexes

뮤텍스는 공유 자원에 대한 접근을 제어하기 위한 동기화 도구, 뮤텍스는 한 번에 하나의 태스크만 자원에 접근할 수 있도록 보장하는 역할을 한다.

뮤텍스는 공유 리소스에 대한 액세스를 제어하는 데 사용되는 특수한 유형의 바이너리 세마포어이다

바이너리 세마포어와 다른 점 : 뮤텍스는 우선순위 역전 현상을 예방해줄 메커니즘을 포함

xSemaphoreHandle = xSemaphoreCreateMutex();            // 뮤텍스 생성

if (xSemaphoreTake(xMutex, portMAX_DELAY)) { // 뮤텍스 획득
    // 자원 사용
    xSemaphoreGive(xMutex);                  //뮤텍스 반환
}
  • 뮤텍스는 우선순위 역전 문제 방지를 위해 우선순위 상속 메커니즘을 제공

  • xSemaphoreCreateMutex(); 에서 NULL 반환 시 힙 영역 부족하다는 뜻

  • 뮤텍스를 태스크 A에서 사용중일 경우, 뮤텍스에 접근한 태스크 B는 Blocked state로 전환

  • 뮤텍스가 반환되면 B의 Blocked state 종료

2) Binary Semaphores

0 과 1로만 이뤄진 semaphore다. Critical section에 대해 p연산을 수행하면 semaphore가 0이 되어서 다른 task의 접근을 막는다.

접근을 요청한 task는 blocked 상태가 되며 들어갈 때 까지 기다린다.

  • take 함수에서 기다리는 시간을 설정
    • xSemaphoreGive();
    • xSemaphoreTake();

3) Priority Inheritance (우선순위 상속)

일시적으로 나의 우선순위(높은)를 주어서 Inversion 문제를 해결

해결 방법

  • 리소스를 가진 LP의 우선순위를 HP 수준으로 일시적으로 끌어올려 줌
  • 따라서 MP가 HP보다 먼저 실행되지 않게 한다.

4) Deadlock (or Deadly Embrace)

둘 이상의 태스크가 서로 가진 자원을 기다리며 무한 대기 상태에 빠지는 것

  • Task A는 Mutex X를 점유하고 Mutex Y를 기다림
  • Task B는 Mutex Y를 점유하고 Mutex X를 기다림
  • → 서로 양보하지 않아서 stuck

해결 방법

  • 리소스를 항상 같은 순서로 획득하도록 설계
  • 타임아웃 설정
  • 재귀 뮤텍스(Recursive Mutex) 사용 (아래 설명)

5) Recursive Mutexes (재귀적 뮤텍스)

xSemaphoreHandle mutex = xSemaphoreCreateRecursiveMutex();

xSemaphoreTakeRecursive(mutex);  
xSemaphoreTakeRecursive(mutex); // 같은 태스크이기 때문에 가능 (재귀)

xSemaphoreGiveRecursive(mutex);  
xSemaphoreGiveRecursive(mutex); // 두번 반환해야 완전 해제
  • 하나의 태스크가 같은 뮤텍스를 여러 번 획득 할 수 있도록 허용
  • 일반 Mutex는 한 번만 획득 가능 → 중첩 호출 시 문제 발생
  • 재귀 뮤텍스는 획득 횟수를 기억하고, 같은 수만큼 반환해야 진짜 반환됨

6) Mutexes and Task Scheduling

  • Mutex는 공유 자원 보호뿐 아니라 태스크 스케줄링에 간접적 영향도 줌
  • 예: 높은 우선순위 태스크가 뮤텍스를 기다리면 블로킹 상태로 빠지고, 그 사이 낮은 우선순위 태스크가 실행될 수 있음

→ 뮤텍스는 단순한 동기화 수단이 아니라, RTOS 스케줄링의 흐름까지 영향을 줌

7) Gatekeeper Tasks

  • 특정 리소스에 대한 단독 소유권을 갖는 태스크
  • 다른 태스크는 해당 리소스를 직접 접근하지 않고, Gatekeeper를 통해 간접 접근
  • Example 21 : gatekeeper 태스크를 사용하도록 vPrintString() 재구현(tick hook 함수를 이용해 200틱마다 메세지 전송)
    • Gatekeeper는 큐(xPrintQueue) 를 통해 문자열 포인터를 수신
    • 출력 요청 태스크는 printf 직접 호출 대신, 메시지를 큐에 전송
    • Gatekeeper는 메시지를 수신하면 printf로 출력, 이후 다시 대기
    • 대부분 시간은 xQueueReceive() 에서 Blocked 상태로 대기
    • Tick Hook
    • vApplicationTickHook() 사용 → 200틱마다 문자열 전송
    • 사용 전 FreeRTOSConfig.h에서 configUSE_TICK_HOOK = 1 설정 필요

Tick Hook 주의사항

    • Tick ISR 컨텍스트에서 실행됨
    • 코드 짧게 작성, 스택 사용 최소화
    • FromISR 계열 함수만 사용 가능
    • pxHigherPriorityTaskWoken → 사용하지 않으면 NULL로 설정 가능
   { 
    char *pcMessageToPrint; 
  for( ;; ) 
  { 
    xQueueReceive( xPrintQueue, &pcMessageToPrint, portMAX_DELAY ); 

    printf( "%s", pcMessageToPrint ); 
    fflush( stdout ); 
  } 
} 
static void prvPrintTask( void *pvParameters ) 
{ 
  int iIndexToString; 
  const TickType_t xMaxBlockTimeTicks = 0x20; 

  iIndexToString = ( int ) pvParameters; 
  for( ;; ) 
  { 

    xQueueSendToBack( xPrintQueue, &( pcStringsToPrint[ iIndexToString ] ), 0 ); 

    vTaskDelay( ( rand() % xMaxBlockTimeTicks ) ); 
  } 
} 
void vApplicationTickHook( void ) 
{ 
  static int iCount = 0; 

  iCount++; 
  if( iCount >= 200 ) 
  { 
    xQueueSendToFrontFromISR( xPrintQueue, &( pcStringsToPrint[ 2 ] ), NULL ); 
    iCount = 0; 
  } 
} 
static char *pcStringsToPrint[] = 
{ 
  "Task 1 ****************************************************\r\n", 
  "Task 2 ----------------------------------------------------\r\n", 
  "Message printed from the tick hook interrupt ##############\r\n" 
}; 

QueueHandle_t xPrintQueue; 

int main( void ) 
{ 

  xPrintQueue = xQueueCreate( 5, sizeof( char * ) ); 

  if( xPrintQueue != NULL ) 
  { 
    xTaskCreate( prvPrintTask, "Print1", 1000, ( void * ) 0, 1, NULL ); 
    xTaskCreate( prvPrintTask, "Print2", 1000, ( void * ) 1, 2, NULL ); 

    xTaskCreate( prvStdioGatekeeperTask, "Gatekeeper", 1000, NULL, 0, NULL ); 

    vTaskStartScheduler(); 
  } 

  for( ;; ); 
}
  • 멀티태스킹 시스템에서 여러 Task 나 ISR이 공유 자원에 동시에 접근
    → Data Corruption이 발생 가능
    1. Accessing Peripherals (주변장치 접근)
      • 여러 Task가 동시에 UART, SPI, I2C에 접근
      • 출력 충돌, 데이터 손상, 통신 오류
    2. Read-Modify-Write Operations (읽기-수정-쓰기 연산)
      • count++, total += value 같은 연산은 다단계 연산
      • context switching 발생 시 값이 꼬임
    3. Non-atomic Access to Variables (비원자적 변수 접근)
      • 32비트 변수나 구조체 등 멀티바이트 자료를 여러 Task가 접근
      • 중간 값이 읽히거나 쓰이면 예상과 다른 결과
    4. Function Reentrancy (재진입성 문제)
      • printf, rand 같은 함수는 thread-safe 하지 않음
      • 여러 Task가 동시에 호출하면 내부 버퍼 충돌
  • freeRTOS document에서 multitasking system의 오류 발생 포인트를 정리해보았다.
  • FreeRTOS 시스템에서는 Task 뿐만 아니라 ISR도 공유자원에 접근 가능
    → 위험성 증가 (ISR이 Task를 가로채 실행될 수 있음)

    ⇒ Mutual Exclusion 기법으로 관리되어야 함
  • mutual exclusion : 한 시점에 단 하나의 Task 또는 ISR만 자원에 접근 가능하게 제어
    • 자원 보호 및 데이터 무결성 유지
  • FreeRTOS 시스템에서 다양한 mutual exclusion 도구를 제공

→ 가장 간단하고 안전한 방법은 공유 자원이 없도록 설계하는 것
→ 하나의 자원은 하나의 Task만 접근!

다음은 위 4 방법 중 Critical Section을 만드는 두 가지 방법이다.

Critical section

인터럽트 응답 시간에 영향을 끼치므로, Critical Section은 짧아야 한다.

  • taskENTER_CRITICAL / taskEXIT_CRITICAL 함수
    • taskENTER_CRITICAL
    • critical section 시작
    • taskEXIT_CRITICAL
    • critical section 종료

두 함수 모두 입력 인자와 반환 값이 없다.

함수명이 FromISR로 끝나지 않으며, ISR로 호출되는 것만은 아니다.

⇒ ISR로부터 접근이 가능하기 때문에, 완전한 보호가 되지 않는다.

  • taskENTER_CRITICAL_FROM_ISR / taskEXIT_CRITICAL_FROM_ISR 함수

앞선 함수에서 interrupt safe 버전이다.

  • taskENTER_CRITICAL_FROM_ISR
  • taskEXIT_CRITICAL_FROM_ISR→ ENTER 함수의 반환 값과 일치해야만 한다.
  • 입력 인자로서 ENTER의 반환 값을 받는다.

Suspending (or Locking) the Scheduler

Scheduler에 lock을 걸어 선점을 막는다.

  • vTaskSuspendAll / vTaskResumeAll 함수
    • vTaskSuspendAll
      • return 값
      • 스케줄러가 suspend 된 동안 요청된 context switch는 보류 상태로 남는데, 스케줄러가 재개될 때 실행된다. 보류된 context switch가 스케줄러 재개 전에 수행되면 pdTRUE를 반환하고, 그렇지 않으면 pdFALSE를 반환한다.
    • 스케줄러를 suspend 한다.
    • vTaskResumeAll
    • 스케줄러 Suspend 해제시킨다

라운드 로빈 스케줄링과 우선순위 스케줄링

라운드 로빈 스케줄링 예시

태스크 간 우선 순위 없이, 일정 시간 간격마다 돌아가며 실행하는 스케줄링
여기서는 Time tick으로 일정 시간 간격마다 실행 태스크를 바꾼다.

freeRTOS 레퍼런스를 참고하여, 같은 우선 순위 두 개 태스크로 실행해보는 예시를 연습해본다.
uart로 태스크마다의 메시지를 출력하며 일정시간씩 번갈아 실행하고있음을 확인합니다.

 

 

우선 순위 스케줄링

우선 순위 높은 태스크가 readylist(준비된태스크)앞에 놓인다.
그래서 두 개 태스크를 생성하고, 우선 순위가 다르다면 높은 순위의 태스크만 계속 돌아가고, 끝나고나서야
낮은 순위의 태스크가 실행됩니다.

스케줄링 시 태스크의 상태에는
Running(실행 중),Ready(준비되어 실행 대기),Block(wait,sleep하며 이벤트를 기다리는 상태),Suspend(실행 중단된 상태)가 있다.

 

 

Not Running State 종류

1. Block State

실행 시간 한 주기가 끝났거나, Delay함수를 호출하게 된 경우 이벤트를 기다리는 상태로 sleep하게 된다.
wait한다고도 한다.
스케줄러가 이 태스크를 준비시커나, 인터럽트나 다른 태스크에 의해 호출받는 등의 이벤트를 기다린다.

Delay를 주어(태스크1에 100ms,태스크2에 1000ms),태스크를 잠시 block 상태에 놓이게 한 후, 실행해보았습니다.

 

출력 결과

 

 

2. Suspend State

스케줄러의 동작을 멈추는 것이다.

⇒컨텍스트 스위칭을 일시적으로 막는다.

태스크 레벨에서의 스케줄링은 멈추지만, 인터럽트 서비스 루틴(ISR)은 실행 가능한 상태

 

 

3. Ready State

이벤트 발생 후 cpu 점유 대기 상태이다.
코드를 들어가면, 같은 순위의 태스크들끼리 더블링크드 리스트로 연결되어, readylist에 저장되어있다.(이전 글 마지막참고)

 

 

Create, Delete, argument 이용해보기

Task1 : argument를 이용해 0~9까지의 숫자를 랜덤 출력(20개씩출력해 확인)
전역으로 int* 타입 argument를 선언

int하나를 넣을 메모리 할당 후 arg파라미터로 전달한다.

Task1에서 Task2생성한다.

Task2 : Task1에서 생성되어 0을 출력하고 자기삭제를 반복

출력 결과

 

 

[8freeRTOS]161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
4.54MB

freeRTOS 공부를 시작하며 태스크에 대한 내용부터 필기합니다.

CMSIS (Cortex(Common) Microcontroller Software Interface Standard)

CubeIDE에서 freeRTOS를 사용할때,xtaskCreate,vtaskxx,pxreadylist~ 등 freeRTOS함수를 감싸는 ostaskCreate등의 CMSIS함수들이 있다.

FreeRTOS API에 기반한 CMSIS-RTOS를 사용하여 스레드(태스크)를 생성한다

Application: CMSIS-OS API만 사용

Wrapper: CMSIS-OS API 호출을 FreeRTOS 네이티브 호출로 변환

FreeRTOS Kernel: 실제 스레드 관리·동기화 실행

CMSIS 사용하는 이유 : 여러가지 펌웨어 버전들을 표준화하여 OS에서 API 호출방법을 편리하게 할 수 있도록 한다.
단점 : 함수 안에 freeRTOS 함수를 또 감싸며 호출하고 있어,코드 속도나 효율성면에서는 떨어진다.

Task 생성

아래에 osThreadCreate에는 여러 선택지 중 Static과 dynamic이 있는데,

크게 나누면 RAM에서 TCB의 공간과 스택 공간이 있는데, Static은 사용자가 직접 스택 메모리 배열을 선언해서 사용하고,

Dynamic은 내부의 malloc함수를 이용해 힙 메모리를 할당해 사용한다. FreeRTOS/Source/portable/MemMang/heap4.c에서 확인 가능.

osThreadCreate(CMSIS -RTOS API)
생성할 스레드(태스크)를 정의하고, xTaskCreate함수를 호출하여 생성한다.

Create의 파라미터를 →Handle이 전달한다.

cmsis_os.c의 Handle은 곧 tasks.c의 tskTaskContrlBlock 구조체포인터이다.

os_thread_def 구조체

xTaskCreate(freeRTOS)

xTaskCreate(
    vTaskFunction,        // Task 함수 포인터(함수명)
    "UART Task",          // Task 이름(디버깅 용도)
    128,                  // Task에 할당할 Stack의 깊이 (타입사이즈*바이트로 워드 단위)
    NULL,                 // Task함수로 전달할 사용자 정의 매개 변수 (void*포인터) 다양한 목적에 따라 구조체/포인터 전달.
    2,                    // Task 우선순위 (0~configMAX값-1)
    &uartTaskHandle       // 우선순위 변경, 삭제 등 API 호출 가능한 핸들, 필요 없으면 NULL

반환 값 : pdPASS (생성 성공), pdFAIL(Task생성실패)

→ 이 파라미터들로 Task를 생성한다.
);

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,  
                        const char * const pcName,  
                        uint16_t usStackDepth,  
                        void *pvParameters,  
                        UBaseType_t uxPriority,  
                        TaskHandle_t *pxCreatedTask );
  1. TCB(Task Control Block) 메모리 힙에 동적 할당
  2. Task용 스택 메모리를 동적 할당
  3. 할당이 되면 Task를 init 및 TCB를 넘겨 ready 상태로 등록 후 pdPASS 성공상태 반환한다.
  4. pvPortMalloc함수 확인→FreeRTOS/Source/portable/MemMang/heap4.c

5. TaskControlBlock 구조체

  • 태스크 스택 포인터(SP)(어셈블리 접근성을 위해 가장 위에 위치)스택 포인터가 가리키는 ****컨텍스트에는 8개의 레지스터 세트로 구성된다.
  • xPSR,리턴 주소, LR(R14),R12, R3,R2,R1,R0 →이걸 MSP 또는 PSP에 푸시한다
  • 태스크가 마지막으로 실행되던 레지스터 값들(컨텍스트)은 태스크 스택(MSP,PSP)에 Push,pop으로 저장됨. 이걸 가리키는 스택 포인터를 TCB에 저장.
  • 태스크의 현재 상태 저장(정수값)(Ready,Blocked,Suspended)
  • 우선순위
  • 태스크 이름

TCB 구조체 코드

typedef struct tskTaskControlBlock /\* The old naming convention is used to prevent breaking kernel aware debuggers. _/  
{  ~~~~
//주요 내용만 발췌
ListItem_t            xStateListItem;    /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t            xEventListItem;        /*< Used to reference a task from an event list. */
UBaseType_t            uxPriority;            /*< The priority of the task.  0 is the lowest priority. */
StackType_t            *pxStack;            /*< Points to the start of the stack. */
char                pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

~~~~
} tskTCB;

이후 create된 태스크는 xStateListItem을 통해 순위에 따라 double linked list구조인 ready list에 저장된다.

 * Place the task represented by pxTCB into the appropriate ready list for
 * the task.  It is inserted at the end of the list.
 */
#define prvAddTaskToReadyList( pxTCB )                                                                \
    traceMOVED_TASK_TO_READY_STATE( pxTCB );                                                        \
    taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );                                                \
    vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
    tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
/*-----------------------------------------------------------*/

Support for Hierarchical Scheduling in FreeRTOS*

 

+ Recent posts