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( ;; ); 
}

+ Recent posts