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

Начнем, в 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-й вопрос решается путем назначения самого высокого приоритета процессу обработки нажатия от кнопки
Также читаем комментарии по ходу
#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
Комментарии (4)
RSS свернуть / развернуть