@Tsvetik

Как сделать простую многозадачность на Си?

Микроконтроллер должен выполнять несколько несложных задач, например, общаться с медленными внешними устройствами и при этом долго ожидать флаги.
Например
void Task1()
{
runStage1();
while (!flagStage1Complete); // ожидание 1 с
runStage2();
while(!flagStage2Complete); // ожидание 1 с
finishTask1();
}

void Task2()
{
runStageA();
while (!flagStageAComplete); // ожидание 1 с
runStageB();
while(!flagStageBComplete); // ожидание 1 с
finishTask2();
}

void main()
{
while (1)
{
Task1();
Task2();
}
}


В простейшем варианте каждый Task будет выполняться раз в 4 секунды. Очевидно, что во время ожидания флагов в Task1() можно делать полезную работу или заодно проверять флаги из Task2() и наоборот. Но такой подход сильно свяжет Task1 и Task2 которые по логике программы могут вообще не иметь друг к другу отношения.

Обычно я эту проблему решаю с помощью switch
Например:

void Task1()
{ 
static int task1state=0;

switch (task1state)
{
case 0:
    runStage1();
    task1state=1;
    break;
case 1:
    if (flagStage1Complete)
    {
    runStage2();
    task1state=2;
    }
    break
case 2:
    if (flagStage2Complete)
    {
    finishTask1();
    task1state=0;
    }
    break;
}
}


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

При такой организации каждый Task выполняется раз в 2 секунды.

До некоторого времени такой подход себя оправдывал. пока каждый Task был довольно простым. Если же Task содержит множество стадий и разветвлённую логику, то огромный switch() становится нечитабельным.

Хочется писать Task в виде простой и читабельно функции:
runStageA();
waitforStageAComplete()
runStageB();
waitForStageBComplete()
finishTask2();


Но при этом, чтобы во время ожидания происходил выход из функции и переход к другому Task()

Можно написать планировщик, который будет запускать Task1, а функции waitFor...() будут прерываниями выходить в планировщик. Затем планировщик должен будет переключить контекст (стеки, регистры и т.п.) на Task2 и запустить его.
Кажется, это очень сложно.

Есть ли какой-то простой способ?
  • Вопрос задан
  • 1794 просмотра
Пригласить эксперта
Ответы на вопрос 4
Olej
@Olej
инженер, программист, преподаватель
Можно написать планировщик

Нужно не писать планировщик, а хорошо поискать среди инструментов: что есть для вашего случая обеспечивающих параллелизм?

А задавая вопрос нужно начинать того, что это за микроконтроллер, и каким инструментом вы его собираетесь программировать.
Ответ написан
Комментировать
@Mercury13
Программист на «си с крестами» и не только
Это называется «кооперативная многозадачность» и такая была в Windows до 3. Если задача у нас отнимает больше, чем один квант времени, есть два варианта.
1. Собрать кванты времени в конечный автомат. Каждый из элементов этого автомата должен сам сохранять свои регистры. Поскольку задача может быть сложная, удобно делать элементы автомата элементами структурного программирования — элемент «оператор», элемент «если», элемент «дождаться»… Мне это приходилось делать на Jav’е, с похожей целью — нужна была простейшая реализация сопрограмм с возможностью сохранения-загрузки, где мы остановились. Сложнее прикладное программирование, системное вообще не нужно.
2. Использовать функцию Yield, которая сохраняет куда-то регистры процессора, переключает стековый сегмент, определяет, кому сейчас работать, и восстанавливает регистры нового процесса. Каждый процесс поминутно запускает этот Yield. Тут наоборот — процесс наполняется Yield’ами, и всё, а архитектуру машины надо всё же знать.

Для подобного дела есть FreeRTOS, но как она работает — я не в курсе.
Ответ написан
Ocelot
@Ocelot
Для ваших целей прекрасно подойдет FreeRTOS. Ее можно настроить и так, чтобы планировщик сам прерывал выполнения задач и переключался между ними, и так, чтобы задача сама отдавала управление планировщику, когда нужно. И еще всякие плюшки вроде FIFO, семафоров и т.д.
Оно собирается под любую платформу: от AVR до x86.
Ответ написан
Комментировать
@Eddy_Em
Я подобные вещи элементарно реализую как КА. В основном цикле проверяются все флаги, если какой-то установлен, выполняются определенные действия. Большинство "медленных" задач выносится на DMA или прерывания, чтобы обеспечить минимальные задержки в основном цикле.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы