0
votes

I'm playing with Mutex in freeRTOS using esp32. in some documents i have read that mutex guarantee ownership, which mean if a thread (let's name it task_A) locks up a critical resource (take token) other threads (task_B and task_C) will stay in hold mode waiting for that resource to be unlocked by the same thread that locked it up(which is task_A). i tried to prove that by setting up the other tasks (task_B and task_C) to give a token before start doing anything and just after that it will try to take a token from the mutex holder, which is surprisingly worked without showing any kid of error. Well, the method i used to verify or display how things works i created a display function that read events published (set and cleared) by each task (when it's in waiting mode it set the waiting bit up if it's working it will set the working bit up etc..., you get the idea). and a simple printf() in case of error in take or give function ( xSemaphoreTake != true and xSemaphoreGive != true).

I can't use the debug mode because i don't have any kind of micro controller debugger.

This is an example of what i'm trying to do: i created many tasks and each one will call this function but in different time with different setup.

void vVirtualResource(int taskId, int runTime_ms){
  int delay_tick = 10;
  int currentTime_tick = 0;
  int stopTime_tick = runTime_ms/portTICK_PERIOD_MS;
  if(xSemaphoreGive(xMutex)!=true){
      printf("Something wrong in giving first mutex's token in task id: %d\n", taskId);
  }

  while(xSemaphoreTake(xMutex, 10000/portTICK_PERIOD_MS) != true){
    vTaskDelay(1000/portTICK_PERIOD_MS);
  }
// notify that the task with <<task id>> is currently running and using this resource
  switch (taskId)
  {
  case 1:
    xEventGroupClearBits(xMutexEvent, EVENTMASK_MUTEXTSK1);
    xEventGroupSetBits(xMutexEvent, EVENTRUN_MUTEXTSK1);
    break;
  case 2:
    xEventGroupClearBits(xMutexEvent, EVENTMASK_MUTEXTSK2);
    xEventGroupSetBits(xMutexEvent, EVENTRUN_MUTEXTSK2);
    break;
  case 3:
    xEventGroupClearBits(xMutexEvent, EVENTMASK_MUTEXTSK3);
    xEventGroupSetBits(xMutexEvent, EVENTRUN_MUTEXTSK3);
    break;
  default:
    break;
  }
  // start running the resource
  while(currentTime_tick<stopTime_tick){
    vTaskDelay(delay_tick);
    currentTime_tick += delay_tick;
  }
  // gives back the token
  if(xSemaphoreGive(xMutex)!=true){
    printf("Something wrong in giving mutex's token in task id: %d\n", taskId);
  }

}

You will notice that for the very first time, the first task that will start running in the processor will print out the first error message because it can't give a token while there still a token in the mutex holder, it's normal, so i just ignore it.

Hope someone can explain to me how mutex guarantee ownership using code in freeRTOS. In the first place i didn't use the first xSemaphoreGive function and it worked fine. but that doesn't mean it guarantee anything. or i'm not coding right.

Thank you.

1

1 Answers

0
votes

Your example is quite convoluted, I also don't see clear code of task_A, task_B or task_C so I'll try to explain on a simplier example which hopefully explains how mutex guarantees resource ownership.

The general approach to working with mutexes is the following:

void doWork()
{
  // attempt to take mutex
  if(xSemaphoreTake(mutex, WAIT_TIME) == pdTRUE)
  {
    // mutex taken - do work
    ...
    // release mutex
    xSemaphoreGive(mutex);
  }
  else
  {
    // failed to take mutex for 'WAIT_TIME' amount of time
  }
}

The doWork function above is the function that may be called by multiple threads at the same time and needs to be protected. This pattern repeats for every function on given resource that needs protection. If resource is more complex, a good approach is to guard the top-most functions that are callable by threads, then if mutex is successfully taken call internal functions that do the actual work.

The ownership guarantee you speak about is the fact that there may not be more than one context (threads, but also interrupts) that are under the if(xSemaphoreTake(mutex, WAIT_TIME) == pdTRUE) statement. In other words, if one context successfully takes the mutex, it is guaranteed that no other context will be able to also take it, unless the original context releases it with xSemaphoreGive first.

Now as for your scenario - while it is not entirely clear to me how it's supposed to work, I can see two issues with your code:

  1. xSemaphoreGive at the beginning of the function - don't do that. Mutexes are by default "given" and you're not supposed to be "giving" it if you aren't the one "taking" it first. Always put a xSemaphoreGive under a successful xSemaphoreTake and nowhere else.

  2. This code block:

    while(xSemaphoreTake(xMutex, 10000/portTICK_PERIOD_MS) != true){
      vTaskDelay(1000/portTICK_PERIOD_MS);
    }
    

    If you need to wait for mutex for longer - specify a longer time. If you want infinite wait, simply specify longest possible time (0xFFFFFFFF). In your scenario, you're polling for mutex every 10s, then delay for 1s during which mutex isn't actually checked, meaning there will be cases where you'll have to wait almost a full second after mutex is released by other thread to start doing work in the current thread that requested it. Waiting for mutex is already done by RTOS in an optimal way - it'll wake the highest priority task currently waiting for the mutex as soon as it's released, there's no need to do more than necessary.

If I was to give an advice of how to fix your example - simplify it and don't do more than needed such as additional calls to xSemaphoreGive or implementing your own waiting for mutex. Isolate the portion of code that performs some work to a separate function that does a single call to xSemaphoreTake at the very top and a single call to xSemaphoreGive only if xSemaphoreTake succeeds. Then call this function from different threads to test whether it works.