Старт ARM. RTOS часть 1-ая. STM32F4 и SAM3N.
Старт ARM. RTOS часть 2-ая.
Старт ARM. RTOS часть 3-ая. Очереди.
Старт ARM. RTOS часть 4-ая. Семафоры.
Всем привет, продолжаем дальше на практике изучать FreeRTOS на базе STM32F429i – DISCO. Сегодня на повестки дня мьютексы.
Мьютекс (англ. mutex, от mutual exclusion — «взаимное исключение») — одноместный семафор, служащий в программировании для синхронизации одновременно выполняющихся потоков.
Мьютексы — это один из вариантов семафорных механизмов для организации взаимного исключения. Они реализованы во многих ОС, их основное назначение — организация взаимного исключения для потоков из одного и того же или из разных процессов.
Мьютексы — это простейшие двоичные семафоры, которые могут находиться в одном из двух состояний — отмеченном или неотмеченном (открыт и закрыт соответственно). Когда какой-либо поток, принадлежащий любому процессу, становится владельцем объекта mutex, последний переводится в неотмеченное состояние. Если задача освобождает мьютекс, его состояние становится отмеченным.
Задача мьютекса — защита объекта от доступа к нему других потоков, отличных от того, который завладел мьютексом. В каждый конкретный момент только один поток может владеть объектом, защищённым мьютексом. Если другому потоку будет нужен доступ к переменной, защищённой мьютексом, то этот поток засыпает до тех пор, пока мьютекс не будет освобождён.
Цель использования мьютексов — защита данных от повреждения в результате асинхронных изменений (состояние гонки), однако могут порождаться другие проблемы — такие, как взаимная блокировка (клинч).
Мьютекс – одна из реализаций спинлока.
Мьютекс отличается от семафора общего вида тем, что только владеющий им поток может его освободить, т.е. перевести в отмеченное состояние.
Во FreeRTOS мьютексы – это механизм управления, функциями которые взаимодействую с железом, то есть аппаратным ресурсом. Семафоры – это механизм управления программными функциями. Очереди – это механизм управления памятью. Все эти штуки организованы как очереди, в принципе одно и тоже, только назвали по-разному, дали каждому свой ярлык. Задача у нас такая, есть один UART и из двух задач через этот интерфейс передаем данные, раньше мы это делали через очереди, что бы защитить от кваказябры, сегодня тоже самое только через мьютекс.
Пример реализации мьютекса
UART_HandleTypeDef huart5; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_UART5_Init(void); volatile xSemaphoreHandle xMutex; void vTask3(void *pvParameters){ char string1[20] = "mcu1.by\n\r"; uint8_t mutex_string1[20] = {0x00}; int i; for (i = 0; i < 20; i++){ mutex_string1[i] = (uint8_t) string1[i]; } for(;;){ xSemaphoreTake(xMutex, portMAX_DELAY); HAL_UART_Transmit(&huart5, mutex_string1, 20, 20); xSemaphoreGive(xMutex); } } void vTask4(void *pvParameters){ char string2[20] = "mcu2.by\n\r"; uint8_t mutex_string2[20] = {0x00}; int i; for (i = 0; i < 20; i++){ mutex_string2[i] = (uint8_t) string2[i]; } for(;;){ xSemaphoreTake(xMutex, portMAX_DELAY); HAL_UART_Transmit(&huart5, mutex_string2, 20, 20); xSemaphoreGive(xMutex); } } void ApplicationIdleHook(void){} int main(void) { HAL_Init(); SystemClock_Config(); HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); MX_GPIO_Init(); MX_UART5_Init(); xMutex = xSemaphoreCreateMutex(); xTaskCreate(vTask3, (signed char *) "Task3", configMINIMAL_STACK_SIZE + 10, NULL, 1, NULL); xTaskCreate(vTask4, (signed char *) "Task4", configMINIMAL_STACK_SIZE + 10, NULL, 1, NULL); vTaskStartScheduler(); while (1){ } }
Пример реализации рекурсивного мьютекса.
UART_HandleTypeDef huart5; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_UART5_Init(void); xSemaphoreHandle xRecursiveMutex; void vTask3(void *pvParameters){ char string1[20] = "mcu1.by\n\r"; uint8_t mutex_string1[20] = {0x00}; int i; for (i = 0; i < 20; i++){ mutex_string1[i] = (uint8_t) string1[i]; } for(;;){ xSemaphoreTakeRecursive( xRecursiveMutex, portMAX_DELAY ); HAL_UART_Transmit(&huart5, mutex_string1, 20, 20); xSemaphoreGiveRecursive( xRecursiveMutex ); } } void vTask5(void){ char string3[20] = "mcu3.by\n\r"; uint8_t mutex_string3[20] = {0x00}; int i; for (i = 0; i < 20; i++){ mutex_string3[i] = (uint8_t) string3[i]; } if(xSemaphoreTakeRecursive( xRecursiveMutex, portMAX_DELAY ) == pdTRUE){ HAL_UART_Transmit(&huart5, mutex_string3, 20, 20); xSemaphoreGiveRecursive( xRecursiveMutex ); } } void vTask4(void *pvParameters){ char string2[20] = "mcu2.by\n\r"; uint8_t mutex_string2[20] = {0x00}; int i; for (i = 0; i < 20; i++){ mutex_string2[i] = (uint8_t) string2[i]; } for(;;){ xSemaphoreTakeRecursive( xRecursiveMutex, portMAX_DELAY ); HAL_UART_Transmit(&huart5, mutex_string2, 20, 20); vTask5(); xSemaphoreGiveRecursive( xRecursiveMutex ); } } void ApplicationIdleHook(void){} int main(void) { HAL_Init(); SystemClock_Config(); HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); MX_GPIO_Init(); MX_UART5_Init(); xRecursiveMutex = xSemaphoreCreateRecursiveMutex(); xTaskCreate(vTask3, (signed char *) "Task3", configMINIMAL_STACK_SIZE + 10, NULL, 1, NULL); xTaskCreate(vTask4, (signed char *) "Task4", configMINIMAL_STACK_SIZE + 10, NULL, 1, NULL); vTaskStartScheduler(); while (1){ } }
Их основное отличие от обычных мьютексов заключается в том, что они корректно работают при вложенных операциях захвата и освобождения мьютекса. Вложенные операции захвата/освобождения мьютекса допускаются только в теле задачи-владельца мьютекса. Что значит, между взятием и освобождением мьютекса можно еще раз взять мьютекс и он корректно будет работать.
С мьютексами на сегодня все! В следующий раз разберем пример для сопрограмм. 🙂