Recursos compartidos

123456
EspecificaciónModeladoVerificaciónImplementaciónVerificaciónAnálisis
PARA QUÉQUÉCÓMO
¿Cuál es el problema?¿Qué tiene que hacer el sistema?¿El modelo cumple la especificación?¿Cómo hace lo que tiene que hacer? (modelo refinado)¿La implementación cumple la especificación?¿Se cumplen los plazos en el caso peor?
LTL, CTLFSM / xFSMnuXmvfsm.h / fsm.cnuXmvAnálisis del tiempo
(model checking)Ejecutivo cíclico / FreeRTOS / Reactor(model checking)de respuesta

Regiones críticas

En el capítulo 7 de [Barry] se comentan varios métodos para garantizar que no hay desalojo cuando un proceso accede a un recurso compartido o realiza una operación crítica que no puede ser interrumpida. La más agresiva es el par de macros taskENTER_CRITICAL y taskEXIT_CRITICAL.

1taskENTER_CRITICAL();
2// ... operaciones muy críticas
3taskEXIT_CRITICAL();

Este método inhibe todo tipo de interrupciones, incluida la relacionada con el tiempo. Esto quiere decir que ni siquiera el contador de ticks se actualiza. No es frecuente que sea necesario hacer esto. Es mucho más razonable inhibir el planificador mediante vTaskSuspendAll y restaurarlo con xTaskResumeAll.

1vTaskSuspendAll();
2// ... operaciones muy críticas
3xTaskResumeAll();

En este caso las interrupciones siguen funcionando y el tiempo se actualiza correctamente pero el planificador está paralizado, con lo que no puede haber desalojos. El análisis del bloqueo máximo es bastante similar al caso del algoritmo de techo de prioridad inmediato, pero en este caso todas las tareas pueden ser bloqueadas por tareas menos prioritarias, no solo las que compiten por el mismo recurso. Cada tarea solo puede ser bloqueada una vez en un ciclo y su tiempo de bloqueo sería.

Bi=maxjlp(i),kCj,k B_i = \max_{j \in lp(i), k}C_{j,k}

donde Cj,kC_{j,k} es el tiempo máximo de cómputo requerido en la región crítica de la tarea jj para acceder al recurso kk.

Regiones críticas protegidas por mutex

FreeRTOS incorpora también mutexes que implementan el algoritmo de herencia de prioridad. Se construyen igual que cualquier otro tipo de semáforo. El mismo semáforo debe usarse en todas las tareas que pretendan sincronizar los accesos a un mismo recurso.

1SemaphoreHandle_t mutex = xSemaphoreCreateMutex();

Diferentes tareas pueden usarlo para garantizar la exclusión mediante las funciones xSemaphoreTake y xSemaphoreGive:

1xSemaphoreTake(mutex, portMAX_DELAY);
2// ... operaciones sobre el recurso compartido
3xSemaphoreGive(mutex);

La constante portMAX_DELAY determina que espera de forma indefinida hasta obtener el acceso al recurso. En código de producción no se recomienda hacer una espera indefinida. Para ello la función xSemaphoreTake devuelve pdTRUE si ha podido obtener acceso y pdFALSE si ha expirado el timeout:

1if (xSemaphoreTake(mutex, delayInTicks) == pdTRUE) {
2    // ... operaciones sobre el recurso compartido
3    xSemaphoreGive(mutex);
4}
5else {
6    // ... no pudo obtener acceso, medidas alternativas
7}

Un mutex implementa automáticamente el algoritmo de herencia de prioridad y por tanto el bloqueo máximo corresponde a:

Bi=jlp(i),kCj,k B_i = \sum_{j \in lp(i), k}C_{j,k}

donde Cj,kC_{j,k} es el tiempo requerido por la tarea jj para acceder al recurso kk (0 si no lo accede).

Referencias bibliográficas

Richard Barry. Mastering the FreeRTOS™ Real Time Kernel, A Hands-On Tutorial Guide. Real Time Engineers Ltd, 2016.