Использование semaphore, mutex, event и msg

NOTE:

  • mutex – используют для доступа к разделяемому ресурсу несколькими задачами. Когда одна задача захватила mutex и использует разделяемый ресурс, вторая не может захватить mutex и использовать разделяемый ресурс. Вторая задача будет находиться в ожидании освобождения mutex первой задачей. И как только mutexбудет освобожден, вторая задача сможет его захватить и использовать разделяемый ресурс.
  • msg — используют для обмена данными между задачами. Note: Глобальные переменные не используют т.к. несколько задач одновременно могут получить доступ к ним, что может привести к ошибке. Либо доступ к этим глобальным переменным нужно выносить в критическую секцию, в которой планирование задач запрещено.
  • event – используют для того чтобы сообщить задаче, что произошло какое-то событие. Note: использование похоже на семафоры в режиме оповещения (сигнала) о том, что произошло событие.
  • semaphore – существует два варианта использования семафора:

Первый: Семафор для одновременного доступа к разделяемом ресурсу более чем 1 задачей (для 1 задачи обычно используют mutex).

При каждом занимание ресурса задачей значение семафора будет уменьшаться, пока не достигнет 0. При нулевом значение семафора разделяемый ресурс больше не может быть занят новой задачей, пока существующая задача его не оставит. При оставлении разделяемого ресурса задачей, значение семафора увеличивается на 1, что приводит к возможности другой задаче занять разделяемый ресурс.

Второй: семафор сигналит о том, что что-то произошло – например, прерывание. Например, из прерывание, сигналить задаче-обработчику этого прерывания о том, что надо запуститься.

Note: Проблема сигналов из двоичного семафора – если у вас двоичный семафор, то если произошло сразу нескольких быстрых прерываний (быстрых настолько, что их задача-обработчик, которой мы хотим просигналить, не успела вызваться), то двоичный семафор просигналит только один раз. И если нам точно нужно знать, сколько прерываний произошло и для всех их вызвать обработчик, то возникнет ошибка. Поэтому, для этого нужно использовать счетные (не бинарные) семафоры с глубиной счета, на столько большой на сколько вы рассчитываете быстро будут идти прерывания, которые вы хотите обрабатывать в задаче-обработчика.

Во всех приложенных проектах-примерах RTOS делает одно и тоже (скачать пример можно в конце статьи):

  • Задача 1 и задача 2 тестируют mutex. Они имеют одинаковый приоритет и пытаются одновременно захватить mutex(использовать разделяемый ресурс — строчку дисплея). Первая захватившая mutex задача выводит на дисплей свое имя и уходит в сон на 2 или 3 секунды (в зависимости 1 или 2 задача). В это время планировщик переключается на вторую задачу (т.к. первая с этим же приоритетом заснула) — вторая задача пытается захватить mutex, но он уже захвачен первой. Тогда, вторая задача блокируется до момента освобождения mutex. Mutexосвободится когда первая задача проснется и освободит его (mutex) — тогда вторая задача захватывает mutex и так-же выводит на дисплей свое имя и уходит в сон. Далее все повторяется по такому же сценарию. Таким образом, мы можем протестировать mutex (более подробно — см. код в примере).
  • Задачи 3, 4 и 5 тестируют semaphore (для доступа к разделяемому ресурсу). Они имеют одинаковый приоритет и пытаются одновременно захватывать семафоры (использовать разделяемый ресурс — 2 строчки дисплея — для 1 строчки дисплея достаточно mutex). Принцип работы этих задач такой-же как и mutex задач 1 и 2, описных выше (более подробно — см. код в примере).
  • Задачи 6, 7 и 8 тестируют event. Задачи 6 и 7 отправляют event, а задача 8 принимает его и в зависимости от принятого event выводит различный текст на дисплей. Задача 6 и 7 имеют одинаковый приоритет, а задача 8 имеет повышенный приоритет — для того, чтобы по принятию event сразу проснуться и выполнить необходимое действие. По выполнению этого действия задача 8 опять уходит в режим блокировки до момента отправки следующего event от задач 6 или 7. Задача 6 отправляет event по нажатию кнопки на плате STM32F429i-Disco. Азадача 7 отправляет event каждые 8 секунд.
  • Задачи 9 и 10 тестируют msg. Для этого задача 9 каждую секунду просыпается, инкрементирует переменную (считает секунды) и отправляет данную переменную через msg задачи 10. Задача 10 принимает данный msg и выводит принятое значение на дисплей (на дисплее считается время в секундах).

 ChibiOS

