Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Управление контекстом БЕЗ RTOS
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
Страницы: 1, 2
yanvasiij
Я понимаю, что сейчас речь пойдет об изобретении велосипеда. Но мне это важно для понимания. В ОСРВах переключение между задачами осуществляется за счет переключения контекста. Мне интересно каким образом можно организовать такое переключения самостоятельно. Например, есть две функции ledBlinking1() и ledBlinking2():
Код
void ledBlinging1 (vodi)
{
    led1On();
    delayms(1000);
    led1Off();
    delayms(1000);
}

void ledBlinging2 (vodi)
{
    led2On();
    delayms(1000);
    led2Off();
    delayms(1000);
}

При размещении этих функций внутри задач rtos они будут моргать не влияя друг на друга. А если я озадачусь сделать тоже моргание без rtos, так чтобы они моргали независимо, то мне придется накидать витиеватый алгоритм запоминания предыдущего состояния и следить, сколько времени прошло, не пора бы потушить или зажечь... Но если я реализую функцию загрузки и выгрузки контекста функции, то такой алгоритм городить не придется. Пусть такая функция есть и называется она downloadContext (), а функция приостановки и сохранения контекста yeld(). Тогда, то же моргание без РТОС будет выглядеть следующим образом:

Код
void ledBlinging1 (void)
{
    led1On();
    timer1.setMs(1000);
    while (timer1.expired() == FALSE) yeld();
    led1Off();
    timer1.setMs(1000);
    while (timer1.expired() == FALSE) yeld();

}

void ledBlinging2 (void)
{
    led1On();
    timer1.setMs(1000);
    while (timer2.expired() == FALSE) yeld();
    led2Off();
    timer2.setMs(1000);
    while (timer2.expired() == FALSE) yeld();
}

int main (void)
{
    while (1)
    {
        downloadContext (ledBlinging1);
        downloadContext (ledBlinging2);
    }
}


Может я чего неправильно сказал, но надеюсь смысл понятен. Вопрос: как сделать такое сохранение и загрузку контекста? Где про это можно почитать, посмотреть пример и т.п.
Спасибо!
1113
ваш main() это и есть диспетчер задач. таким образом ваше предположение "А если я озадачусь сделать тоже моргание без rtos" уже не верно. то что вы придумали - и есть RTOS.
yanvasiij
Цитата(1113 @ Nov 21 2014, 13:09) *
ваш main() это и есть диспетчер задач. таким образом ваше предположение "А если я озадачусь сделать тоже моргание без rtos" уже не верно. то что вы придумали - и есть RTOS.


Ну пусть так, я же говорю - изобретение велосипеда. Мне интересно как это сделать - сохранить контекст функции, а потом восстановить, когда потребуется. В некоторых языка программирования это называется Coroutines кажется. Функция запоминающее свое предыдущее состояние.
1113
в СИ такого нет. вам надо реализовать надстройку над СИ, которая периодически будет сохранять контексты процессов и менять их между собой. имхо простой способ понять эту кухню - изучить CooсoxOS. в ней всё сделано как в книжке.

если ваш процессор ARM, то есть несложные статьи на тему сохранения и замещения контекста процесса.
yanvasiij
Ну я вот думал, что мне предложат ассемблерный сохраняльщик контекста. CooCoxOS - это ртосина я так понимаю, в ее доках описан принцип работы ее планировщика? Да - процессор АRM
1113
всё описано, даже на русском. а "сохраняльщик" там - на ассемблере.
scifi
Цитата(yanvasiij @ Nov 21 2014, 11:04) *
витиеватый алгоритм запоминания предыдущего состояния

Всё придумано до нас: protothreads.
demiurg_spb
Цитата(1113 @ Nov 21 2014, 12:23) *
в СИ такого нет

https://ru.wikipedia.org/wiki/Setjmp.h
yanvasiij
Цитата(scifi @ Nov 21 2014, 13:25) *
Всё придумано до нас: protothreads.


Извиняюсь за выражение, но просто Офигеть! blink.gif blink.gif blink.gif Это конечно не сохранение/загрузка контекста в том виде, в котором я думал, но тоже решение.

Цитата(demiurg_spb @ Nov 21 2014, 13:36) *

Этой штукой вообще безопасно пользоваться?
scifi
Цитата(yanvasiij @ Nov 21 2014, 11:39) *
Извиняюсь за выражение, но просто Офигеть! blink.gif blink.gif blink.gif Это конечно не сохранение/загрузка контекста в том виде, в котором я думал, но тоже решение.

Хорошее решение. Широко применяется, позволяет получать отличные результаты. Названия макросов оставляют желать лучшего: заглавные буквы пестрят, длинные имена растягивают код вправо. А в остальном всё хорошо.
yanvasiij
Цитата(scifi @ Nov 21 2014, 13:42) *
Хорошее решение. Широко применяется, позволяет получать отличные результаты. Названия макросов оставляют желать лучшего: заглавные буквы пестрят, длинные имена растягивают код вправо. А в остальном всё хорошо.

РТОСина на 8 килобайт. Ох, сколько же килограмм кода можно было сэкономить с этой штукой, где Вы были раньше ))
1113
Цитата(yanvasiij @ Nov 21 2014, 11:41) *
Извиняюсь за выражение, но просто Офигеть! blink.gif blink.gif blink.gif Это конечно не сохранение/загрузка контекста в том виде, в котором я думал, но тоже решение.
для такого типа "многозадачности" код в своих процессах должен быть прерываемым, типа state-машины.
yanvasiij
Цитата(1113 @ Nov 21 2014, 13:53) *
для такого типа "многозадачности" код в своих процессах должен быть прерываемым, типа state-машины.


