Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Обмен данными между задачами RTOS
Форум разработчиков электроники ELECTRONIX.ru > Cистемный уровень проектирования > Операционные системы
Rev0.0
Привет! Вот мучаюсь и все тут, нужен свежий взгляд. Ниже привел псевдокод, который по моему мнению должен нормально работать.
Имеем некоторую RTOS и 2 задачи для простоты.
Задача task1 имеет приоритет выше, чем task2. Задача task1 висит в ожидании данных от некоторой периферии.
Задача task2 должна принимать ПОСЛЕДНИЕ данные от task1, т.е если задача task1 получила скажем 10 буферов данных, то нам нужен в task2 только последний буфер, остальные можем безболезненно потерять (т.е. очередь не нужна).
Важное условие - код task2 должен выполняться как можно быстрее, поэтому я мьютекс поместил внутрь опроса флага, т.е. чтобы данные блокировались только когда они пришли, а не каждый цикл выполнения task2.
Прав ли я с таким кодом, покритикуйте, пожалуйста.

Код
int a,b,c;
int buf[3];

task1
{
while(1)
{
  if (wait_peri_data(&buf))
  {
    mutex_lock();
    a = buf[0];
    b = buf[1];
    c = buf[2];
    mutex_unlock();
    peri_data_available = 1;
  }
  os_delay(1);
}
}

task2
{
int a_task, b_task, c_task;    

while(1)
{
  if (peri_data_available)
  {
    mutex_lock();        
    a_task = a;
    b_task = b;
    c_task = c;
    mutex_unlock();
    peri_data_available = 0;            
  }
  os_delay(1);
}
}
Сергей Борщ
Цитата(Rev0.0 @ Jan 30 2015, 11:49) *
покритикуйте, пожалуйста.
А почему вторая задача должна просыпаться каждый тик и проверять переменную? Неужели в вашей ОС нет средства сигнализации вроде флага? Чтобы вторая задача ушла в ожидание этого флага и проснулась только тогда, когда первая задача этот флаг просигналит?
AlexandrY
Вот так надо делать:

Код
int a,b,c;
int buf[3];

task1
{

while(1)
{
   mutex_lock();
  if (wait_peri_data(&buf))
  {
    a = buf[0];
    b = buf[1];
    c = buf[2];
  }
  mutex_unlock();
  os_delay(1);
}
}

task2
{
int a_task, b_task, c_task;    

while(1)
{
  mutex_lock();        
  a_task = a;
  b_task = b;
  c_task = c;
  mutex_unlock();
  os_delay(1);
}
}
Сергей Борщ
Цитата(AlexandrY @ Jan 30 2015, 12:58) *
Вот так надо делать:
И что мы получили? Высокоприоритетная задача вынуждена ждать, пока низкоприоритетная задача отпустит mutex, хотя этот mutex ей для возобновления работы не нужен совсем. Тогда уж запихнуть все тело второй задачи в первую, эффект будет таким же, плюс экономия на стеке второй задачи и времени на переключение между ними.
AlexandrY
Цитата(Сергей Борщ @ Jan 30 2015, 13:20) *
И что мы получили? Высокоприоритетная задача вынуждена ждать, пока низкоприоритетная задача отпустит mutex, хотя этот mutex ей для возобновления работы не нужен совсем. Тогда уж запихнуть все тело второй задачи в первую, эффект будет таким же, плюс экономия на стеке второй задачи и времени на переключение между ними.


Тут как ни крути абсурд будет по причине os_delay в обоих задачах.
А я просто привел к виду принятому в RTOS.
Rev0.0
Сергей Борщ
Ах да, забыл сказать, что task2 нельзя делать ожидающей, задумано так, что она должна работать каждый тик, поэтому лишние переключения нежелательны.
Также нежелательно юзать мьютексы каждый раз при входе в задачу, т.к. они довольно ресурсоемки да и задачи выполняют еще какой-то код помимо кода обмена данными. По этим причинам у меня и возникла идея проверять флаг каждый тик и затем если флаг TRUE, то тогда уже используем лок мьютексом.
AlexandrY
Цитата(Rev0.0 @ Feb 2 2015, 07:58) *
Также нежелательно юзать мьютексы каждый раз при входе в задачу, т.к. они довольно ресурсоемки да и задачи выполняют еще какой-то код помимо кода обмена данными.


Самый ресурсоемкий в ваших примерах это вызов os_delay. На все остальное после этого можно не обращать внимания.
Rev0.0
Цитата(AlexandrY @ Feb 2 2015, 11:08) *
Самый ресурсоемкий в ваших примерах это вызов os_delay. На все остальное после этого можно не обращать внимания.

