Перейти к содержанию
    

Процесс, ограниченный по времени

Доброго времени!

Требуется соорудить такую конструкцию:

предположим, у нас выполняется некий процесс. Считаем что-то, короче.

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

На ассемблере это делается элементарно.

Проблема в реализации этой конструкции на C - мешает нереентерабельность функций, огромные сохраняемые контексты.

Пока что представляю, как это сделать в рамках main() - с помощью setjump() и такой-то матери.:)

Как это сделать в любой другой функции - как эти функции заточить по правильному - не имею понятия.

В идеале хочу выйти на что-то типа

TIME_LIMITED_BLOCK(T_100us)
{/*code*/
}

 

взяв за основу for() как ATOMIC_BLOCK в WINAVR.

 

В общем,если есть какие-то советы, милости просим.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для решения таких вопросов (конечно же и для многих других) и создаются планировщики и ОС. Но без ОС решения тоже бывают.

 

"Мягкий" риалтайм делаю несложно поллингом значения таймера (сколько прошло от старта) либо признака, устанавливаемого в прерывании таймера. При этом для выхода/входа использую механизм статических сопрограмм http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html http://www.chiark.greenend.org.uk/~sgtatham/coroutine.h и его различные модификации, например, Protothreads http://www.sics.se/~adam/pt/index.html. Что касается таймера в AVR, то часто использую известное простое решение - декремент переменной в прерывании 16-и-битного таймера и поллинг её на ноль

#define Lo(X) (*(((unsigned char*)&X)+0))

#define Hi(X) (*(((unsigned char*)&X)+1))

 

//критическая секция для занесения значения в декрементируемую переменную

#define __countdown_start(X,VAL) do{\

unsigned char sreg = SREG; \

cli(); \

{ X = VAL; } \

SREG = sreg; \

}while(0)

 

#define __countdown16_start __countdown_start

#define __countdown16(X) ((Hi(X))?true:(!!(Lo(X)))) //безопасный вариант для поллинга на ноль без использования критических секций, правда иногда рубится оптимизатором компилятора GCC:(

 

Что касается жесткого риалтайма, то применяю его ТОЛЬКО там, где нельзя применить "мягкий" и часто всё необходимое спокойно укладывается небольшим объемом в тело обработчика. Но, конечно же, случаи бывают разные.

 

В некоторых камнях, вроде есть даже аппаратная поддержка сохранения контекста для нескольких процессов - кажись в Futjitsu такое.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

#define __countdown16_start __countdown_start

#define __countdown16(X) ((Hi(X))?true:(!!(Lo(X)))) //безопасный вариант для поллинга на ноль без использования критических секций, правда иногда рубится оптимизатором компилятора GCC:(

 

Дык надо volatile объявить. Рубиться не должно.

 

Что касается жесткого риалтайма, то применяю его ТОЛЬКО там, где нельзя применить "мягкий"

 

Итак, проясняются вопросы применимости.

Удобно мягкий риалтайм делать на математике, допустим, итерации заточить.

Неудобно в сложных автоматах. Как обстоят дела в этом случае?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Дык надо volatile объявить. Рубиться не должно.

Объявлял. Чего-то версии 071221 было не так ещё. С тех пор к программингу AVR на WinAVR не получалось прикасаться.

Неудобно в сложных автоматах. Как обстоят дела в этом случае?

Для взаимоувязанных дискретных выходов офромляю "программные генераторы", обслуживающие пины в жестком риалтайме (в обработчике прерываний таймера), а задания для них формирую в фоне. Что касается вариантов с разрешением порядком 1 мс, то плавание этой 1-й мс в диапазоне, например, 0... +20 мкс (минуса НИКОГДА не будет) часто никак не сказывается на реальном оборудовании и реальных системах в разумных оценках. Как пример - управляю быстрым клапаном, для которого 1 мс это гарантированное минимальное время открывания (с хорошим запасом для учета воздействий температуры, состава носителя, износа и пр.) - дык производительность системы не страдает (ну не заметно этого никак и ничем). Грубо - ну будет в среднем ошибка +10 мкс - соотнесём это с разной шероховатостью кромок всяких железочек там, неточностью диаметра отверстия от экземпляра к экземпляру и разной вязкостью носителя в разное время, зависящее от погоды в Ханты-Мансийске - в общей погрешности погрешность времени срабатывания окажется ничтожно малой, и это с учётом того, что взят худший случай - минимальное время импульса открывания.

Если рассматривать возможные ошибки между параллельными во времени автоматами, то даже с использованием прерываний абсолютную параллельность на одном процессорном ядре никак не получить, а при мягком риалтайме сетка времени обычно выбирается одна для разных параллельных процессов. Конечно если возникает необходимость в разрешении взаимно-зависимых временных коллизий, то тут нужно определиться - насколько количество таких завязок велико и решать либо вопросы взаимоблокировок по месту, либо строить общий программный механизм, рулящий приоритетами обслуживания временнЫх событий. Не буду пытаться всё обозвать правильными терминами - там, напрмер, кое-чего есть http://george-sergeev.by.ru/osch4.html

