Межпроцессная коммуникация в scmRTOS. Часть 1. Мьютексы

В данной статье постараюсь осветить тему межпроцессной коммуникации в scmRTOS 3.10 (4 версию пока не рассматриваю т.к. нет ее стабильной ветки, хотя скорее всего все нижесказанное будет справедливо и для нее), как прикрутиить ее к AVR Studio можно посмотреть тут.

Начнем, в scmRTOS есть несколько способов межпроцессной коммуникации:

  • Мьютексы (семафоры)
  • События
  • Сообщения
  • Каналы

Рассмотрим работу с мьютексами, дабы не плодить абстракций возьмем «боевую» задачу, к примеру, необходимо спроектировать счетчик числа посетителей крупного магазина, требования простые:
у магазина есть несколько входов (пусть их будет 3), надо вести учет по каждому входу и каждые 600 мсек выводить на дисплей номер входа и кол-во посетителей, также есть одна допоолнительная информационная кнопка, при нажатии на которую на экран выводится температура на улице (выводится может что угодно, требование с кнопкой я придумал в последний момент, оно немного усложняет жизнь :) )

Итак, что получаем, у нас есть 5 процессов в RTOS:
  • первые 3 — опрашивают счетчики
  • 1 — обрабатывает нажатие кнопки
  • 1 — занимается выводом информации на дисплей

Задача первых 4х процессов отправить данные процессу номер 5, который собственно и выведет их на дисплей, но что ж поехали…

Мьютекс или семафор — второе название хорошо отражает суть, работает просто, как шлагбаум,
поднят — может пройти только один, за ним шлагбаум опускается, и не поднимется пока не он выйдет, вышел — шлагбаум вновь поднят, заходит следующий

В данном случае семафор нам пригодится для того, чтобы контролировать доступ каждого процесса, к одной переменной (точнее массиву из 2х элементов), куда собственно и будут записываться данные с количеством посетителей и номером входа.
У семафора есть несколько методов:
  • Lock – захватить семафор, собственно зайти и опустить за собой шлагбаум, если там никого нет, если есть — то ждать пока не выйдет, вечно….т.е. если процесс захвативший семафор, его не вернет, то мы погружаемся в анабиоз :)
  • Unlock — освободить семафор, выйти и поднять шлагбаум (освободить может только тот, кто занял)
  • IsLocked – проверить на занятость (подергать шлагбаум), сразу вернет true, если семафор уже кем-то захвачен и false, в противном случае

Важно!!!
При вызове функции Unlock управление передается ядру, все процессы, которые ожидали семафор переводятся в состояние «Готов к выполнению», далее происходит перепланирование процессов и управление отдается самому высокоприоритетному, в перепланировании учавствуют ВСЕ готовые к выполнению процессы системы (т.е. и те которые семафор не ожидали и тот, который семафор освободил), таким образом, если семафор освобождает самый высокоприоритетный процесс, то управление никуда не отдается.

На самом деле работает все просто (далее псевдокод)


 Процесс1
 {
  семафор1.Lock
  ...выполняем действия над общим ресурсом
  семафор1.Unlock
  Sleep //передаем управление другим процессам
 }

 Процесс2
 {
  семафор1.Lock
  ...выполняем действия над общим ресурсом
  семафор1.Unlock
  Sleep //передаем управление другим процессам
 }  


Еще можно отметить использование функции Sleep, если приоритет Процесса1 > приоритета Процесса2, то формально в Процессе2 Sleep можно не использовать т.к. при вызове Unlock будет произведено перепланирование и передача управления Процессу1, но тут все зависит от того есть ли другие процессы в системе и их приоритеты относительно Процесса1 и Процесса2, поэтому, без особой нужды, лучше всегда явно передавать управление, иначе можно долго ломать голову, почему какой-либо процесс не запускается.

Вот такой у нас получается код для нашего счетчика посетителей
Он не претендует на оригинальность или лучшее решение поставленной задачи, его основная цель — рассмотреть работу с семафорами в нескольких процессах.