Ну да, я это понимаю. Просто я получилось так, что я спрашивал, как работать со контекстом функции, чтобы строить state-машины. А посоветовали готовое средство для них. sm.gif Правда я по-прежнему озадачен вопросом работы с контекстом для целей самообразования.
ViKo
Цитата(yanvasiij @ Nov 21 2014, 11:51) *
РТОСина на 8 килобайт. Ох, сколько же килограмм кода можно было сэкономить с этой штукой, где Вы были раньше ))

RTOS-ина на 5KB. http://www.keil.com/pack/doc/cmsis_rtx/_technical_data.html
Ох!
Golikov A.
прерывание в этом не поможет? Собственно как в РТОсах и сделано
возникает прерывание вы влетаете в него
у вас есть адрес возврата (причем в стеке), и указатель стека.

теперь вы меняете указатель стека на другое место а адрес возврата тоже меняется на новую функцию.
Старое все сохраняете

со следующим влетом в прерывание сохраняете полученное, и восстанавливаете прошлое, так и получиться что будете по чуть чуть делать от каждой функции, и контекст (стек и адрес работы) перебрасывать... или я чего то не понял?
1113
Цитата(ViKo @ Nov 21 2014, 12:08) *
http://www.coocox.org/CoOS.htm
974 байта.
demiurg_spb
Цитата(yanvasiij @ Nov 21 2014, 12:41) *
Этой штукой вообще безопасно пользоваться?
Конечно нет!:)
Как и любой режущий инструмент очень опасен для жизни, но всё же им пользуются...
AlexandrY
Цитата(yanvasiij @ Nov 21 2014, 10:04) *
Может я чего неправильно сказал, но надеюсь смысл понятен. Вопрос: как сделать такое сохранение и загрузку контекста? Где про это можно почитать, посмотреть пример и т.п.
Спасибо!


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

Переключение будет выглядеть вот так:
Код
/*
* Performs a context switch between two threads.
*/
                PUBLIC _port_switch
