2014-11-21 4 views
2

В настоящее время я пишу приложение, зависящее от времени встраиваемых систем, в частности, на ARM Cortex M4. Он имеет ряд задач, которые происходят на разных частотах, и поэтому мы используем таймер SysTick ARMs для отслеживания времени в пределах 1-секундного кадра. Основной занят цикл ожидания, пока таймер не увеличивается и начинает задачу, а так:В C самый простой способ отменить задачу?

void main(){ 
while(1){ 
    if(!timerTicked) 
    continue; //timer hasn't updated since last loop, do nothing. 
    if(time==0) 
    toggleStatusLED(); 
    if(time==0 || time==500){ 
    twoHzTask(); //some task that must run twice a second 
    } 
    if(time%300==5){ //run at ms=5, 305 and 605 
    threeHzTask(); //some task that must run three times a second 
    } 
} 
} 

void SysTick_increment(){ //Interrupt Service Routine for SysTick timer 
time=(time+1)%1000; 
} 

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

void main(){ 
while(1){ 
    if(time==500){ //run at ms=500 
    savingWorld=1; 
    saveTheWorld(); //super important task 
    savingWorld=0; 
    } 
    else if(time==750){ 
    spinningWheels=1; 
    spinWheels(); //not very important task. Code for this function is immutable, can not alter it to support task cancellation. 
    spinningWheels=0; 
    } 
} 
} 

void SysTick_increment(){ //Interrupt Service Routine for SysTick timer 
time=(time+1)%1000; 
if(time==900 && spinningWheels){ 
    spinningWheels_cancel(); //we have more important stuff to do 
} 
} 

Как это базовая встроенная система, я бы предпочел, чтобы не вводить сложность операционной системы или многопоточного механизма. Кроме того, остальная система должна находиться в стабильном состоянии после вызова spinningWheels_cancel(). То есть saveTheWorld() должен по-прежнему работать должным образом. Каков самый простой способ сделать это?

+1

Если вы не можете изменить spinWheels(), вы не сможете отменить его после его запуска, поэтому у вас нет выбора, кроме как «не выполнить его», если вы слишком близко к времени выполнения задачи saveTheWorld() нет? Поэтому, если spinWheels() занимает более 750 циклов в вашей системе, он все равно будет работать, когда придет время для запуска saveTheWorld(), и ничего не поделаешь. Альтернативой может быть запуск saveTheWord() в ISR, но зависит от того, что он делает. – Chris

+0

Ну, твоя идея рода работ, хотя время жестко составляет 900, вероятно, не идеально. > = 900 было бы лучше, если время будет ровно 900, и вы прерываете между ними 'else if (time == 750)' и 'spinningWheels = 1;', вы не поймаете, не останавливая вращение колес, но все равно войдете в задачу как только вы вернетесь из ISR. Хотя вам не нужна полная RTOS для этого, вам придется реализовать части базового. В основном, я думаю, что обратный адрес вашего 'SysTick_increment' должен измениться, если вы отмените вращение колеса, чтобы избежать возврата обратно в эту задачу. – Unn

+0

Я думаю, что у Криса есть хорошая идея, хотя, используя ISR для ваших самых важных задач, так они могут прерывать менее важные. (Хотя будьте осторожны, они не блокируют друг друга). – Unn

ответ

2

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

Тогда spinningWheels() будет использовать переменные частного состояния, чтобы помнить, где он остановился, и продолжить вращение на какое-то время. В зависимости от его характера это может быть настроенное количество итераций внутреннего цикла, или это может означать выборку time и отказ от нее, когда она слишком велика.

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

Учитывая только совместные задачи, рассмотрите, какая должна быть ваша предлагаемая функция spinningWheels_cancel(). Он вызывается из прерывания таймера, поэтому он ограничен функциями, которые, как известно, являются реентерабельными. Поскольку у вас нет официального планировщика и отдельных задач, он не может фактически «убить» задачу. Таким образом, его реализация может установить только атомный флаг, который проверяется всеми внутренними петлями в spinningWheels() и используется для сигнализации о начале выхода.

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

