Синхронизация задач много-к-одному привратнику

Я работаю над дизайном, который использует задачу привратника для доступа к общему ресурсу. Базовый дизайн, который у меня сейчас есть, - это одна очередь, которую получает задача привратника, и несколько задач, помещающих в нее запросы.

Это система с ограничением памяти, и я использую FreeRTOS (порт Cortex M3).

Проблема заключается в следующем: обрабатывать эти запросы асинхронно довольно просто. Запрашивающая задача ставит свой запрос в очередь и занимается своими делами, опрашивая, обрабатывая или ожидая других событий. Чтобы обрабатывать эти запросы синхронно, мне нужен механизм для блокировки запрашивающей задачи, чтобы после обработки запроса привратник мог разбудить задачу, которая вызвала этот запрос.

Самый простой вариант, который я могу придумать, - это включать семафор в каждый запрос, но, учитывая ограничения памяти и довольно большой размер семафора в FreeRTOS, это непрактично.

Что я придумал, так это использование функции приостановки и возобновления задачи, чтобы вручную заблокировать задачу, передав дескриптор привратнику, с помощью которого он может возобновить задачу после завершения запроса. Однако есть некоторые проблемы с приостановкой / возобновлением, и я бы очень хотел их избежать. Один вызов возобновления разбудит задачу независимо от того, сколько раз она была приостановлена ​​другими вызовами, и это может вызвать нежелательное поведение.

Несколько простых псевдо-C для демонстрации метода приостановки / возобновления.

void gatekeeper_blocking_request(void)
{
     put_request_in_queue(request);
     task_suspend(this_task);
}

void gatekeeper_request_complete_callback(request)
{
     task_resume(request->task);
}

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

Псевдо-C, чтобы продемонстрировать блокировку для конкретной задачи, но это необходимо реализовать в каждой задаче.

void requesting_task(void)
{
     while(1)
     {
         gatekeeper_async_request(callback);
         pend_on_sempahore(sem);
     }
}

void callback(request)
{
     post_to_semaphore(sem);
}

Возможно, лучшим решением будет просто не реализовывать блокировку в привратнике и API и заставлять каждую задачу обрабатывать ее. Однако это повысит сложность потока каждой задачи, и я надеялся, что смогу этого избежать. По большей части все вызовы нужно блокировать до завершения операции.

Есть ли какая-то конструкция, которую мне не хватает, или просто лучший термин для этого типа проблемы, который я могу найти в Google? Ничего подобного в поисках не встречал.

Дополнительные примечания - две причины для использования привратника:

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

  2. Ресурс не всегда доступен в ЦП. Он синхронизирует не только задачи в ЦП, но и задачи вне ЦП.


person rjp    schedule 09.11.2010    source источник
comment
Использование семафора было бы традиционным подходом для синхронизации двух задач. Правильно ли я понимаю из вашего текста, что ваше (единственное) возражение против семафора - это его размер в FreeRTOS? Я совсем недавно не использовал FreeRTOS, но не помню, чтобы семафоры были очень дорогими. И похоже, что ваше предложенное решение (с обратным вызовом) все равно использует семафоры? Я просто немного запутался, извини, если я здесь тупица. Кроме того, я бы обернул последовательность async post / pend в крошечную функцию ...   -  person Dan    schedule 09.11.2010
comment
Дэн, FreeRTOS реализует семафоры как очереди нулевой длины. Очередь имеет четыре элемента-указателя (по 32 бита каждый), пять элементов portBASE_TYPE (порт определяется как 32-битный) и два xList по 20 байтов каждый. Решение обратного вызова позволит каждой задаче использовать свой собственный механизм сигнализации, а не иметь семафор для каждого запроса. Теперь у меня есть дизайн с использованием мьютекса, и в нем сохраняется отдельная задача для экономии стека, но я не могу найти хорошего способа реализовать неблокирующие запросы.   -  person rjp    schedule 09.11.2010


Ответы (2)


Используйте мьютекс и сделайте привратник подпрограммой, а не задачей.

person Doug Currie    schedule 09.11.2010
comment
Это работает для случая блокировки, но некоторые вызывающие задачи действительно хотят выполнять асинхронные запросы. Не знаю, как бы я справился с этими просьбами. - person rjp; 09.11.2010
comment
Для запрашивающих асинхронность используйте очередь и задачу администратора очередей, как вы изначально набросали, но пусть задача администратора очередей использует тот же мьютекс, что и задачи синхронизации, для выполнения операции. Однако это не решает вашу (добавленную позже) проблему с размером стека. - person Doug Currie; 10.11.2010
comment
Используя то, что вы предложили, но сохраняя ресурс в другой задаче, мне удалось придумать способ сохранить все синхронизированное в двух задачах. Я отказываюсь от асинхронности как функции для начала, но я рассмотрю возможность реализации вашего предложения, когда это станет необходимым. Единственная проблема заключается в том, что при использовании этого метода могут возникнуть некоторые сложные проблемы с инверсией приоритета, но я могу просто иметь задачу ресурса с более высоким приоритетом, чем все вызывающие. Это не идеально, но FreeRTOS не имеет возможностей для полного наследования приоритетов. - person rjp; 10.11.2010

Прошло шесть лет с тех пор, как я разместил этот вопрос, и мне было трудно заставить синхронизацию работать так, как мне нужно. Были ужасные злоупотребления конструкциями ОС. Я считал, что обновление этого кода, даже если он работает, будет менее оскорбительным, и поэтому я рассмотрел более элегантные способы справиться с этим. FreeRTOS также добавил ряд функций за последние шесть лет, одна из которых, я считаю, предоставляет легкий метод для достижения той же цели.

Уведомления прямо к задаче

Возвращаясь к моему первоначальному предложенному методу:

void gatekeeper_blocking_request(void)
{
     put_request_in_queue(request);
     task_suspend(this_task);
}

void gatekeeper_request_complete_callback(request)
{
     task_resume(request->task);
}

Причина, по которой этого метода избегали, заключалась в том, что вызовы приостановки / возобновления задачи FreeRTOS не ведут счет, поэтому несколько вызовов приостановки будут отменены одним вызовом возобновления. В то время приложение использовало функцию приостановки / возобновления, так что это была реальная возможность.

Начиная с FreeRTOS 8.2.0, прямые уведомления о задачах по существу предоставляют легкий встроенный в задачу двоичный семафор. Когда уведомление отправляется задаче, может быть установлено значение уведомления. Это уведомление будет бездействовать до тех пор, пока уведомленная задача не вызовет какой-либо вариант xTaskNotifyWait(), или оно будет разбужено, если оно уже сделало такой вызов.

Приведенный выше код можно немного переработать, чтобы он стал следующим:

 void gatekeeper_blocking_request(void)
 {
      put_request_in_queue(request);
      xTaskNotifyWait( ... );
 }

 void gatekeeper_request_complete_callback(request)
 {
      xTaskNotify( ... );
 }

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

person rjp    schedule 30.08.2016