esp-open-rtosを使ってみる

Mutex機能

マルチタスクのシステムを設計する際に、重要な要素として資源の排他制御が有ります。
資源の排他制御には、前回紹介したSemaphore機能を使うこともできますが、FreeRTOSではMutex機能が用意されています。

Semaphore機能とMutex機能は非常によく似た機能です。
その違いを分かる範囲で紹介します。

Semaphoreを使うためにはxSemaphoreCreateBinary()、Mutexを使うときは xSemaphoreCreateMutex()で初期化しますが、
初期化後のSemaphore値が違います。
以下のコードで違いを確認することができます。
/* The example of esp-free-rtos
 *
 * This sample code is in the public domain.
 */
#include <stdlib.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "esp8266.h"


void user_init(void)
{
  SemaphoreHandle_t xSemaphore1;
  SemaphoreHandle_t xSemaphore2;

  uart_set_baud(0, 115200);
  printf("SDK version:%s\n", sdk_system_get_sdk_version());
  printf("pcTaskGetName=%s\n",pcTaskGetName(0));

  UBaseType_t value;
  xSemaphore1 = xSemaphoreCreateBinary();
  configASSERT( xSemaphore1 );
  value = uxSemaphoreGetCount( xSemaphore1 );
  printf("xSemaphoreCreateBinary=%d\n",(int)value);

  xSemaphore2 = xSemaphoreCreateMutex();
  configASSERT( xSemaphore2 );
  value = uxSemaphoreGetCount( xSemaphore2 );
  printf("xSemaphoreCreateMutex=%d\n",(int)value);
}

Mutexの初期値は1で、保護したい資源を使う前に必ずTakeする必要が有ります。
一方、Semaphoreの初期値は0で、他のタスクに制御を渡すときにGiveします。
SDK version:0.9.9
pcTaskGetName=uiT
xSemaphoreCreateBinary=0
xSemaphoreCreateMutex=1



MutexとSemaphoreはよく似ていますが、微妙な違いがいくつかあります。
最大の違いは、Mutexは優先順位継承メカニズムを持っていますが、Semaphoreはそのような機能を持っていません。
こ ちらにその違いの説明が有りますが、私も正確には理解できなかったので実際のコードで試してみました。

最初にSemaphoreを使う以下のコードを実行します。
Mutexとの初期値の違いをなくすために、xSemaphoreCreateBinary()直後に、xSemaphoreGive()で値を 1に変更しています。
/* The example of esp-free-rtos
 *
 * This sample code is in the public domain.
 */
#include <stdlib.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "esp8266.h"

SemaphoreHandle_t xSemaphore;


//時間稼ぎ
void ConsumptionTick(int delay) {
    TickType_t startTick;
    TickType_t endTick;
    TickType_t nowTick;
    startTick = xTaskGetTickCount();
    endTick = startTick + delay;
    //printf("startTick=%d endTick=%d\n",startTick,endTick);
    while(1) {
      nowTick = xTaskGetTickCount();
      if (nowTick > endTick) break;
    }
}

void task1(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  printf("[%s:%d] Take\n",pcTaskGetName(0),nowTick);
  ConsumptionTick(1000);
  prio = uxTaskPriorityGet( NULL );
  xSemaphoreGive(xSemaphore);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Give Semaphore Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  while(1) {
    vTaskDelay(100);
  }

}

void task2(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  while(1) {
    ConsumptionTick(1000);
  }
}

void task3(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  ConsumptionTick(200);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Waiting mutex....\n",pcTaskGetName(0),nowTick);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Take Semaphore\n",pcTaskGetName(0),nowTick);
  while(1) {
    vTaskDelay(100);
  }
}