Есть 2 интересных момента:
  1. Это необходимость выводить значения на экран в заранее определенной последовательности
  2. Отображение информации при нажатии на кнопку, оно должно происходить немедленно и не зависеть от последовательности вывода значений счетчиков

1-й вопрос решается добавлением новой переменной, в которой хранится номер счетчика, который мы должны вывести на экран, каждый процесс, осуществляющий снятие показаний с счетчика, прежде всего проверяет в свою ли очередь он это собирается сделать и только потом работает

2-й вопрос решается путем назначения самого высокого приоритета процессу обработки нажатия от кнопки

Также читаем комментарии по ходу


#define F_CPU 8000000
#include <util/delay.h> 
#include <avr/io.h> 
#include <scmRTOS.h>

#include <stdlib.h>
#include <string.h>
#include "hd44780.h"

//создаем наши процессы
typedef OS::process<OS::pr0, 100> TProcCustomButtonHandler; 
typedef OS::process<OS::pr1, 100> TProcVisitorCounter1;
typedef OS::process<OS::pr2, 100> TProcVisitorCounter2;
typedef OS::process<OS::pr3, 100> TProcVisitorCounter3;
typedef OS::process<OS::pr4, 100> TProcLCDWriter;

TProcCustomButtonHandler Proc1;
TProcVisitorCounter1 Proc2;
TProcVisitorCounter2 Proc3;
TProcVisitorCounter3 Proc4;
TProcLCDWriter Proc5;



int lcdData[2];         //глобальный массив с данными для дисплея, в ячейке [0] храним номер входа, в [1] – число посетителей
int processToRun = 0;   //в этой переменной храним номер процесса, который запустится следующим
OS::TMutex LCDMutex;    //семафор

//функция вывода на дисплей
void lcd_puts(const char *s)
{
    register char c;

    while ( (c = *s++) ) {
        HD44780_SEND_CHAR(c);
    }

}


int main()
{
    hd44780_init();
    HD44780_SEND_CURSOR_POS(0, 0);
    DDRB = 0xFE;  //1 пин DDRB На вход
    PORTB = 0x01; //подрубаем pullup 
    //инициализируем таймер
    TCCR0 = 0x03;     
    TIMSK |=  (1 << TOIE0);  
    OS::Run();
}


//процедура обработки нажатия кнопки
namespace OS 
{
    template<> OS_PROCESS void TProcCustomButtonHandler::Exec()
    {
		for(;;)
		{
			if ((PINB & 0x01) == 0)
			{
			   /*
				   Выводим данные сразу
				   после этого лочим семафор, чтобы никто больше не смог вывести данные
				   т.к. данный процесс самый приоритетный, то как только любой другой разлочит семафор, 
				   этот получит управление
			   */    
			   lcdData[0] = 10;
			   lcdData[1] = 10;
			   LCDMutex.Lock();
			   Sleep(600/2);        //передаем управление другим процессам на 600 мсек, т.к. семафор залочен, 
						//то ни один процесс, отправляющий инфу на экран, не сможет ее вывести
			   LCDMutex.Unlock();   //разблокируем семафор, т.к. процесс самый приоритетный, то переключение не происходит
			} 
			Sleep(1); 
		}
    }
}

/*
 процесс опроса счетчика, предполагается, что счетчик - некоторое устройство, способное само считать посетителей, 
 мы только получаем данные от него
 т.к. на самом деле у нас такого ус-ва нет, то роль счетчика играет переменная, увеличивающая свое значение в цикле :)
 Все остальные процессы опроса счетчика будут работать аналогично данному  
*/
namespace OS 
{
	template<> OS_PROCESS void TProcVisitorCounter1::Exec()
	{
		int i = 0; //наш счетчик
		for(;;)
		{
			i++;  
			if (processToRun == 0)      //смотрим не наша ли очередь выводить данные на экран
			{
				LCDMutex.Lock();    //лочим семафор
				lcdData[0] = 1;     //готовим данные для дисплея, номер счетчика
				lcdData[1] = i;     //количество посетителей
				Sleep(600/2);       //передаем управление другим процессам на 600 мсек, 
                                                    //т.к. семафор залочен, никто не сможет вывести новые данные
				processToRun++;     //устанавливаем очередность вывода на экран
				LCDMutex.Unlock();  //разлочим семафор, тут происходит перепланирование и управление может получить 
				                    //готовый к выполнению процесс с более высоким приоритетом, чем этот, в нашем случае это
				                    //процесс обработки нажатия кнопки, потом управление снова вернется сюда
			}
			Sleep(1);                   //отдаем управление другим процессам
		}
	}
}