В смысле? Если мне нужно чтобы каждая задача выполнялась периодически, одна с периодом 1 мс, другая 10 мс, третья 100 мс, все задачи с разным приоритетом. Как вы это сделаете? os_delay как раз и нужна для того, чтобы когда высокоприоритетная задача отработала другие имели возможность тоже сделать это. Иначе вся ОС без os_delay будет работать в бесконечном цикле только в одной задаче с самым высоким приоритетом.
Сергей Борщ
Цитата(Rev0.0 @ Feb 2 2015, 07:58) *
Ах да, забыл сказать, что task2 нельзя делать ожидающей, задумано так, что она должна работать каждый тик, поэтому лишние переключения нежелательны.
Недопонял. Ну и что, что каждый тик? Все равно выполняя свой os_delay() вы вызываете переключение. Вот только момент возврата из os_delay() и начала полезной работы у вас получился никак не связан с моментом готовности данных.

Цитата(Rev0.0 @ Feb 2 2015, 07:58) *
Также нежелательно юзать мьютексы каждый раз при входе в задачу, т.к. они довольно ресурсоемки да и задачи выполняют еще какой-то код помимо кода обмена данными.
Опять не вижу связи. Если вы используете мутех при входе в задачу - задача будет ждать, даже если этот мутех ей сейчас нафиг не нужен. Я не знаю, что у вас за ОС, но мне кажется, что тут нужен не мутех, тут нужно оформить a, b, c в виде сообщения и это сообщение послать задаче 2. Возможно через очередь сообщений, а возможно достаточно и вырожденной очереди из одного сообщения. И задача 2 должна ждать появления этого сообщения. Возможно ожидать с таймаутом в один тик. А возможно надо разделить на две задачи ту работу, которую задача2 выполняет по приходу данных и ту работу, которую она должна выполнять периодически.

Цитата(Rev0.0 @ Feb 2 2015, 08:51) *
В смысле? Если мне нужно чтобы каждая задача выполнялась периодически, одна с периодом 1 мс, другая 10 мс, третья 100 мс, все задачи с разным приоритетом. Как вы это сделаете?
В вашей реализации одна задача будет выполняться с периодом 1 мс + некоторое время на ее выполнение + некоторое время на выполнение других задач, вторая - с периодом 10 мс + некоторое время, третья - 100 мс + некоторое заранее неизвестное время.

Цитата(Rev0.0 @ Feb 2 2015, 08:51) *
Иначе вся ОС без os_delay будет работать в бесконечном цикле только в одной задаче с самым высоким приоритетом.
Значит у вас что-то не так с архитектурой программы. Потому что правильно спроектированная программа с ОС все свободное время крутится в цикле задачи с наименьшим приоритетом (IdleTask), а все остальные задачи в свободное время ждут появления запускающего их события.
Rev0.0
Может быть я задачу как-то непонятно описал.
Я же просто привел вариант когда нужно в задаче выполнять периодически действия - как вы это сделаете без os_delay? Аппаратный таймер, который шлет задаче семафор? Да, можно, но вопрос не в этом.
Как вы строите архитектуру с RTOS - все задачи чего-то ждут? У меня в проекте 10 задач и некоторые ждут, а некоторые периодически выполняются. Тем задачам, что выполняются периодически нужна связь с ожидающими. Про очередь - хорошая идея, но как я писал выше мне нужны только последние данные - нет смысла в очереди. Тем более мне нужна очередь с нулевым временем ожидания.

Кстати для решения того, что вы описали с нечетким периодом выполнения задач в FreeRTOS, например, есть vTaskDelayUntil.
AlexandrY
Цитата(Rev0.0 @ Feb 2 2015, 12:16) *
Я же просто привел вариант когда нужно в задаче выполнять периодически действия - как вы это сделаете без os_delay?


Используются сервисы ожидания с таймаутом.
xEventGroupWaitBits с указанием xTicksToWait

Но лучше создать отдельную задачу для строго периодических действий.

Кстати, во Free RTOS хорошо написано почему vTaskDelay нельзя использовать для периодических задач.
Timmy
Цитата(Rev0.0 @ Jan 30 2015, 12:49) *
Привет! Вот мучаюсь и все тут, нужен свежий взгляд. Ниже привел псевдокод, который по моему мнению должен нормально работать.
Имеем некоторую RTOS и 2 задачи для простоты.
Задача task1 имеет приоритет выше, чем task2. Задача task1 висит в ожидании данных от некоторой периферии.
Задача task2 должна принимать ПОСЛЕДНИЕ данные от task1, т.е если задача task1 получила скажем 10 буферов данных, то нам нужен в task2 только последний буфер, остальные можем безболезненно потерять (т.е. очередь не нужна).
Важное условие - код task2 должен выполняться как можно быстрее, поэтому я мьютекс поместил внутрь опроса флага, т.е. чтобы данные блокировались только когда они пришли, а не каждый цикл выполнения task2.
Прав ли я с таким кодом, покритикуйте, пожалуйста.

