Всем привет, продолжаем дальше на практике изучать FreeRTOS на базе STM32F429i – DISCO. Сегодня на повестки дня семафоры.
Семафор — объект, ограничивающий количество потоков, которые могут войти в заданный участок кода. Определение введено Эдсгером Дейкстрой. Семафоры используются при передаче данных через разделяемую память.
Семафор — это объект, с которым можно выполнить три операции.
init(n): счётчик := n enter(): ждать пока счётчик станет больше 0; после этого уменьшить счётчик на единицу. leave(): увеличить счётчик на единицу.
Предположим, что есть такой участок кода:
semaphore.init(5); // ..... // ..... void DoSomething() { semaphore.enter(); // ....... semaphore.leave(); }
Тогда не более пяти потоков могут одновременно выполнять функцию DoSomething().
В более сложных семафорах может использоваться очередь; при этом потоки, ожидающие освобождения семафора, будут проходить через семафор именно в том порядке, в котором они вызывали enter().
Применение семафоров
Вот некоторые из проблем, которые могут решать семафоры.
запрет одновременного выполнения заданных участков кода;
поочерёдный доступ к критическому ресурсу (важному ресурсу, для которого невозможен (или нежелателен) одновременный доступ).
Следующий пример показывает, как наладить поочерёдный доступ к консоли.
semaphore.init(1); // Поток 1: semaphore.enter(); cout < < "Состояние массива: "; for (int i=0; i<n; i++) cout << a[i] << ' '; cout << '\n'; semaphore.leave(); // Поток 2: semaphore.enter(); cout << "Нажато Esc.\n"; semaphore.leave();
Этот код поможет предотвратить появление вывода наподобие
Состояние массива: 1 2 3 Нажато Esc. 4 5 6
Проблемы семафоров
Во-первых, можно написать программу с «утечкой семафора», вызвав enter() и забыв вызвать leave(). Реже встречаются ошибки, когда дважды вызывается leave().
Во-вторых, семафоры чреваты взаимной блокировкой потоков. В частности, опасен такой код:
// Поток 1: semaphore1.enter(); semaphore2.enter(); // ... semaphore2.leave(); semaphore1.leave(); // Поток 2: semaphore2.enter(); semaphore1.enter(); // ... semaphore1.leave(); semaphore2.leave();
В качестве примера создадим бинарный семафор, функция vTask1 будет захватывать семафор, при следующем прохождении цикла планировщик переведет ее в блокирующее состояние, пока не возникнет прерывание и функция обработки прерывания не вернет семафор, как только семафор вернули, vTask1 снова захватит семафор и все повториться заново. Чтобы программа корректно работала я переопределяю configASSERT, без неё FreeRTOS и прерывания просто не дружат, и весь проект валится.
#include <assert.h> #define configASSERT(x) assert(x) #define LONG_TIME 0xffff xSemaphoreHandle xBinarySamaphore; extern void interrupt_button(void){ static portBASE_TYPE xHigherPriorityTaskWoken; xHigherPriorityTaskWoken = pdFALSE; HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET); xSemaphoreGiveFromISR(xBinarySamaphore, &xHigherPriorityTaskWoken); if( xHigherPriorityTaskWoken == pdTRUE ) { portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } } void SystemClock_Config(void); static void StartThread(void const * argument); static void MX_GPIO_Init(void); typedef struct TaskParam_t{ signed long int period; } Task; Task p1; void vTask1(void *pvParameters){ Task *task_period; task_period = (Task *)pvParameters; for(;;){ HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_RESET); xSemaphoreTake( xBinarySamaphore, LONG_TIME ); vTaskDelay(task_period->period); } vTaskDelete(NULL); } int main(void) { p1.period = 100; vSemaphoreCreateBinary(xBinarySamaphore); HAL_Init(); SystemClock_Config(); HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); MX_GPIO_Init(); xTaskCreate(vTask1, (signed char *) "LED_GREEN", configMINIMAL_STACK_SIZE + 256, (void *) &p1, 1, NULL); vTaskStartScheduler(); for(;;); }
С семафорами на сегодня все! В следующий раз разберем пример для мьютекса. 🙂
Проект freeRTOS_semaphore.7z