Существует довольно много ядер RTOS в дикой природе, которые имеют малый вес, обеспечивают упреждающее планирование и поддерживают ARM. Как коммерческие (с широким спектром лицензионных моделей), так и свободные кандидаты находятся там, но ожидают, что процесс отбора будет нетривиальным.

+0

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

+0

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

2

Я думаю, что планировщик RTOS мог бы представить простоту, а не вводить сложность (один из которых у вас есть на вашей целевой цели). Независимые параллельно планируемые задачи могут улучшить cohesion и уменьшить муфта - обе желаемые цели в любой системе.

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

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

Например, основываясь на вашей схеме, но, возможно, несколько надуманный для целей иллюстрации и из-за недостатка информации:

#include <stdio.h> 

#define TICKS_PER_SECOND = 100u ; // for example 
volatile uint32_t tick ; 

void SysTick_increment() 
{ 
    tick++ ; 
} 

void main() 
{ 
    for(;;) 
    { 
     LedUpdate() ; 
     TwoHzTask() ; 
     ThreeHzTask() ; 
     SpinningWheels() ; 
    } 
} 

void LedUpdate() 
{ 
    static uint32_t last_event_timestamp = tick ; 
    uint32_t now = tick ; 

    if(now - last_event_timestamp >= TICKS_PER_SECOND) 
    { 
     last_event_timestamp = now ; 
     toggleStatusLED() ; 
    } 
} 

void TwoHzTask() 
{ 
    static uint32_t last_event_timestamp = tick ; 
    uint32_t now = tick ; 

    if(now - last_event_timestamp >= 2 * TICKS_PER_SECOND) 
    { 
     last_event_timestamp = now ; 
     // Do 2Hz task... 
    } 
} 

void ThreeHzTask() 
{ 
    static uint32_t last_event_timestamp = tick ; 
    uint32_t now = tick ; 

    if(now - last_event_timestamp >= 3 * TICKS_PER_SECOND) 
    { 
     last_event_timestamp = now ; 
     // Do 3Hz task... 
    } 
} 

void SpinningWheels() 
{ 
    static enum 
    { 
     IDLE, SPINNING; 
    } state = IDLE ; 

    switch(state) 
    { 
     case IDLE ; 
     { 
      if(startSpinning()) 
      { 
       state = SPINNING ; 
      } 
     } 
     break ; 

     case SPINNING ; 
     { 
      bool keep_spinning = doOneSpin() ; 
      if(!keep_spinning) 
      { 
       state = IDLE ; 
      } 
     } 
     break ; 
    } 
} 

Важно отметить, что ни один из четырех «задачи» функции зависит от других, чтобы определить, когда они выполняются, кроме того, что задача «хорошо себя вести» не блокирует, чтобы ждать какого-либо события - она ​​просто устанавливает свое состояние, указывающее, что оно ждет этого события, и и/или выполняет некоторые действия при возникновении этого события. «Задача» не должна быть занята - ждать или выполнять какую-либо длительную обработку, которая не будет потреблять в реальном времени другие задачи. Так, например, если у вас может быть длина или не детерминированный цикл, вы бы повторно определили это, чтобы быть в «состоянии итерации», где одна итерация выполняется для каждого вызова (например, функция doOneSpin() в приведенном выше примере).

Обратите внимание, что в исходном систоле выражение time=(time+1)%1000 - это действительно плохая идея, и ее следует избегать. Это позволяет обрабатывать временную проблему, если вы вынуждаете ее обернуть каждые 1000 тиков. В идеале он должен обернуть некоторую мощность 2, и поскольку в любом случае все целые типы имеют мощность в два диапазона, вы можете просто увеличить его, как я сделал. Если вы это сделаете, выражения, такие как now - last_event_timestamp, дадут правильную разницу во времени даже тогда, когда счетчик обернулся (пока это не обернуто дважды за это время), например 1 - 999 даст ответ -998 (или 4294966298 если без знака), тогда как 1 - 0xffffffff = 2

Еще одна причина, почему следует избегать выражения в ISR, заключается в том, что оператор% имеет операцию неявного деления, которая является относительно дорогой и принимает переменное количество тактовых циклов. Разумеется, мы говорим о небольшом количестве времени, но в некоторых жестких приложениях реального времени вызванный дрожанием может быть нежелательным.

Смежные вопросы