Ссылка на сайт разработчика: http://www.chibios.org/ . Документация онлайн: http://chibios.sourceforge.net/docs3/rt/index.html .

Задачи

Помимо Idle task, ChibiOs имеет еще одну задачу, которая создается по умолчанию — это main задача. В ней выполняется тот код который у вас находится в функции main. Данная задача имеет NORMALPRIO приоритет. Поэтому в main после создания всех задач должен обязательно быть бесконечный цикл — можно в него поместить какое-то полезное действие (задачу).

Создание задач:

// стек текущей задачи 1024 байта
static THD_WORKING_AREA(waThread1, 1024);
static THD_FUNCTION(Thread1, arg)
{
    while (true)
    {
        ...
        // code
        ...
    }
}

chThdCreateStatic(waThread1, sizeof(waThread1),
                  NORMALPRIO + 4, Thread1, NULL);

В main, RTOS запускается вызовом функций halInit и chSysInit.

int main(void)
{
    halInit();
    chSysInit();

    ...
    // code
    ...

    chThdCreateStatic(waThread1, sizeof(waThread1),
                      NORMALPRIO + 4, Thread1, NULL);
    ...

    while (true)
    {
        chThdSleepMilliseconds(500);
    }
}

Отправка задачи в сон:

chThdSleepMilliseconds(500) // заснуть на 500 мсек

Работа с mutex

Создание:

static mutex_t mutex_1;
chMtxObjectInit(&mutex_1);

Использование:

chMtxLock(&mutex_1); // захватываем mutex

// code
// делаем что либо с разделяемым ресурсом

chMtxUnlock(&mutex_1); // освобождаем mutex

Работа с семафорами

Создание:

static semaphore_t sem_1;
chSemObjectInit(&sem_1,
                2); // cnt_n = 2

Если вы используете семафор для одновременного доступа к разделяемом ресурсу, то cnt_n указывает какое число задач могут иметь одновременный доступ к разделяемому ресурсу.

Если вы используете семафор для оповещения что произошло некое событие, то cnt_n должно быть равно 0. То есть если сравнивать с TNKernel, данный параметр похож на Start value.

Использование:

chSemWait(&sem_1); // захватываем ресурс семафором (или ждем события)

// code
// делаем что либо с разделяемым ресурсом

chSemSignal(&sem_1); // освобождаем  ресурс семафором (или сигналим о событии)

Работа с msg

Создание:

static msg_t msg_que[10]; // глубина очереди msg = 10
MAILBOX_DECL(mailbox_1, msg_que, 10); // глубина очереди msg = 10

Использование:

Передача:

unsigned int tx_data;
unsigned int data;

tx_data = data;
chMBPost(&mailbox_1, (msg_t)&tx_data, TIME_INFINITE);

Прием:

msg_t msg_status;
msg_t msg;

unsigned int rx_data;
msg_status = chMBFetch(&mailbox_1, &msg, TIME_INFINITE);

if(msg_status == MSG_OK)
{
    rx_data = *( (unsigned int *)msg );
}

Работа с event

Создание:

thread_t *p_task_rec_event;
event_source_t event_1;

// создание задачи, которой будем отправлять event
// данная задача принимает event
p_task_rec_event = chThdCreateStatic(waThread8, sizeof(waThread8), NORMALPRIO + 1, Thread8, NULL);

Использование:

Отправка:

// p_task_rec_event - задача которой отпраляем event
// отправляем флаг - 0b 0000 0001
chEvtSignal(p_task_rec_event, 1); 

Прием:

eventmask_t rec_evt;
rec_evt = chEvtWaitAny(ALL_EVENTS);