void user_init(void)
{
  uart_set_baud(0, 115200);
  printf("SDK version:%s\n", sdk_system_get_sdk_version());
  printf("pcTaskGetName=%s\n",pcTaskGetName(0));

#if 1
  /* Create Semaphore */
  xSemaphore = xSemaphoreCreateBinary();
  /* Check everything was created. */
  configASSERT( xSemaphore );
  /* Add value */
  xSemaphoreGive( xSemaphore );
#endif

#if 0
  /* Create Mutex */
  xSemaphore = xSemaphoreCreateMutex();
  /* Check everything was created. */
  configASSERT( xSemaphore );
#endif
  /* Get current value */
  UBaseType_t value;
  value = uxSemaphoreGetCount( xSemaphore );
  printf("Semaphore value=%d\n",(int)value);

  xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
  vTaskDelay(10);
  xTaskCreate(task2, "task2", 256, NULL, 4, NULL);
  xTaskCreate(task3, "task3", 256, NULL, 4, NULL);

}

まず最初に優先度=2のtask1が起動しTakeします。
次に優先度=4のtask2が起動し、全速力で動き続けます。task1は実行権を剥奪され、動くことができません。
次に優先度=4のtask3が起動し、200tick動いた後にTakeしようと試みますが、task2が動き続けているので、task1は動く ことができず
task3のTakeは永遠に待たされます。
SDK version:0.9.9
pcTaskGetName=uiT
Semaphore value=1
[task1:0] Start Priority=2
[task1:0] Take
mode : sta(18:fe:34:d4:2f:77)
add if0
[task2:160] Start Priority=4
[task3:160] Start Priority=4
[task3:361] Waiting mutex....



次にMutexを使う以下のコードを実行します。
/* The example of esp-free-rtos
 *
 * This sample code is in the public domain.
 */
#include <stdlib.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "esp8266.h"

SemaphoreHandle_t xSemaphore;


//時間稼ぎ
void ConsumptionTick(int delay) {
    TickType_t startTick;
    TickType_t endTick;
    TickType_t nowTick;
    startTick = xTaskGetTickCount();
    endTick = startTick + delay;
    //printf("startTick=%d endTick=%d\n",startTick,endTick);
    while(1) {
      nowTick = xTaskGetTickCount();
      if (nowTick > endTick) break;
    }
}

void task1(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  printf("[%s:%d] Take\n",pcTaskGetName(0),nowTick);
  ConsumptionTick(1000);
  prio = uxTaskPriorityGet( NULL );
  xSemaphoreGive(xSemaphore);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Give Semaphore Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  while(1) {
    vTaskDelay(100);
  }

}

void task2(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  while(1) {
    ConsumptionTick(1000);
  }
}

void task3(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  ConsumptionTick(200);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Waiting mutex....\n",pcTaskGetName(0),nowTick);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Take Semaphore\n",pcTaskGetName(0),nowTick);
  while(1) {
    vTaskDelay(100);
  }
}


void user_init(void)
{
  uart_set_baud(0, 115200);
  printf("SDK version:%s\n", sdk_system_get_sdk_version());
  printf("pcTaskGetName=%s\n",pcTaskGetName(0));

#if 0
  /* Create Semaphore */
  xSemaphore = xSemaphoreCreateBinary();
  /* Check everything was created. */
  configASSERT( xSemaphore );
  /* Add value */
  xSemaphoreGive( xSemaphore );
#endif

#if 1
  /* Create Mutex */
  xSemaphore = xSemaphoreCreateMutex();
  /* Check everything was created. */
  configASSERT( xSemaphore );
#endif
  /* Get current value */
  UBaseType_t value;
  value = uxSemaphoreGetCount( xSemaphore );
  printf("Semaphore value=%d\n",(int)value);

  xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
  vTaskDelay(10);
  xTaskCreate(task2, "task2", 256, NULL, 4, NULL);
  xTaskCreate(task3, "task3", 256, NULL, 4, NULL);

}