И ещё - вспоминается POSIX 1003.1b - http://qnx.org.ru/article11.html - ведь ОС вааще не знает об алгоритмических зависимостях между процессами (грубо - каждое ядро занято своим процессом - как им объяснить, что кто-то главнее? - приоритеты, но приоритеты - палка о двух концах, потому если есть возможность их - разноуровневые - не употреблять, то лучше и не встрявать; и ими ещё надо не только пользоваться, но и уметь рулить в конкретной системе).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

вместо вытесняющей многозадачности возможно применение кооперативной многозадачности.

 

Кооперативка, реализованная на С, настолько некрасива, по сравнению, например,с таким подходом на асме , что у меня язык не поворачивается называть эти все switch(state) процессом. :)

 

Я аналогично делаю. Есть единый счетчик тиков...

 

Дык это ужЕ аксиома. Имеем "длинный" счетчик с тиком 10 мс (или 1мс, но 32-бита), с флагом предупреждения об "атомарности" доступа (т.е. он выскочит когда значение младшего байта равно 0xfe и снимется - когда будет 0x01), после чего жаба за ресурсы не удушит, также быстрый 8-битный с тиком переполнений от 8-битного таймера.

 

По теме.

Решил пойти от обратного. Имеем априорно известное максимально допустимое время выполнения программы, выше которого все развалится. Поскольку ограничивать по времени надо процессы с наименьшим приоритетом, можно замерять и фиксировать максимальное время выполнения процесса, а затем перед следующим вызовом проверять, успеет оно поработать или нет. Если не успевает - то не вызывать. Количество не-вызовов тоже подсчитываем и, если их много, сбрасываем измеренное время выполнения в ноль. И на все 3-4 байт на задачу.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Опять приперло пересмотреть концепцию. Эх, ОС-пользователи, не близки вам страдания негритянского народа ! :)

В общем, сделал красивше во сто раз, о чем и хвастаюсь:

Это препарированный timer.h

#ifndef TIMER_H_INCLUDED
#define TIMER_H_INCLUDED 1

typedef uint16_t timer_t;
//typedef uint32_t timer_t;

extern volatile timer_t ticks;
timer_t get_time(timer_t from);

#define ENABLE_LIFETIME_SW

#ifdef ENABLE_LIFETIME_SW
#include <setjmp.h>
typedef struct 
{
timer_t time;
jmp_buf context;
} lifetime_t;

typedef void( * limited_proc_t)(void);
extern volatile lifetime_t *lifetime;
unsigned char time_limited(timer_t time, limited_proc_t process);

#endif

#endif // TIMER_H_INCLUDED

 

Это реализация time_limited

 


ISR(TIMER0_COMPA_vect)

{

ticks++;

#ifdef ENABLE_LIFETIME_SW 
if((lifetime) && (lifetime->time >= ticks))
{
 lifetime_t *tmp = lifetime;
 lifetime = 0; // purge
 longjmp(tmp->context,1);
}
#endif 

}

/*...........*/

#ifdef ENABLE_LIFETIME_SW
unsigned char time_limited(timer_t time, limited_proc_t process)
{
lifetime_t list;
list.time = time + get_time(0);
if(setjmp(list.context)) return 1; // aborted
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) lifetime = &list;
process();
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) lifetime = 0;// UPD
return 0;
}
#endif

 

Тестовая программа


#include <avr/io.h>
#include "timer.h"
void MyProcess(void);
int main(void)
{
time_limited(100,MyProcess); 
return 0;
}

void MyProcess(void)
{
volatile int a,b,c;
a = b + c;
return;
}

 

И попутно вопрос. Приятно удивила реентерабельность void MyProcess(void)

