Синхронизация задач привратника много-к-одному - PullRequest
6 голосов
/ 09 ноября 2010

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

Это ограниченная память, и я использую 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 и заставлять каждую задачу справляться с этим. Это увеличит сложность потока каждой задачи, и я надеялся, что смогу избежать этого. По большей части все вызовы будут блокироваться до завершения операции.

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

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

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

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

Ответы [ 2 ]

2 голосов
/ 09 ноября 2010

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

0 голосов
/ 30 августа 2016

Прошло шесть лет с тех пор, как я опубликовал этот вопрос, и я изо всех сил пытался заставить синхронизацию работать так, как мне было нужно.Были некоторые ужасные злоупотребления использованными конструкциями ОС.Я решил обновить этот код, хотя он и работает, чтобы он был менее оскорбительным, и поэтому я искал более изящные способы справиться с этим.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( ... );
 }

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

...