まず最初に優先度=2のtask1が起動しTakeします。
次に優先度=4のtask2が起動し、全速力で動き続けます。task1は実行権を剥奪され、動くことができません。
次に優先度=4のtask3が起動し、200tick動いた後にTakeしようと試みます。
この時、task1の優先度は一時的に4に変更され、task1とtask2は並行して動くことができるようになります。
その結果、task1のGiveが実行され、task3のTakeは成功します。
これが、Mutexの優先順位継承メカニズムです。
下の表示でも、task1がGiveする直前の優先度は4に変わっていることが分かります。
SDK version:0.9.9
pcTaskGetName=uiT
Semaphore value=1
[task1:0] Start Priority=2
[task1:0] Take
mode : sta(18:fe:34:d4:2f:77)
add if0
[task2:160] Start Priority=4
[task3:160] Start Priority=4
[task3:361] Waiting mutex....
[task1:1002] Give Semaphore Priority=4
[task3:1002] Take Semaphore

今回は、MutexとSemaphoreの違いを説明するために、わざと分かりやすいコードを書きましたが、
そもそも優先度の高いタスク(task2)が永久ループするのが間違いです。
実際のシステムで優先度の違う複数のタスクが動き始めると、いったい何が起こっているのか、分からなくなることが有ります。



1つのMutexを2つ以上のタスクがTakeしようとしたらどうなるのか、気になったので確認してみました。
最初に同じ優先度のタスクが2つTakeを試みる場合です。

/* The example of esp-free-rtos
 *
 * This sample code is in the public domain.
 */
#include <stdlib.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "esp8266.h"

SemaphoreHandle_t xSemaphore;


//時間稼ぎ
void ConsumptionTick(int delay) {
    TickType_t startTick;
    TickType_t endTick;
    TickType_t nowTick;
    startTick = xTaskGetTickCount();
    endTick = startTick + delay;
    //printf("startTick=%d endTick=%d\n",startTick,endTick);
    while(1) {
      nowTick = xTaskGetTickCount();
      if (nowTick > endTick) break;
    }
}

void task1(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  printf("[%s:%d] Take\n",pcTaskGetName(0),nowTick);
  ConsumptionTick(1000);
  prio = uxTaskPriorityGet( NULL );
  xSemaphoreGive(xSemaphore);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Give Semaphore Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  while(1) {
    vTaskDelay(100);
  }

}

void task2(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  ConsumptionTick(200);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Waiting Mutex\n",pcTaskGetName(0),nowTick);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Take Semaphore\n",pcTaskGetName(0),nowTick);
  while(1) {
    vTaskDelay(100);
  }
}

void task3(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  ConsumptionTick(200);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Waiting Mutex\n",pcTaskGetName(0),nowTick);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Take Semaphore\n",pcTaskGetName(0),nowTick);
  while(1) {
    vTaskDelay(100);
  }
}


void user_init(void)
{
  uart_set_baud(0, 115200);
  printf("SDK version:%s\n", sdk_system_get_sdk_version());
  printf("pcTaskGetName=%s\n",pcTaskGetName(0));

  /* Create Mutex */
  xSemaphore = xSemaphoreCreateMutex();
  /* Check everything was created. */
  configASSERT( xSemaphore );
  /* Get current value */
  UBaseType_t value;
  value = uxSemaphoreGetCount( xSemaphore );
  printf("Semaphore value=%d\n",(int)value);

#if 1
  xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
  vTaskDelay(10);
  xTaskCreate(task2, "task2", 256, NULL, 2, NULL);
  vTaskDelay(210);
  xTaskCreate(task3, "task3", 256, NULL, 2, NULL);
#endif

#if 0
  xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
  vTaskDelay(10);
  xTaskCreate(task2, "task2", 256, NULL, 3, NULL);
  vTaskDelay(210);
  xTaskCreate(task3, "task3", 256, NULL, 4, NULL);
#endif

}