Циклический опрос следует применять только в исключительных ситуациях. В вашем случае task2 может непрерывно спать до пробуждения со стороны task1 в момент поступления новых данных. Для пробуждения можно в завимости от OS и ситуации использовать mailbox, semaphore, event и т.п.
И, похоже, вы хотите выкидывать часть входных данных, если они поступают слишком быстро(более одного сэмпла в миллисекунду), но не объявили эту цель в явном виде, и не описали условие выкидывания.
501-q
Приветствую.

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

Ну и я бы еще флаги менял по-другому. И можно без мьютексов.

Цитата(Rev0.0 @ Jan 30 2015, 15:49) *
Привет! Вот мучаюсь и все тут, нужен свежий взгляд. Ниже привел псевдокод, который по моему мнению должен нормально работать.
Имеем некоторую RTOS и 2 задачи для простоты.
Задача task1 имеет приоритет выше, чем task2. Задача task1 висит в ожидании данных от некоторой периферии.
Задача task2 должна принимать ПОСЛЕДНИЕ данные от task1, т.е если задача task1 получила скажем 10 буферов данных, то нам нужен в task2 только последний буфер, остальные можем безболезненно потерять (т.е. очередь не нужна).

Код
volatile int a,b,c;
int buf[3];
volatile int peri_data_available;

task1
{
while(1)
{
  if (wait_peri_data(&buf))
  {
    a = buf[0]; b = buf[1]; c = buf[2];
    peri_data_available = 1;
  }
  os_delay(1);
}
}

task2
{
int a_task, b_task, c_task;    

while(1)
{
  if ( peri_data_available )
  {
    do {
        peri_data_available = 0;
        a_task = a; b_task = b; c_task = c;
    }  while (peri_data_available);
    usefull_work( a_task, b_task, c_task );
  }
  os_delay(10);
}
}


Илья
Rev0.0
501-q, зачем вы указали цикл в task2, мне же нужны только последние данные цикл не нужен. Да и как без мьютексов, блок из трех переменных - единый, а в вашем коде задача может прервать операцию присваивания на одной из переменных и целостности блока не будет.
501-q
Приветствую!

Цитата(Rev0.0 @ Feb 3 2015, 16:38) *
зачем вы указали цикл в task2, мне же нужны только последние данные цикл не нужен. Да и как без мьютексов, блок из трех переменных - единый, а в вашем коде задача может прервать операцию присваивания на одной из переменных и целостности блока не будет.

Именно для целостности блока переменных нужен цикл. Если в процессе копирования переменных в task2 сработает task1 и обновит общие переменные, то в task2 повторим копирование переменных. Обычно будет только одна итерация цикла. Поэтому и мьютексы не нужны.

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

Илья
Rev0.0
Илья, верно, ваш вариант будет работать. НО! Есть вероятность зависания task2 в цикле если задача task1 постоянно принимает данные. Для корректной реализации вашего случая подойдет алгоритм Деккера, но я не хотел заниматься реализацией критических секций - хотелось бы использовать сервисы RTOS ибо они уже есть, поэтому и взял мьютексы.
AlexandrY
Цитата(Rev0.0 @ Feb 9 2015, 19:49) *
Илья, верно, ваш вариант будет работать. НО! Есть вероятность зависания task2 в цикле если задача task1 постоянно принимает данные. Для корректной реализации вашего случая подойдет алгоритм Деккера, но я не хотел заниматься реализацией критических секций - хотелось бы использовать сервисы RTOS ибо они уже есть, поэтому и взял мьютексы.


Алгоритм Деккера как раз здесь мертво зависнет.
Параллельное программирование и RTOS это разные вещи.
Этот алгоритм не рассчитан на задачи с разными приоритетами на одном ядре.
501-q
Приветствую!
Цитата(Rev0.0 @ Feb 9 2015, 23:49) *
НО! Есть вероятность зависания task2 в цикле если задача task1 постоянно принимает данные.

В task1 есть os_delay(1). Но в целом, замечание верное.

Илья
Russky
Есть несколько вариантов, но концепция верна:
Записывающая задача: залочили, записали, разлочили
Считывающая задача: залочили, считали, разлочили

Лично я бы, к мъютексу добавил бы еще семафор, правда я не знаю как он в этой операционке реализован.

Код
int a,b,c;
int buf[3];

task1
{
while(1)
{

  os_delay(1);
    mutex_lock();
    a = buf[0];
    b = buf[1];
    c = buf[2];
    mutex_unlock();
    release_semaphore();

}
}

task2
{
int a_task, b_task, c_task;    

while(1)
{

    bool res = wait_for_semaphore(timeout);
    if (res)
   {
    mutex_lock();        
    a_task = a;
    b_task = b;
    c_task = c;
    mutex_unlock();
   } else
{
    другие_действия();
}
  }
}
}
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.