_port_switch:
                push    {r4, r5, r6, r7, lr}
                mov     r4, r8
                mov     r5, r9
                mov     r6, r10
                mov     r7, r11
                push    {r4, r5, r6, r7}
                mov     r3, sp
                str     r3, [r1, #CONTEXT_OFFSET]
                ldr     r3, [r0, #CONTEXT_OFFSET]
                mov     sp, r3
                pop     {r4, r5, r6, r7}
                mov     r8, r4
                mov     r9, r5
                mov     r10, r6
                mov     r11, r7
                pop     {r4, r5, r6, r7, pc}


А смотреть надо стандарт ARM EABI по поводу того какие регистры сохранять, а какие не надо сохранять.
SSerge
Цитата(scifi @ Nov 21 2014, 15:25) *
Всё придумано до нас: protothreads.

Кстати, о protothreads.
С использованием возможностей С++ становится ещё удобнее.
http://blog.brush.co.nz/2008/07/protothreads/
MrYuran
А если немного подумать, может и не нужна никакая многозадачность и вполне достаточно event-driven state machine
В случае с двумя светодиодами - однозначно

Я к qp давно приглядываюсь, а он за это время становится все лучше и лучше
AlexandrY
Цитата(MrYuran @ Nov 21 2014, 14:46) *
А если немного подумать, может и не нужна никакая многозадачность и вполне достаточно event-driven state machine
В случае с двумя светодиодами - однозначно

Я к qp давно приглядываюсь, а он за это время становится все лучше и лучше


Для state machine лучше IAR visualSTATE трудно что либо найти.

Неутомимая Xenia у нас тут с этим помогает.
MrYuran
Цитата(AlexandrY @ Nov 21 2014, 17:06) *
Неутомимая Xenia у нас тут с этим помогает.

Я из противоположной конфессии. К сожалению. Или к счастью.
scifi
Цитата(MrYuran @ Nov 21 2014, 15:46) *
А если немного подумать, может и не нужна никакая многозадачность и вполне достаточно event-driven state machine
В случае с двумя светодиодами - однозначно

Как сказать. Если программа описывает процесс, линейно текущий во времени, с ветвлениями и переходами, то она естественным образом ложится на сишный код, и мне такое представление кажется интуитивно более понятным. И protothreads тут весьма кстати. Те же две лампочки вписываются в эту схему.
Да, всё можно свести к конечному автомату, но не всегда стоит это делать, так как читаемость кода может страдать.
WitFed
Я лично после долгого знакомства с ОС2000 без отладчика и попытками обуздать там 100 нитей очень негативно отношусь ко всякой шизофрении, когда непонятно, "кто шил костюм", а память портится, причём нестабильно.
Хотя периодически сильно хочется почитать научный обзор страниц так на 100, что же там придумано на текущий момент в этой области. Но по-русски и без наездов.
Лучше всего ИМХО 1-нитевые "шедулеры", которые по очереди исполняют функции, которые были добавлены в список до того, и каждая функция добавляет новые адреса исполнения в список, плюс её возвращаемое значение -- адрес функции, которая должна выполниться после этой, ну или 0, если эта "нитка" завершена.
Задача бъётся на кучу функций, которые имеют глобальные данные (возможно, в отдельных namespace, или там там сидят указатели на них в куче), и никакого сохранения контекста и переключения десятков регистров не нужно, стек у всех один ! Бродить в переключателе контекста на asm-е не надо, чтобы понять, куда меня перешедуливают, само переключение -- выход из одной функции и вызов другой, данные глобальны.
Шедулер тупо вызывает по кругу свой массив адресов функций, они "как бы крутятся", возвращая вместо себя "наследника" в тот же адрес массива на то же место, и что-то кусочно исполняют.
Также можно завести отдельный массив "ожидателей" -- системного таймера или не 0 по какому-то адресу, который будет и "семафором", "мьютексом", и чем угодно -- не нужны никакие критические секции, никто у тебя за спиной бяку никогда не сделает в непонятной нити с высоким приоритетом, лишь бы был отладчик хоть какой-то в IDE.
Прерывания тоже можно использовать, но только в виде исключения -- если уж точно кто-то чего-то требует за 100 тактов, а не за 1000 (в порядке очереди). Ибо от прерываний основные проблемы нестабильности в ртосах всяких, когда и надо что-то бы сделать "где-то", а "там" в этот же момент тоже вдруг кто-то ковыряется "под стрелой"...
Такие коды получаются абсолютно переносимыми (ну, кроме таймера), без-ассемблерными. И все контексты сидят "в домиках" возле каждой функции, их использующей, локальные автоматические переменные действуют недолго и не требуют сохранения между вызовами. Понятно, что ожидания и большие объёмы обработки там недопустимы, надо бить процессы на непрерывные куски.
Случилось нечто -- запускается "процесс" его обработки, т.е. подшедуливается функция, где она расписана.
Единственное плохо -- все варианты событий должны быть известны заранее, хотя во встроенных небольших системах так обычно и есть. Можно и параметры передавать в шедулер, с которыми планируется вызов функции, но это уже непрозрачней гораздо.
Код ТС получается типа такого:
Код
typedef (func*)(void);
func led1On() {/*make*/ addDelayed(led1Off, 1000); return NULL; }
func led1Off() {/*make*/ addDelayed(led1On, 1000); return NULL; }
func led2On() {/*make*/ addDelayed(led2Off, 1000); return NULL; }
func led2Off() {/*make*/ addDelayed(led2On, 1000); return NULL; }

int main() {
  addDelayed(led1On, 1000);
  addDelayed(led1Off, 1000);
  main_loop();
}

Вдруг на самом деле что такое уже есть в мировой практике -- подскажите.
Golikov A.
ага супер луп на конечных автоматах называется... это то что было до операционок.
Вы придумали шаг назадsm.gif...

jcxz
Цитата(WitFed @ Nov 27 2014, 20:04) *
Также можно завести отдельный массив "ожидателей" -- системного таймера или не 0 по какому-то адресу, который будет и "семафором", "мьютексом", и чем угодно -- не нужны никакие критические секции, никто у тебя за спиной бяку никогда не сделает в непонятной нити с высоким приоритетом, лишь бы был отладчик хоть какой-то в IDE.

Непонятно - почему не нужны критические секции? Как без них если нужна атомарность доступа к объекту?

Случай (работа под ОС): семафор проверяется внутри критической секции. При его занятости, ОС переводит таск в сост. "ожидает данный семафор" и переключает контекст.
А как Вы своим велосипедом такой случай разрулите? sm.gif
А если внутри двух критических секций?
А как реализуете функцию типа WaitForMultipleObjects?

Все плюсы и минусы построения диспетчера с переключением контекста (ОС) и без (все вариации суперцикла) думаю давно известны.
Суперцикл хорош, только если не нужен блокирующий доступ к ресурсам (защищённым семафорами и мьютексами).
AlexandrY
Цитата(WitFed @ Nov 27 2014, 16:04) *
Лучше всего ИМХО 1-нитевые "шедулеры", которые по очереди исполняют функции, которые были добавлены в список до того, и каждая функция добавляет новые адреса исполнения в список, плюс её возвращаемое значение ...


Извините, но ваш пост воспринимается как малопонятный набор слов.
А приведенный код попахивает рекурсией. А рекурсия это самое вредное что можно придумать во встраиваемой системе. Убьете стек и не заметите.

Может вы хотели сказать о машинах состояний управляемых шаблонами (цепочками состояний)?
На более высоком уровне это называется интерперетаторы.

Приведу пример простейшей машины состояний по шаблону отрабатывающей управление массивом соленоидов.

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

Этот автомат применяется мною и с RTOS и без.
Сервисы RTOS здесь мало применимы, потому что им тут нечего делать и поскольку момент коммутации соленоидов строго привязан к окончанию выборки АЦП, так чтобы они не засоряли сигнал АЦП.

CODE
// Управляющая структура машины состояний управляемой шаблоном
typedef struct
{
INT32U init_state;
INT32U counter;
INT32U *pattern_start_ptr; // Указатель на массив констант int являющийся цепочкой состояний (шаблоном)
// Если значение в массиве = 0xFFFF, то процесс обработки завершается
// Если значение в массиве = 0x0000, то вернуть указатель на начало цепочки
INT32U *pttn_ptr; // Текущая позиция в цепочке состояний

} T_solnd_ptrn;

// Шаблоны работы соленоидов
// ----------------------------------------------------------------------
// Шаблон состоит из массива пар слов.
// Первое слово - значение напряжения (в процентах от максимального) на текущем интервале времени
// Второе - длительность интервала времени в мс
// интервал равный 0 - означает возврат в начало шаблона
// интервал равный 65535 - означает застывание состояния

const INT32U SOLENOID1_ON[10] = { 100, 300, 25, 300, 100, 300, 25, 300, 25, 0xFFFF };
const INT32U SOLENOID2_ON[10] = { 25, 300, 100, 300, 25, 300, 100, 300, 25, 0xFFFF };
const INT32U SOLENOID_OFF[4] = { 0, 2000, 0, 0xFFFF };
// ----------------------------------------------------------------------

// Массив управляющих структур соленоидов .
T_solnd_ptrn solnd_cbl[2];


/*-------------------------------------------------------------------------------------------------------------
Инициализация машины состояний заданным шаблоном
*pttn - указатель на начало шаблона
n - индекс соленоида (0..1)
-------------------------------------------------------------------------------------------------------------*/
void Set_Solenoid_pattern(const INT32U *pttn, INT32U n)
{
if ( pttn != 0 )
{
solnd_cbl[n].pattern_start_ptr = (INT32U *)pttn;
solnd_cbl[n].pttn_ptr = (INT32U *)pttn;
Set_solenoids_voltage(*solnd_cbl[n].pttn_ptr, n);
solnd_cbl[n].pttn_ptr++;
solnd_cbl[n].counter = *solnd_cbl[n].pttn_ptr;
solnd_cbl[n].pttn_ptr++;
}
}

/*------------------------------------------------------------------------------
Вызывается из процедуры прерывания АЦП с периодичностью ADC_PERIOD
------------------------------------------------------------------------------*/
void Solenoid_automat(void)
{
static INT32U solnd_presc = 0;
INT32U duration;
INT32U voltage;
INT32U n;

solnd_presc++;

if ( solnd_presc >= (1000 / ADC_PERIOD) ) // Обработка ведется с периодом повторения - 1 мс
{
solnd_presc = 0;
for (n=0; n<2; n++)
{
// Управление состоянием сигнала соленоида
if ( solnd_cbl[n].counter )
{
solnd_cbl[n].counter--;
if ( solnd_cbl[n].counter == 0 ) // Меняем состояние сигнала при обнулении счетчика
{
if ( solnd_cbl[n].pattern_start_ptr != 0 )
{
voltage = *solnd_cbl[n].pttn_ptr;
solnd_cbl[n].pttn_ptr++;
duration = *solnd_cbl[n].pttn_ptr;
solnd_cbl[n].pttn_ptr++;
if ( duration != 0xFFFF )
{
if ( duration == 0 )
{
solnd_cbl[n].pttn_ptr = solnd_cbl[n].pattern_start_ptr;
voltage = *solnd_cbl[n].pttn_ptr;
solnd_cbl[n].pttn_ptr++;
solnd_cbl[n].counter = *solnd_cbl[n].pttn_ptr;
solnd_cbl[n].pttn_ptr++;
Set_solenoids_voltage(voltage, n);
}
else
{
solnd_cbl[n].counter = duration;
Set_solenoids_voltage(voltage, n);
}
}
else
{
// Обнуляем счетчик и таким образом выключаем обработку шаблона
Set_solenoids_voltage(voltage,n);
solnd_cbl[n].counter = 0;
}
}
else
{
Set_solenoids_voltage(0,n);
}
}
}
}

} // if ((prediv & 3)==0)
}
Golikov A.
Цитата
Непонятно - почему не нужны критические секции? Как без них если нужна атомарность доступа к объекту?

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

такие суперлупы неплохое и надежное решение, но все же это предыдущий шаг перед операционкой.






Цитата
Извините, но ваш пост воспринимается как малопонятный набор слов.

труднопонятный sm.gif

Цитата
А приведенный код попахивает рекурсией.

есть такое... может с именами функций что напутано...

WitFed приоткройте завесу, обрисуйте чуть подробнее задумку мастера?
psL
Цитата(AlexandrY @ Nov 28 2014, 10:04) *
Этот автомат применяется мною и с RTOS и без...

сложный пример. Можно проще:
CODE
enum fsm_state { IDLE, PROCESS, ERROR };

struct fsm {
enum fsm_state state;
// data...
};

static struct fsm myfsm = {
.state = IDLE,
// data startup init...
};

void fsm_init(struct fsm* fsm){
fsm->state = IDLE;
// data (re)init...
}

void fsm_proc(struct fsm* fsm){
switch(fsm->state){
case IDLE:
// do startup init ...
fsm->state = PROCESS;
break;
case PROCESS:
// do process ...
fsm->state = IDLE;
// or fsm->state = ERROR;
break;
default:
fsm->state = ERROR;
case ERROR:
// error handler
break;
}
}

Вообще без ОСи никакого контекста не будет, потому что в этом случае процесс(задача)разветвленная, но одна. В противном случае что такое контекст? Состояние регистров и стека при выполнении подпрограммы или прерывания?
AlexandrY
Цитата(psL @ Nov 28 2014, 10:12) *
сложный пример. Можно проще:


Забавно состояние ERROR по отношению к процессу управления светодиодом.
Что бы это могло значить? biggrin.gif
psL
Цитата(AlexandrY @ Nov 28 2014, 11:17) *
Забавно состояние ERROR по отношению к процессу управления светодиодом.
Что бы это могло значить? biggrin.gif

Враги украли плату индикации wink.gif
AlexandrY
Цитата(psL @ Nov 28 2014, 10:26) *
Враги украли плату индикации wink.gif


Ни как ОНО узнает об этом без обратной связи то?
ERROR лишнее состояние, да и IDLE тоже.
Golikov A.
fsm->state можно и в прерывании сменить на ERROR чтобы вся схема перешла в это состояние.
таких автоматов может быть много. Еще хорошо бы состояние RESET или INIT, из которого схема в IDLE переходит.
AlexandrY
Цитата(Golikov A. @ Nov 28 2014, 11:11) *
fsm->state можно и в прерывании сменить на ERROR чтобы вся схема перешла в это состояние.
таких автоматов может быть много. Еще хорошо бы состояние RESET или INIT, из которого схема в IDLE переходит.


Ага, еще нужны состояния ALARM, WARNING, FAULT, HARD FAULT, PANIC и т.д. wacko.gif
psL
Цитата(AlexandrY @ Nov 28 2014, 12:09) *
Ни как ОНО узнает об этом без обратной связи то?

откуда вообще взялся светодиод в энтом абстрактном примере?wink.gif Придираетесь, профессор?
Цитата(AlexandrY @ Nov 28 2014, 12:09) *
ERROR лишнее состояние, да и IDLE тоже.

да, даsm.gif назовем IDLE "константа 65535" и захардкодим, чтобы джедаи не догадались sm.gif
AlexandrY
Цитата(psL @ Nov 28 2014, 11:25) *
откуда вообще взялся светодиод в энтом абстрактном примере?wink.gif Придираетесь, профессор?


Т.е. нынче принято обсуждать что угодно, но только не то, что спросил TC?

Цитата(psL @ Nov 28 2014, 11:25) *
да, даsm.gif назовем IDLE "константа 65535" и захардкодим, чтобы джедаи не догадались sm.gif


Боюсь вы термину IDLE придумали некое свое семантическое значение. Если оно вам помогает , ну и хорошо.
Только автоматов разных может быть много в программе и не упасётесь разными вариациями: IDLE1, IDLE2, IDLE3 ...
psL
Цитата(AlexandrY @ Nov 28 2014, 12:57) *
Т.е. нынче принято обсуждать что угодно, но только не то, что спросил TC?

ну ТС хотелось "контекста". В общем случае "контекст" может выглядеть так:
Код
struct job {
    void* data;              // "контекст"
    void  (*proc)(void*);
};

struct job jobs[] = {
   {.data=..., .proc=... }, ...
};

void job_proc(struct job* job){
   job->proc(job->data);
}

void main(){
  for(i=0; i<sizeof(jobs)/sizeof(struct job);i++)
     job_proc(&jobs[i]);
}

В качестве data и *proc м.б. struct fsm и fsm_proc из предыдущего примера с небольшой доработкой.
Или принципиально затачивать каждый пример для светодиода?
jcxz
Цитата(Golikov A. @ Nov 28 2014, 13:49) *
потому что при предложенном подходе каждая функция изначально критическая секция, с атомарным доступом ко всем используемым переменным до окончания своей работы. Функции не прерываются в середине, а только в определенных местах, и они должны правильно отрабатывать смену параметров на лету.

Это верно только в одном частном случае - когда границы критической секции не выходят за пределы таких коммутируемых функций.
А если выходят - как быть?
Также тут предполагается, что моменты переключения задач всегда на границах таких функций. Это корпоративная многозадачность.
А если длительность таких функций очень (на порядки различается)?
А если нужно выполнять функции, реагирующие на реалтайм-события, критичные к времени реакции. И в то же время среди функций есть тяжёлые (длительные) вычислительные функции, к тому же если время их выполнения заранее не известно.
Как в этом случае быть? wink.gif
AlexandrY
Цитата(psL @ Nov 28 2014, 12:55) *
ну ТС хотелось "контекста". В общем случае "контекст" может выглядеть так:
Код
struct job {
    void* data;              // "контекст"
    void  (*proc)(void*);
};

struct job jobs[] = {
   {.data=..., .proc=... }, ...
};

void job_proc(struct job* job){
   job->proc(job->data);
}

void main(){
  for(i=0; i<sizeof(jobs)/sizeof(struct job);i++)
     job_proc(&jobs[i]);
}

В качестве data и *proc м.б. struct fsm и fsm_proc из предыдущего примера с небольшой доработкой.
Или принципиально затачивать каждый пример для светодиода?


А где здесь сохранение контекста?
Здесь изображен просто синхронный вызов функций с неким указателем неизвестно на что.
А если брать struct fsm как контекст задачи в применении к светодиоду, то это будет точно то же что я и продемонстрировал если вам дописать недостающий код, но будет более раздуто, так как состояниям будет выделяться дополнительная величина в управляющем шаблоне.
psL
Цитата(AlexandrY @ Nov 28 2014, 14:28) *
А где здесь сохранение контекста?

Указатель на "контекст" хранится в *data, не нужно ТС никаких downloadContext. Т.е.
Код
struct job jobs[] = {
   {.data=(void*)myfsm, .proc=fsm_proc }, ...
};

void fsm_proc(void* p){
   struct fsm* myfsm = (struct fsm*)p;
   ...
}

да, будет почти тоже самое. В этих примерах все абстрактно. Но, пмсм, так проще для понимания.

Насколько вообще понимаю суть вопроса, ТС просит обьяснить, как писать реентерабельные функции.
AlexandrY
Цитата(psL @ Nov 28 2014, 13:48) *
Указатель на "контекст" хранится в *data, не нужно ТС никаких downloadContext. Т.е.


Вопрос был как сохранить контекст, а не где его хранить!

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

Мой пример тоже основан на этом.
Но! Только для светодиодов, соленоидов и прочей односигнальной мелочи.

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



Golikov A.
Цитата
Это верно только в одном частном случае - когда границы критической секции не выходят за пределы таких коммутируемых функций.
А если выходят - как быть?

не могут выходить, функции так создаются

Цитата
Также тут предполагается, что моменты переключения задач всегда на границах таких функций. Это корпоративная многозадачность.
А если длительность таких функций очень (на порядки различается)?

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

Цитата
А если нужно выполнять функции, реагирующие на реалтайм-события, критичные к времени реакции. И в то же время среди функций есть тяжёлые (длительные) вычислительные функции, к тому же если время их выполнения заранее не известно.
Как в этом случае быть? wink.gif

ну собственно вы сейчас называете все недостатки такого подхода и почему появились операционки. Отсюда ответ на ваш вопрос использовать операционку!
Slash
Цитата(AlexandrY @ Nov 28 2014, 10:04) *
Этот автомат применяется мною и с RTOS и без.

Извините, но это жесть что влезаю из топика "С vs C++", но этот код можно сделать еще лучше.

Код
// Управляющая структура машины состояний управляемой шаблоном
typedef struct
{
  INT32U  init_state;
  INT32U  counter;
  INT32U *pattern_start_ptr;  // Указатель на массив констант int являющийся цепочкой состояний (шаблоном)
                              // Если значение в массиве = 0xFFFF, то процесс обработки завершается
                              // Если значение в массиве = 0x0000, то вернуть указатель на начало цепочки
  INT32U *pttn_ptr;           // Текущая позиция в цепочке состояний

} T_solnd_ptrn;

pattern_start_ptr и pttn_ptr нет ну никакого смысла делать указателями, только лишние разименовывания. По смыслу это индекс позиции. Дать им имена pos и startPos или index и startIndex.
Чистая вкусовщина - меня коробит от сочетания в именах больших букв и подчеркиваний ну и typedef struct туда-же. Выделять типы данных префиксом "T_" нет смысла - современный редактор цветом покажет, что данный символ - тип данных. Т.е. меняем на
Код
struct SolenoidPattern
{
  INT32U  init_state;
  INT32U  counter;
  INT32U startPos ;  // Указатель на массив констант int являющийся цепочкой состояний (шаблоном)
                              // Если значение в массиве = 0xFFFF, то процесс обработки завершается
                              // Если значение в массиве = 0x0000, то вернуть указатель на начало цепочки
  INT32U pos;           // Текущая позиция в цепочке состояний
};


Код
//  Шаблоны работы соленоидов
//  ----------------------------------------------------------------------
//  Шаблон состоит из массива пар слов.
//  Первое слово - значение напряжения (в процентах от максимального) на текущем интервале времени
//  Второе       - длительность интервала времени в мс
//  интервал равный 0     - означает возврат в начало шаблона
//  интервал равный 65535 - означает застывание состояния

const INT32U  SOLENOID1_ON[10] = { 100, 300, 25,  300, 100, 300, 25,  300, 25, 0xFFFF };
const INT32U  SOLENOID2_ON[10] = { 25,  300, 100, 300, 25,  300, 100, 300, 25, 0xFFFF };
const INT32U  SOLENOID_OFF[4] = { 0, 2000, 0, 0xFFFF };

Здорово бы комментарии поддержать кодом, введя тип данных
Код
struct Point
{
    uint32_t voltage;
    uint32_t time;
};
const Point SOLENOID1_ON[5] = { {100, 300}, {25,  300}, {100, 300}, {25,  300}, {25, 0xFFFF} };


Тогда такая комбинация
Код
    voltage = *solnd_cbl[n].pttn_ptr;
    solnd_cbl[n].pttn_ptr++;
    duration =  *solnd_cbl[n].pttn_ptr;
    solnd_cbl[n].pttn_ptr++;

сокращается до
Код
    voltage = solnd_cbl[n].at(pos).voltage;
    duration =  solnd_cbl[n].at(pos).time;
    solnd_cbl[n].pos++;


Тут мне не все понятно, вроде бы эта функция вроде конструктора SolenoidPattern. Не хватает определения функции Set_solenoids_voltage.
Код
/*-------------------------------------------------------------------------------------------------------------
  Инициализация машины состояний заданным шаблоном
  *pttn - указатель на начало шаблона
  n - индекс соленоида (0..1)
-------------------------------------------------------------------------------------------------------------*/
void Set_Solenoid_pattern(const INT32U *pttn, INT32U n)
{
  if ( pttn != 0 )
  {
    solnd_cbl[n].pattern_start_ptr = (INT32U *)pttn;
    solnd_cbl[n].pttn_ptr          = (INT32U *)pttn;
    Set_solenoids_voltage(*solnd_cbl[n].pttn_ptr, n);
    solnd_cbl[n].pttn_ptr++;
    solnd_cbl[n].counter     = *solnd_cbl[n].pttn_ptr;
    solnd_cbl[n].pttn_ptr++;
  }
}


Ну и может быть отказаться от конструкции
Код
for (int i = 0; i < n; ++i)
{}

в пользу
Код
for (const Point & point : pointsArray)
{}

тогда из цикла пропадает индекс, но так сделать не всегда можно, иногда он нужен.

Golikov A.
чего то я видать безнадежно устарел
Код
for (const Point & point : pointsArray)

вот это что за на?
jcxz
Цитата(Golikov A. @ Nov 28 2014, 20:07) *
К примеру: есть функция обработки большого массива, функция при каждом вызове обрабатывает 1 элемент и выходит с кодом 1, а когда обработает последний вернет 0. В основной программе функция вызывается пока не вернет 0.

А если таких элементов миллион и обработка - одна простая операция?
Будет чудовищный оверхед - 10% времени будет выполняться полезная работа и 90% - входы/выходы в функцию и разные проверки.
Вы конечно скажете - "в таком случае нужно не по 1-му, а по 100 элементов за раз обрабатывать".
Но тогда получается программа привязывается к среде выполнения и кроме решения задачи каждый раз нужно учитывать ещё кучу условий.
А если длительность обработки каждого элемента заранее неизвестна?
А если вызываете функции некоей сторонней библиотеки, где тоже время выполнения заранее неизвестно?

Цитата(Golikov A. @ Nov 28 2014, 20:07) *
ну собственно вы сейчас называете все недостатки такого подхода и почему появились операционки. Отсюда ответ на ваш вопрос использовать операционку!

Конечно. Всё уместно на своём месте. И для суперлупа есть свой узкий круг задач, которые для него оптимальны, и своеобразное построение алгоритма. Я тоже его частенько использую (совместно с ОС), для экономии памяти (под стек).
Но ТС пишет о таком подходе как о панацее для всего. Что в корне не верно. Традиционная вытесняющая ОС гораздо более универсальна.
А для суперцикла есть только своя ниша.

Цитата(Golikov A. @ Nov 28 2014, 21:04) *
чего то я видать безнадежно устарел
Код
for (const Point & point : pointsArray)

вот это что за на?

Вся эта си-плюс-плюсная объектно-инкапсулированная хрень хороша только для тех, кто не заглядывает в файлы листинга компилятора.
А если Вы задумываетесь об оптимальности не исходников (как здесь), а результирующего кода (скорости выполнения и размера), то выбирайте наиболее стандартные конструкции, типа for (int i = 0; i < n; ++i). Оптимизаторы компиляторов на них наиболее "натасканы" и код будет оптимальным.

Я, после опыта оптимизации по скорости DSP-кода, взял это себе за правило - если хочется чтобы код был наиболее оптимален после компилятора, конструкции в исходнном коде должны быть наиболее простыми.
На входе у меня был вот такой вот весь правильный С++ код, со всеми конструкторами/деструкторами и т.п. и при этом он безбожно тормозил и алгоритм не успевал обработать поток данных. После убиения всей этой плюсовой красоты и полного переписывания на простой си-код, скорость выполнения того-же самого алгоритма увеличилась в несколько сотен раз!
Потому что оптимизатор простой код сумел многократно распараллелить.
Golikov A.
вопрос в другом.
Разве в С++ есть for без ;; ?
for(int i=0) - так же нельзя или уже можно?
Slash
Сколько не читал битв "C vs C++", почему то противники С++ "заставляют" его использовать в самых "тяжелых" проявлениях - RTTI, виртуальные функции (хотя они не тяжелые).
С++ можно использовать только за более жесткий контроль типов. И все.
Потом понять, что ссылки - это удобно и код становится чище.
Потом начать применять приведение типов static_cast(безопасное) и reinterpret_cast(опасное, на усмотрение программиста) вместо С-ного приведения типа (uint32_t *)var. Представьте, как легко найти в коде места, где вы приводите типы рискованно и которые надо проверить в случае ошибки.
Потом понять, что конструктор - это удобно. И их можно использовать в структурах.
Пространства имен.
Все эти возможности несут нулевой оверхед и делают код удобнее и понятнее.

Цитата(Golikov A. @ Nov 28 2014, 18:04) *
чего то я видать безнадежно устарел
Код
for (const Point & point : pointsArray)

вот это что за на?

Это нововведение стандарта С++ за 2011 год - "range-based for".
Отлично описано в этой статье

Цитата(jcxz @ Nov 28 2014, 18:34) *
Вся эта си-плюс-плюсная объектно-инкапсулированная хрень хороша только для тех, кто не заглядывает в файлы листинга компилятора.
А если Вы задумываетесь об оптимальности не исходников (как здесь), а результирующего кода (скорости выполнения и размера), то выбирайте наиболее стандартные конструкции, типа for (int i = 0; i < n; ++i). Оптимизаторы компиляторов на них наиболее "натасканы" и код будет оптимальным.

А не нужно туда заглядывать.
Задач, где нужна оптимальность кода, не так много. Чаще пишется обычный, не критичный ко времени исполнения код, развесистая логика. Тут важнее читабельность исходников. И не только для автора.
Разговоры "я пишу один и мне все понятно" в пользу бедных.

Цитата(jcxz @ Nov 28 2014, 18:34) *
Я, после опыта оптимизации по скорости DSP-кода, взял это себе за правило - если хочется чтобы код был наиболее оптимален после компилятора, конструкции в исходнном коде должны быть наиболее простыми.
На входе у меня был вот такой вот весь правильный С++ код, со всеми конструкторами/деструкторами и т.п. и при этом он безбожно тормозил и алгоритм не успевал обработать поток данных. После убиения всей этой плюсовой красоты и полного переписывания на простой си-код, скорость выполнения того-же самого алгоритма увеличилась в несколько сотен раз!
Потому что оптимизатор простой код сумел многократно распараллелить.

Очень хотелось бы поверить. Но слишком много неизвестных - какой был компилятор (сейчас компиляторы совершенствуются), каков Ваш уровень мастерства как С++ программиста, каков уровень как С программиста, в каком конкретно месте была потеря скорости?

Вот пример, который привел Александр, хороший с точки зрения проверки С vs C++, т.к. хорошо переводится на С++.
Golikov A.
Цитата
Стоит отметить, что хоть range-based for и является мощным и удобным инструментом, он, как и все остальное в C++, не работает на Святом Духе, как могут считать некоторые представители педагогического состава нашей страны. range-based for неявно вызывает у контейнера методы begin() и end(), которые возвращают, в свою очередь, привычные нам итераторы.


неплохо... то есть вместо for(int i=0;i<n;i++) зафигачили трудночитаемую конструкцию, которая при этом еще работает через одно место множа вызовы.

Есть интересные нововведения 11 стандарта, но часть из них для борьбы с разросшимися шаблонами, и их не стоит применять для разработки железа. А именно этот пример читабельность не улучшает, и скорость не увеличивает... так что это ради понтов не более....


однако грустный момент, как соответствовать то? Стандарты выходят быстрее продуктов...

Цитата
Углубляясь в историю, стоит отметить, что auto переменные были и раньше, до принятия стандарта C++11. Вот только значение они имели другое. Под auto подразумевался спецификатор хранения переменной. То есть, auto находился в одном ряду с register, static, extern, и указывал на то, что переменная имеет локальное время жизни. Об этом почти не знают начинающие, так как любая переменная объявленная в некотором блоке неявно определяется как auto.


прикольно, я начинающий%).... я про авто не сном не духом, что-то в моих книжках про них авторы не упомянулиsad.gif

Цитата
А не нужно туда заглядывать.
Задач, где нужна оптимальность кода, не так много. Чаще пишется обычный, не критичный ко времени исполнения код, развесистая логика. Тут важнее читабельность исходников. И не только для автора.
Разговоры "я пишу один и мне все понятно" в пользу бедных.


верно для компьютеров не верно для железа, все таки пока еще такты считаем, хочется чтобы схема и реагировала побыстрее и дел делала побольше.
Про читабельность, я пишу на С# и там часто использую foreach, но в данном конкретном случае этот аналог - for читабельность не увеличил, по мне даже уменьшил ссылками и прочим...
jcxz
Цитата(Slash @ Nov 28 2014, 22:07) *
Очень хотелось бы поверить. Но слишком много неизвестных - какой был компилятор (сейчас компиляторы совершенствуются), каков Ваш уровень мастерства как С++ программиста, каков уровень как С программиста, в каком конкретно месте была потеря скорости?

Компилятор - CCS, cgtools - довольно свежие. И дело не в компиляторе.
Плюсовой код порождал множественные вызовы функций, что страшное зло для DSP-циклов, плюс - как я понимаю из-за сложности для оптимизатора, он не мог правильно рассчитать кол-во проходов циклов, распараллелить обработку так как не мог просчитать зависимости переменных друг от друга и т.п.
Потеря была во всех критичных местах - фильтрах, обработках массивов сэмплов и т.д.. Остальные места меня не интересовали.

Цитата(Slash @ Nov 28 2014, 22:07) *
Задач, где нужна оптимальность кода, не так много. Чаще пишется обычный, не критичный ко времени исполнения код, развесистая логика. Тут важнее читабельность исходников. И не только для автора.

Мы вообще-то находимся в области embedded, где и размер кода и скорость его выполнения всегда будут важны, так как при улучшении этих показателей, позволяют впихнуть приложение в менее мощный, а значит - более дешёвый МК.
psL
Цитата(AlexandrY @ Nov 28 2014, 14:58) *
Вопрос был как сохранить контекст, а не где его хранить!
Для более сложных задач нужно применять минимум кооперативную многозадачность.

Разве как и где не взаимосвязаны? Очевидно, что данные сохраняются по указателю в структуре данных задачи. И это разжевывать не нужно.
Для "минимум коопертивной многозадачности" задача как раз таки вырождается в машину состояния. Для примера можно, например, посмотреть выход с препроцессора для protothreads


Цитата(Slash @ Nov 28 2014, 17:38) *
Извините, но это жесть что влезаю из топика "С vs C++", но этот код можно сделать еще лучше.

Непонятен смысл рефакторинга указателей. Мало того, что массив превращается в контейнер, так еще указатели превращаются в объекты с перегруженным operator []. Ради чего это сделано? Кто и кому будет кидать исключение для .at() на микроконтроллере? Или все это ради for на наборе? Как-то избыточно.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.