En sistemas multitarea existe el problema de querer acceder a un recurso desde dos tareas a la vez, por ejemplo a un display.

Exclusión mutua

Consiste en prohibir el acceso desde una segunda tarea.

FreeRTOS dispone de varios métodos para implementar la exclusión mutua pero, el mejor sistema, es diseñar la aplicación para que no se tengan que compartir los recursos y que cada uno se acceda desde una sola tarea.

Secciones críticas y Suspensión del Planificador de tareas

Secciones críticas básicas

Las secciones críticas básicas son las regiones del código que inician y finalizan por llamadas a las macros taskENTER_CRITICAL() y taskEXIT_CRITICAL() respectivamente.

Al implementar una sección críticas de esta manera, desactivamos las interrupciones completamento o hasta una determinada prioridad si se activa configMAX_SYSCALL_INTERRUOT_PRIORIY. Puede haber cambios preventivos dentro de una interrupción, durante el tiempo que, la interrupción, permanezca desactivada.

La tarea que llame taskENTER_CRITICAL() se garantiza que permanece en Running hasta que salga de la sección crítica.

Las secciones críticas tienen que ser lo más cortas posibles, en otro caso, puede afectar al funcionamiento de interrupciones.

Por seguridad, es recomendable anidar las secciones críticas ya que el kernel lleva una cuenta de la longitud de anidación. Para salir de la sección, la cuenta debe ser cero antes de llamar a taskEXIT_CRITICAL().

Suspensión o bloqueo del planificador de tareas

Las secciones críticas impiden el acceso de interrupciones y tareas dentro de una región de código.

Una sección crítica implementada suspendiendo el planificador, protege una sección del código de la ejecución de otras tareas pero permite las interrupciones.

Mediante este sistema, podemos inhabilitar las tareas secundarias durante un periodo mayor de tiempo que si bloqueamos las interrupciones también.

Función vTaskSuspendAll()

Suspende el planificador de tareas permitiendo las interrupciones.

Las tareas que dependan de las interrupciones que han ocurrido, mientras el planificador esté suspendido, se ejecutarán al activarlo de nuevo.

No se deben llamar funciones del API de FreeRTOS mientras el planificador esté suspendido.

void vTaskSuspendAll(void);

Función xTaskResumeAll()

Reactiva el planificador de tareas.

portBASE_TYPE xTaskResumeAll(void);

Return value:

  • pdTRUE: si existe algún cambio de tarea pendiente.
  • pdFALSE: si no existe ningún cambio de tarea.

Por seguridad, es recomendable anidar las llamadas a vTaskSuspendAll() y xTaskResumeAll() ya que el kernel lleva una cuenta de la longitud de anidación. Para salir de la sección, la cuenta debe ser cero antes de llamar a taskEXIT_CRITICAL().

Exclusiones mútuas (Mutex)

Un Mutex es un tipo especial de semáforo binario, se usa para tomar el control de recursos entre dos o más tareas.

Se puede considerar el Mutex como el token a un recurso asociado.

Para que una tarea pueda acceder al recurso, primero tiene que "tomar" el token y, al terminar, lo tiene que "dar".

Una tarea no puede acceder al token si no comparte el recurso.

Los mutexes y los semáforos comparten muchas características. La principal diferencia es lo que ocurre una vez el semáforo se haya obtenido:

  • Un semáforo que se use para exclusión mutua, siempre tiene que devolver (give).
  • Un semáforo que se usa para sincronización, normalmente se descarta y no vuelve.

Función xSemaphoreCreateMutex()

Como Mutex es un tipo de semáforo, los enlaces se almacenan en el tipo de variable xSemaphoreHandle.

Antes de usar un mutex se tiene que crear:

xSemaphoreHandle xSemaphoreCreateMutex(void);

Return value:

  • NULL el mutex no se ha creado porque no tiene suficiente memoria el sistema.
  • NO-NULL el semáforo se ha creado y el valor es el enlace.

Inversión de prioridad

Uno de los problemas de los mutex es que, puede ocurrir, que una tarea con más prioridad tenga que esperar a que otra de menor prioridad termine, esto se llama inversión de prioridades.

Esto suele ocurrir si la tarea de menor prioridad se está ejecutando y, la de más prioridad está esperando al semáforo.

Este problema puede ser significante pero, en pequeños sistemas embebidos, se puede evitar diseñando un sistema de tiempos.

Herencia de prioridad

Los semáforos binarios y los Mutex en FreeRTOS son muy similares, la diferencia es que los mutex incluyen la herencia de prioridad.

La herencia de prioridad es una esquema que minimiza los efectos negativos de la inversión de prioridad. No soluciona el problema, sino que lo simplifica. Sin embargo, la herencia de prioridad complica el análisis en el tiempo, por lo que no es bueno confiar en ello para un correcto funcionamiento del sistema operativo.

La herencia de prioridad funciona quitando temporalmente la prioridad a la tarea que está esperando al mutex. La tarea de menor prioridad hereda temporalmente la prioridad de la otra.
La prioridad del mutex se resetea automáticamente al valor original cuando se "da" el semáforo.
Sólo se puede usar un mutex a la vez.

Punto muerto o abrazo mortal

Punto muerto (Deadlock) es otro fallo de los mutex.

Deadlock ocurre cuando dos tareas están esperando un recurso y una bloquea la otra. Por ejemplo, una tarea A y otra B necesitan un mutex X y otro Y para realizar una acción:

  1. La tarea A ejecuta y toma correctamente el mutex X
  2. La tarea A se adelantó a la B
  3. La tarea B toma correctamente el mutex Y, e intentan tomar el mutex X. Pero este último está retenido por la tarea A. La tarea B se bloquea hasta que mutex X esté libre.
  4. La tarea A continua ejecutándose. Intenta tomar el mutex Y, pero está cogido por la otra tarea. Entonces la tarea A se bloquea también.

Como en la inversión de prioridad, el mejor método contra el Deadlock es realizar un correcto diagrama de tiempos.

Tareas Portero

Las tareas portero (Gatekeeper) proporcionan un método de implementación de exclusión mútua sin el riesgo de la inversión de prioridad ni del Deadlock.

Las Gatekeeper son tareas que poseen un recurso como único usuario, o sea, que esa tarea es la única que puede acceder al recurso directamente.

El resto de tareas accederán al recurso por medio de esta tarea.