2016년 2월 2일 화요일

아두이노에서 FreeRTOS 사용하기 - 4. Mutual Exclusion (상호배제)


이번 주제인 mutual exclusion(상호 배제)는 여러 태스크가 같은 자원에 동시에 억세스 하지 못하도록 보장해 주는 메커니즘이다. 

두개의 태스크가 동시에 같은 자원(여기서는 가상의 프린터 )를 사용하는 경우 태스크가 실행되는 도중에 RTOS에 의해 선점되면 출력 결과가 아래와 같은 식으로 되어 버린다.

Printer 1: +++++++++++Printer 2: -----------------------------
++++++++++++++++++
Printer 1: Printer 2: -----------------------------
+++++Printer 2: -----------------------------
++++++++++++++++++++++++
Printer 2: -----------------------------
PrintePrinter 2: -----------------------------
r 1: ++++++++Printer 2: -----------------------------
+++++++++++++++++++++
Printer 1: Printer 2: -----------------------------
+++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++Printer 2: -----------------------------
++++++++++++++
Printer 1: ++++++Printer 2: -----------------------------
+++++++++++++++++++++++
Printer 2: -----------------------------

위의 결과가 나온 코드이다.

#include <FreeRTOS_AVR.h>

#define MS(x) ((unsigned long)(x)/portTICK_PERIOD_MS)

void vPrint(void *);

void setup()
{
  Serial.begin(115200);

  xTaskCreate(vPrint, "Printer1", 200, (void *)"Printer 1: +++++++++++++++++++++++++++++", 1, NULL);
  xTaskCreate(vPrint, "Printer2", 200, (void *)"Printer 2: -----------------------------", 2, NULL);

  vTaskStartScheduler();
  while (1) ;
}

void loop()
{
}

void vPrint(void *pvParameters)
{
  unsigned char led_state = 0;

  while (1) {
    printer((char *)pvParameters);
    vTaskDelay(MS(random(100)));
  }
}

void printer(char *p)
{
  while (*p != '\0') {
    Serial.print(*p++);
    delay(1);
  }
  Serial.println(" ");
}

하지만 원하는 것은 한 태스크가 프린터로 출력하는 동안 다른 태스크가 방해하지 못하게 하는 것이다.

Printer 1: +++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++

이런식으로 출력되게 하기 위해 한 태스크가 프린터를 사용하는 동안 다른 태스크는 프린터에 접근하지 못하고 대기하게 하기 위해 MUTEX를 사용한다.

#include <FreeRTOS_AVR.h>

#define MS(x) ((unsigned long)(x)/portTICK_PERIOD_MS)

SemaphoreHandle_t xMutex;

void vPrint(void *);

void setup()
{
  Serial.begin(115200);

  xMutex = xSemaphoreCreateMutex();
  if (NULL != xMutex) {
    xTaskCreate(vPrint, "Printer1", 200, (void *)"Printer 1: +++++++++++++++++++++++++++++", 1, NULL);
    xTaskCreate(vPrint, "Printer2", 200, (void *)"Printer 2: -----------------------------", 2, NULL);

    vTaskStartScheduler();
  } else {
    Serial.println("Create mutex failed.");
  }
  while (1) ;
}

void loop()
{
}

void vPrint(void *pvParameters)
{
  unsigned char led_state = 0;

  while (1) {
    xSemaphoreTake( xMutex, portMAX_DELAY);       // block forever until the semaphore is given
    // Critical section start
    printer((char *)pvParameters);
    // Critical section end
    xSemaphoreGive( xMutex );
    vTaskDelay(MS(random(500)));
  }
}

void printer(char *p)
{
  while (*p != '\0') {
    Serial.print(*p++);
    delay(1);
  }
  Serial.println(" ");
}

위의 코드에서 빨간색으로 표시된 부분을 Critical Section(임계영역)이라고 한다. 여러 태스크가 공유하지만 동시에 억세스하면 안되는 자원을 억세스 하는건 critical section 내에서만 해야 한다. C.S.는 xSemaphoreTake()로 시작하고 xSemaphoreGive()로 끝난다. 한 태스크가 C.S. 내에 있는 코드를 실행하고 있는 동안 다른 태스크가 C.S. 안으로 들어가려고 하면 xSemaphoreTake()에서 블럭되어 현재 C.S. 내에서 실행중이 태스크가 빠져 나오면서 xSemaphoreGive()를 호출할 때 까지 sleep 상태로 대기하게 되므로 동시에 두개 이상의 태스크가 C.S. 내로 진입할 수 없게 된다.  
 
 
1. CS에 아무 태스크도 진입하지 않은 상태

 2.태스크1이 xSemaphoreTake() 를 호출해 키를 가지고 CS에 진입

 3. 태스크1이 CS 내부에서 공유자원인 프린터를 사용

 4. 태스크1이 아직 CS 내에 있는데 태스크2도 CS에 진입하기 위해 xSemaphoreTake()를 호출했으나 키가 없어 CS에 진입하지 못하고 sleep 상태로 키가 돌아오기를 대기


 5. 태스크1이 CS에서 빠져나오며 xSemaphoreGive()를 호출해 키를 돌려줌

 6. Sleep상태로 기다리던 태스크2가 키가 돌아왔으므로 깨어나 CS에 진입
 
 
위의 예제에서는 태스크간에 공유하는 자원으로 프린터를 예로 들었지만 그 이외에 서로 공유하는 전역변수들 역시 억세스 할 때는 C.S. 내에서 해야 한다. 
 
그리고 태스크가 C.S.  내에 들어 있는 동안 그 C.S.에 진입하려고 하는 태스크는 블럭되어 실행되지 못하므로 ISR과 마찬가지로 C.S. 내에서도 가능한 빨리 작업을 마치고 빠져 나와야 한다.

댓글 없음:

댓글 쓰기