namespace OS 
{
	template<> OS_PROCESS void TProcVisitorCounter2::Exec()
	{
		int i2 = 0; 
		for(;;)
		{
			i2++;
			if (processToRun == 1)
			{
				LCDMutex.Lock();
				lcdData[0] = 2;
				lcdData[1] = i2;
				Sleep(600/2);
				processToRun++;
				LCDMutex.Unlock();  //ВНИАНИЕ!!! 
                                   //вот здесь кроется подводный камень и объяснение зачем нам нужна переменная, хранящая номер
				   //процесса, осуществляющего опрос счетчика: мы освобождаем семафор, происходит перепланирование процессов,
				   //сначала управление получит TProcCustomButtonHandler, а за ним TProcVisitorCounter1,
				   //который, если бы не было данной переменной, что сделал, правильно, залочил бы семафор,
				   //суть в том, процессы с более низким приоритетом, чем этот, никогда бы не смогли залочить семафор 
				   //т.к. тут управление всегда передавалось бы выше
			}
			Sleep(1);
		}
	}
}

//3й процесс счетчика
namespace OS 
{
	template<> OS_PROCESS void TProcVisitorCounter3::Exec()
	{
		int i3 = 0; 
		for(;;)
		{
			i3++;
			if (processToRun == 2)
			{
				LCDMutex.Lock();
				//LED_PORT |= (1<<LED1);
				lcdData[0] = 3;
				lcdData[1] = i3;
				Sleep(600/2);
				processToRun = 0;
				//LED_PORT &= ~(1<<LED1);
				LCDMutex.Unlock();
			}
			Sleep(1);
		}
	}
}

/*
 процесс вывода на экран
 тут никаких особенностей, единственно что, перед тем как выводить данные, происходит проверка, 
 отличаются ли они от уже отображаемых? выводим, только если отличаются
*/
namespace OS 
{
	template<> OS_PROCESS void TProcLCDWriter::Exec()
	{
		int oldData[] = {0 ,0}; 
		for(;;)
		{
			if ((oldData[0] != lcdData[0]) || (oldData[1] != lcdData[1]) )
			{
				char buf[5];
				HD44780_SEND_CMD_CLEAR;
				itoa(lcdData[0], buf, 10); //преобразуем число в строковое представление
				lcd_puts(buf);
				lcd_puts("--");
				itoa(lcdData[1], buf, 10);
				lcd_puts(buf);
				oldData[0] = lcdData[0];  //запоминаем, то что вывели
				oldData[1] = lcdData[1];
			}  
			Sleep(1);
		}
	}
}


Дисплейчик прикрутил используя либу, найденную где-то на просторах интернета, автору спасибо, пригодилась, прикрепил ее к файлам статьи.
Архив с проектом для AVR студии и файлик для протеуса также смотрим в аттачах.

Итак, с семафорами разобрались, в следующей статье рассмотрим использование событий и сообщений… оставайтесь на связи :)
  • +2
  • 28 сентября 2011, 22:38
  • Lekster
  • 3
Файлы в топике: proteus.zip, hd44780.zip, scm_rtos_mutex.zip

Комментарии (4)

RSS свернуть / развернуть
Стабильная 4-я версия уже давно существует.
0
  • avatar
  • a9d
  • 28 сентября 2011, 22:44
на Download страничке только 3я версия, в репозитории ссыылка на бранч pre4, хотя и заявляют, что текущая версия 4, не ясно чего не выкладывают
0
на форуме разрабы написали, что обновят ссылки после того как выпустят все порты.
0
  • avatar
  • a9d
  • 28 сентября 2011, 22:58
тогда надо глянуть :)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.