Старт ARM. RTOS часть 5-ая. Мьютексы.

Старт 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){
	}
}

Их основное отличие от обычных мьютексов заключается в том, что они корректно работают при вложенных операциях захвата и освобождения мьютекса. Вложенные операции захвата/освобождения мьютекса допускаются только в теле задачи-владельца мьютекса. Что значит, между взятием и освобождением мьютекса можно еще раз взять мьютекс и он корректно будет работать.

С мьютексами на сегодня все! В следующий раз разберем пример для сопрограмм. 🙂

Проект freeRTOS_mutex_1.7z
Проект freeRTOS_mutex_2.7z