まず最初に優先度=2のtask1が起動しTakeします。
その後、同じ優先度のtask2とtask3が順次起動し、最初にtask2が、次にtask3がTakeしようと試みます。
task2とtask3は同じ優先度なので並行して動きます。
同じ優先度の場合は、最初にTakeを試みたタスクに与えられるようです。
SDK version:0.9.9
pcTaskGetName=uiT
Semaphore value=1
[task1:0] Start Priority=2
[task1:0] Take
[task2:160] Start Priority=2
[task2:480] Waiting Mutex
[task3:480] Start Priority=2
[task3:690] Waiting Mutex
[task1:1002] Give Semaphore Priority=2
[task2:1002] Take Semaphore



次は違う優先度のタスクが2つTakeを試みる場合です。

/* The example of esp-free-rtos
 *
 * This sample code is in the public domain.
 */
#include <stdlib.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "esp8266.h"

SemaphoreHandle_t xSemaphore;


//時間稼ぎ
void ConsumptionTick(int delay) {
    TickType_t startTick;
    TickType_t endTick;
    TickType_t nowTick;
    startTick = xTaskGetTickCount();
    endTick = startTick + delay;
    //printf("startTick=%d endTick=%d\n",startTick,endTick);
    while(1) {
      nowTick = xTaskGetTickCount();
      if (nowTick > endTick) break;
    }
}

void task1(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  printf("[%s:%d] Take\n",pcTaskGetName(0),nowTick);
  ConsumptionTick(1000);
  prio = uxTaskPriorityGet( NULL );
  xSemaphoreGive(xSemaphore);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Give Semaphore Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  while(1) {
    vTaskDelay(100);
  }

}

void task2(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  ConsumptionTick(200);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Waiting Mutex\n",pcTaskGetName(0),nowTick);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Take Semaphore\n",pcTaskGetName(0),nowTick);
  while(1) {
    vTaskDelay(100);
  }
}

void task3(void *pvParameters)
{
  UBaseType_t prio;
  TickType_t nowTick;
  nowTick = xTaskGetTickCount();
  prio = uxTaskPriorityGet( NULL );
  printf("[%s:%d] Start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
  ConsumptionTick(200);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Waiting Mutex\n",pcTaskGetName(0),nowTick);
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  nowTick = xTaskGetTickCount();
  printf("[%s:%d] Take Semaphore\n",pcTaskGetName(0),nowTick);
  while(1) {
    vTaskDelay(100);
  }
}


void user_init(void)
{
  uart_set_baud(0, 115200);
  printf("SDK version:%s\n", sdk_system_get_sdk_version());
  printf("pcTaskGetName=%s\n",pcTaskGetName(0));

  /* Create Mutex */
  xSemaphore = xSemaphoreCreateMutex();
  /* Check everything was created. */
  configASSERT( xSemaphore );
  /* Get current value */
  UBaseType_t value;
  value = uxSemaphoreGetCount( xSemaphore );
  printf("Semaphore value=%d\n",(int)value);

#if 0
  xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
  vTaskDelay(10);
  xTaskCreate(task2, "task2", 256, NULL, 2, NULL);
  vTaskDelay(210);
  xTaskCreate(task3, "task3", 256, NULL, 2, NULL);
#endif

#if 1
  xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
  vTaskDelay(10);
  xTaskCreate(task2, "task2", 256, NULL, 3, NULL);
  vTaskDelay(210);
  xTaskCreate(task3, "task3", 256, NULL, 4, NULL);
#endif

}

まず最初に優先度=2のtask1が起動しTakeします。
その後、優先度=3のtask2と、優先度=4のtask3が順次起動します。
task3が動き始めると、優先度=3のtask2は実行権を剥奪されます。
その結果、優先度=4のtask3がTakeします。
SDK version:0.9.9
pcTaskGetName=uiT
Semaphore value=1
[task1:1] Start Priority=2
[task1:1] Take
[task2:160] Start Priority=3
[task2:361] Waiting Mutex
[task3:480] Start Priority=4
[task3:681] Waiting Mutex
[task1:1002] Give Semaphore Priority=4
[task3:1002] Take Semaphore

続く....