void MyProcess(void)
{
 6c:	df 93       	push	r29
 6e:	cf 93       	push	r28
 70:	00 d0       	rcall	.+0      	; 0x72 
 72:	00 d0       	rcall	.+0      	; 0x74 
 74:	00 d0       	rcall	.+0      	; 0x76 
 76:	cd b7       	in	r28, 0x3d	; 61
 78:	de b7       	in	r29, 0x3e	; 62
volatile int a,b,c;
a = b + c;
 7a:	2b 81       	ldd	r18, Y+3	; 0x03
 7c:	3c 81       	ldd	r19, Y+4	; 0x04
 7e:	8d 81       	ldd	r24, Y+5	; 0x05
 80:	9e 81       	ldd	r25, Y+6	; 0x06
 82:	82 0f       	add	r24, r18
 84:	93 1f       	adc	r25, r19
 86:	9a 83       	std	Y+2, r25	; 0x02
 88:	89 83       	std	Y+1, r24	; 0x01
return;
 8a:	26 96       	adiw	r28, 0x06	; 6
 8c:	0f b6       	in	r0, 0x3f	; 63
 8e:	f8 94       	cli
 90:	de bf       	out	0x3e, r29	; 62
 92:	0f be       	out	0x3f, r0	; 63
 94:	cd bf       	out	0x3d, r28	; 61
 96:	cf 91       	pop	r28
 98:	df 91       	pop	r29
 9a:	08 95       	ret

 

Это всегда так должно быть? Или этим можно управлять как-то ?

UPD исправлено - вставил пропущенную строчку.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я пользуюсь такой конецепцией.

Разбиваю все процессы на линейные быстро выполняющиеся блоки работающие со своими локальными структурами данных.

Например такой процесс:

 

{
    TTHIS_PROCESS_CONTEXT context;

    for(;;)
    {
       wait_sema( DATA_READY );
       {
          for(i = 0; i < N; i++)
          {
               iteration( &context, i );
          }
          post_result( &cpntext);
       }
    }
}

 

Можно преврать в

{
   static TTHIS_PROCESS_CONTEXT context;
   static i = 0;

   if (!DATA_READY)
       return;

   interation( &context, i );
   if (++i >= N)
   {
      post_result( &context );
      i = 0;
   }
}

Что это дает. Это дает возможность упростить планировщик до тупого поочередного вызыва функций всех процессов. Если процессу нужно подождать некоторое время, то достаточно его удалить из списка процессов на некоторое время. Такой подход позволяет наиболее эффективно использовать стек память - что для AVR весьма актуально. (Скажем имеется 1KB стека и 10 процессов. В классической ОС 1KB стека превратится в 100 байт стека для отдельно взятого процесса, а в описанном случае - каждый процесс получит в свое распоряжение этот 1KB).

 

 

Решил пойти от обратного. Имеем априорно известное максимально допустимое время выполнения программы, выше которого все развалится. Поскольку ограничивать по времени надо процессы с наименьшим приоритетом, можно замерять и фиксировать максимальное время выполнения процесса, а затем перед следующим вызовом проверять, успеет оно поработать или нет. Если не успевает - то не вызывать. Количество не-вызовов тоже подсчитываем и, если их много, сбрасываем измеренное время выполнения в ноль. И на все 3-4 байт на задачу.

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

Планировщик можно построить как-то так:

 

U32 local_time = 0;
U8 dispatch_cycle = 0;
volatile U8 timer_tick = 0;

Sys_Dispatch(void)
{
    Sys_Idle(); 

    if (!dispatch_cycle)
       return;

    dispatch_cycle = FALSE;

    for(int i = 0; i < NUM_USER_TASKS; i++)
        user_tasks[i]();
}


Sys_Idle(void)
{
    if (timer_tick)
    {
        local_time += 1;
        timer_tick = FALSE;
        dispatch_cycle = TRUE;
    }

    for(int i = 0; i < NUM_DRIVER_TASKS; i++)
        driver_tasks[ i ]();
}


U32 Sys_GetTime(void)
{
    return local_time;
}


Sys_Wait(U32 ticks)
{
    U32 time = Sys_GetTime(); 
    while(  Sys_GetTime() - time < ticks)
         Sys_Idle();
}


ISR(TICK_TIMER)
{
     timer_tick = TRUE;  
}


main()
{
     .... add user tasks to user task list
     .... add driver tasks to driver task list

     for(;;)
     {
         Sys_Dispatch();
     }
}

 

Внутри громоздких "user task" иногда вызывать Sys_Idle() или Sys_Wait() тем самым давая возможность отработать высокоприоритетным задачам.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Внутри громоздких "user task" иногда вызывать Sys_Idle() или Sys_Wait() тем самым давая возможность отработать высокоприоритетным задачам.

 

Делал так тоже. То, что Вы говорите - тоже практически "классика жанра" кооперативной многозадачности. Добавлю свои 5 копеек:

 

 

void Sys_Idle( char priority)

{
  switch(priority)
{
 case 0: do_statistic();
 case 1: do_screen();
 case 2: do_UART();
}
return;
}

 

Говоря по-русски, можно при вызове Sys_Idle() фильтровать вызываемые процессы по их приоритетам.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Делал так тоже. То, что Вы говорите - тоже практически "классика жанра" кооперативной многозадачности.

Угу и никаких switch'ей как видите ;)

Для полного удобства работы можно еще добавить одноразовые колбеки с заданной задержкой запуска.

С их помощью управлять постановкой и снятием задачь из списка.

 

Добавлю свои 5 копеек:

Ну понятно, что поле для улучшения есть.

Можно навводить event'ы, семафоры, временнЫе параметры (когда или через сколько кого запускать) и т.п.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...