|
Управление контекстом БЕЗ RTOS |
|
|
|
Nov 21 2014, 08:04
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Я понимаю, что сейчас речь пойдет об изобретении велосипеда. Но мне это важно для понимания. В ОСРВах переключение между задачами осуществляется за счет переключения контекста. Мне интересно каким образом можно организовать такое переключения самостоятельно. Например, есть две функции 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); } } Может я чего неправильно сказал, но надеюсь смысл понятен. Вопрос: как сделать такое сохранение и загрузку контекста? Где про это можно почитать, посмотреть пример и т.п. Спасибо!
|
|
|
|
|
 |
Ответов
(1 - 53)
|
Nov 21 2014, 08:12
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Цитата(1113 @ Nov 21 2014, 13:09)  ваш main() это и есть диспетчер задач. таким образом ваше предположение "А если я озадачусь сделать тоже моргание без rtos" уже не верно. то что вы придумали - и есть RTOS. Ну пусть так, я же говорю - изобретение велосипеда. Мне интересно как это сделать - сохранить контекст функции, а потом восстановить, когда потребуется. В некоторых языка программирования это называется Coroutines кажется. Функция запоминающее свое предыдущее состояние.
|
|
|
|
|
Nov 21 2014, 08:41
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Цитата(scifi @ Nov 21 2014, 13:25)  Всё придумано до нас: protothreads. Извиняюсь за выражение, но просто Офигеть!  Это конечно не сохранение/загрузка контекста в том виде, в котором я думал, но тоже решение. Цитата(demiurg_spb @ Nov 21 2014, 13:36)  Этой штукой вообще безопасно пользоваться?
|
|
|
|
|
Nov 21 2014, 08:51
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Цитата(scifi @ Nov 21 2014, 13:42)  Хорошее решение. Широко применяется, позволяет получать отличные результаты. Названия макросов оставляют желать лучшего: заглавные буквы пестрят, длинные имена растягивают код вправо. А в остальном всё хорошо. РТОСина на 8 килобайт. Ох, сколько же килограмм кода можно было сэкономить с этой штукой, где Вы были раньше ))
|
|
|
|
|
Nov 21 2014, 08:59
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Цитата(1113 @ Nov 21 2014, 13:53)  для такого типа "многозадачности" код в своих процессах должен быть прерываемым, типа state-машины. Ну да, я это понимаю. Просто я получилось так, что я спрашивал, как работать со контекстом функции, чтобы строить state-машины. А посоветовали готовое средство для них.  Правда я по-прежнему озадачен вопросом работы с контекстом для целей самообразования.
|
|
|
|
|
Nov 21 2014, 10:08
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(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 по поводу того какие регистры сохранять, а какие не надо сохранять.
|
|
|
|
|
Nov 21 2014, 12:21
|
Профессионал
    
Группа: Свой
Сообщений: 1 719
Регистрация: 13-09-05
Из: Novosibirsk
Пользователь №: 8 528

|
Цитата(scifi @ Nov 21 2014, 15:25)  Всё придумано до нас: protothreads. Кстати, о protothreads. С использованием возможностей С++ становится ещё удобнее. http://blog.brush.co.nz/2008/07/protothreads/
--------------------
Russia est omnis divisa in partes octo.
|
|
|
|
|
Nov 21 2014, 13:06
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(MrYuran @ Nov 21 2014, 14:46)  А если немного подумать, может и не нужна никакая многозадачность и вполне достаточно event-driven state machine В случае с двумя светодиодами - однозначно Я к qp давно приглядываюсь, а он за это время становится все лучше и лучше Для state machine лучше IAR visualSTATE трудно что либо найти. Неутомимая Xenia у нас тут с этим помогает.
|
|
|
|
|
Nov 21 2014, 15:46
|
Гуру
     
Группа: Свой
Сообщений: 3 020
Регистрация: 7-02-07
Пользователь №: 25 136

|
Цитата(MrYuran @ Nov 21 2014, 15:46)  А если немного подумать, может и не нужна никакая многозадачность и вполне достаточно event-driven state machine В случае с двумя светодиодами - однозначно Как сказать. Если программа описывает процесс, линейно текущий во времени, с ветвлениями и переходами, то она естественным образом ложится на сишный код, и мне такое представление кажется интуитивно более понятным. И protothreads тут весьма кстати. Те же две лампочки вписываются в эту схему. Да, всё можно свести к конечному автомату, но не всегда стоит это делать, так как читаемость кода может страдать.
|
|
|
|
|
Nov 27 2014, 14:04
|
Местный
  
Группа: Свой
Сообщений: 271
Регистрация: 6-12-11
Из: Taganrog
Пользователь №: 68 701

|
Я лично после долгого знакомства с ОС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(); } Вдруг на самом деле что такое уже есть в мировой практике -- подскажите.
|
|
|
|
|
Nov 28 2014, 04:02
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(WitFed @ Nov 27 2014, 20:04)  Также можно завести отдельный массив "ожидателей" -- системного таймера или не 0 по какому-то адресу, который будет и "семафором", "мьютексом", и чем угодно -- не нужны никакие критические секции, никто у тебя за спиной бяку никогда не сделает в непонятной нити с высоким приоритетом, лишь бы был отладчик хоть какой-то в IDE. Непонятно - почему не нужны критические секции? Как без них если нужна атомарность доступа к объекту? Случай (работа под ОС): семафор проверяется внутри критической секции. При его занятости, ОС переводит таск в сост. "ожидает данный семафор" и переключает контекст. А как Вы своим велосипедом такой случай разрулите?  А если внутри двух критических секций? А как реализуете функцию типа WaitForMultipleObjects? Все плюсы и минусы построения диспетчера с переключением контекста (ОС) и без (все вариации суперцикла) думаю давно известны. Суперцикл хорош, только если не нужен блокирующий доступ к ресурсам (защищённым семафорами и мьютексами).
|
|
|
|
|
Nov 28 2014, 07:04
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(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) }
Сообщение отредактировал IgorKossak - Nov 28 2014, 15:58
Причина редактирования: [codebox] для длинного кода, [code] - для короткого!!!
|
|
|
|
|
Nov 28 2014, 07:49
|
Гуру
     
Группа: Свой
Сообщений: 4 256
Регистрация: 17-02-06
Пользователь №: 14 454

|
Цитата Непонятно - почему не нужны критические секции? Как без них если нужна атомарность доступа к объекту? потому что при предложенном подходе каждая функция изначально критическая секция, с атомарным доступом ко всем используемым переменным до окончания своей работы. Функции не прерываются в середине, а только в определенных местах, и они должны правильно отрабатывать смену параметров на лету. такие суперлупы неплохое и надежное решение, но все же это предыдущий шаг перед операционкой. Цитата Извините, но ваш пост воспринимается как малопонятный набор слов. труднопонятный Цитата А приведенный код попахивает рекурсией. есть такое... может с именами функций что напутано... WitFed приоткройте завесу, обрисуйте чуть подробнее задумку мастера?
|
|
|
|
|
Nov 28 2014, 08:12
|
Знающий
   
Группа: Свой
Сообщений: 526
Регистрация: 5-08-05
Пользователь №: 7 390

|
Цитата(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; } } Вообще без ОСи никакого контекста не будет, потому что в этом случае процесс(задача)разветвленная, но одна. В противном случае что такое контекст? Состояние регистров и стека при выполнении подпрограммы или прерывания?
Сообщение отредактировал IgorKossak - Nov 28 2014, 15:59
Причина редактирования: [codebox] для длинного кода, [code] - для короткого!!!
|
|
|
|
|
Nov 28 2014, 09:25
|
Знающий
   
Группа: Свой
Сообщений: 526
Регистрация: 5-08-05
Пользователь №: 7 390

|
Цитата(AlexandrY @ Nov 28 2014, 12:09)  Ни как ОНО узнает об этом без обратной связи то? откуда вообще взялся светодиод в энтом абстрактном примере?  Придираетесь, профессор? Цитата(AlexandrY @ Nov 28 2014, 12:09)  ERROR лишнее состояние, да и IDLE тоже. да, да  назовем IDLE "константа 65535" и захардкодим, чтобы джедаи не догадались
|
|
|
|
|
Nov 28 2014, 09:57
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(psL @ Nov 28 2014, 11:25)  откуда вообще взялся светодиод в энтом абстрактном примере?  Придираетесь, профессор? Т.е. нынче принято обсуждать что угодно, но только не то, что спросил TC? Цитата(psL @ Nov 28 2014, 11:25)  да, да  назовем IDLE "константа 65535" и захардкодим, чтобы джедаи не догадались  Боюсь вы термину IDLE придумали некое свое семантическое значение. Если оно вам помогает , ну и хорошо. Только автоматов разных может быть много в программе и не упасётесь разными вариациями: IDLE1, IDLE2, IDLE3 ...
|
|
|
|
|
Nov 28 2014, 10:55
|
Знающий
   
Группа: Свой
Сообщений: 526
Регистрация: 5-08-05
Пользователь №: 7 390

|
Цитата(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 из предыдущего примера с небольшой доработкой. Или принципиально затачивать каждый пример для светодиода?
|
|
|
|
|
Nov 28 2014, 11:06
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Golikov A. @ Nov 28 2014, 13:49)  потому что при предложенном подходе каждая функция изначально критическая секция, с атомарным доступом ко всем используемым переменным до окончания своей работы. Функции не прерываются в середине, а только в определенных местах, и они должны правильно отрабатывать смену параметров на лету. Это верно только в одном частном случае - когда границы критической секции не выходят за пределы таких коммутируемых функций. А если выходят - как быть? Также тут предполагается, что моменты переключения задач всегда на границах таких функций. Это корпоративная многозадачность. А если длительность таких функций очень (на порядки различается)? А если нужно выполнять функции, реагирующие на реалтайм-события, критичные к времени реакции. И в то же время среди функций есть тяжёлые (длительные) вычислительные функции, к тому же если время их выполнения заранее не известно. Как в этом случае быть?
|
|
|
|
|
Nov 28 2014, 11:28
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(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 как контекст задачи в применении к светодиоду, то это будет точно то же что я и продемонстрировал если вам дописать недостающий код, но будет более раздуто, так как состояниям будет выделяться дополнительная величина в управляющем шаблоне.
|
|
|
|
|
Nov 28 2014, 11:48
|
Знающий
   
Группа: Свой
Сообщений: 526
Регистрация: 5-08-05
Пользователь №: 7 390

|
Цитата(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; ... } да, будет почти тоже самое. В этих примерах все абстрактно. Но, пмсм, так проще для понимания. Насколько вообще понимаю суть вопроса, ТС просит обьяснить, как писать реентерабельные функции.
|
|
|
|
|
Nov 28 2014, 11:58
|

Ally
     
Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050

|
Цитата(psL @ Nov 28 2014, 13:48)  Указатель на "контекст" хранится в *data, не нужно ТС никаких downloadContext. Т.е. Вопрос был как сохранить контекст, а не где его хранить! И ваш ответ по сути таков: превращать любую задачу в автомат состояний и состояние хранить в структуре, которую не надо сохранять потому как она у каждой задачи своя. Мой пример тоже основан на этом. Но! Только для светодиодов, соленоидов и прочей односигнальной мелочи. Для более сложных задач нужно применять минимум кооперативную многозадачность.
|
|
|
|
|
Nov 28 2014, 14:07
|
Гуру
     
Группа: Свой
Сообщений: 4 256
Регистрация: 17-02-06
Пользователь №: 14 454

|
Цитата Это верно только в одном частном случае - когда границы критической секции не выходят за пределы таких коммутируемых функций. А если выходят - как быть? не могут выходить, функции так создаются Цитата Также тут предполагается, что моменты переключения задач всегда на границах таких функций. Это корпоративная многозадачность. А если длительность таких функций очень (на порядки различается)? опять же функции создаются так чтобы такого не было и переключение всегда на границе функций. К примеру: есть функция обработки большого массива, функция при каждом вызове обрабатывает 1 элемент и выходит с кодом 1, а когда обработает последний вернет 0. В основной программе функция вызывается пока не вернет 0. Цитата А если нужно выполнять функции, реагирующие на реалтайм-события, критичные к времени реакции. И в то же время среди функций есть тяжёлые (длительные) вычислительные функции, к тому же если время их выполнения заранее не известно. Как в этом случае быть? wink.gif ну собственно вы сейчас называете все недостатки такого подхода и почему появились операционки. Отсюда ответ на ваш вопрос использовать операционку!
|
|
|
|
|
Nov 28 2014, 14:38
|
Местный
  
Группа: Участник
Сообщений: 202
Регистрация: 10-04-05
Из: Санкт-Петербург
Пользователь №: 4 011

|
Цитата(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) {} тогда из цикла пропадает индекс, но так сделать не всегда можно, иногда он нужен.
|
|
|
|
|
Nov 28 2014, 15:34
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(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-кода, взял это себе за правило - если хочется чтобы код был наиболее оптимален после компилятора, конструкции в исходнном коде должны быть наиболее простыми. На входе у меня был вот такой вот весь правильный С++ код, со всеми конструкторами/деструкторами и т.п. и при этом он безбожно тормозил и алгоритм не успевал обработать поток данных. После убиения всей этой плюсовой красоты и полного переписывания на простой си-код, скорость выполнения того-же самого алгоритма увеличилась в несколько сотен раз! Потому что оптимизатор простой код сумел многократно распараллелить.
|
|
|
|
|
Nov 28 2014, 16:07
|
Местный
  
Группа: Участник
Сообщений: 202
Регистрация: 10-04-05
Из: Санкт-Петербург
Пользователь №: 4 011

|
Сколько не читал битв "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++, т.к. хорошо переводится на С++.
Сообщение отредактировал Slash - Nov 28 2014, 16:31
|
|
|
|
|
Nov 28 2014, 16:15
|
Гуру
     
Группа: Свой
Сообщений: 4 256
Регистрация: 17-02-06
Пользователь №: 14 454

|
Цитата Стоит отметить, что хоть 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. прикольно, я начинающий%).... я про авто не сном не духом, что-то в моих книжках про них авторы не упомянули  Цитата А не нужно туда заглядывать. Задач, где нужна оптимальность кода, не так много. Чаще пишется обычный, не критичный ко времени исполнения код, развесистая логика. Тут важнее читабельность исходников. И не только для автора. Разговоры "я пишу один и мне все понятно" в пользу бедных. верно для компьютеров не верно для железа, все таки пока еще такты считаем, хочется чтобы схема и реагировала побыстрее и дел делала побольше. Про читабельность, я пишу на С# и там часто использую foreach, но в данном конкретном случае этот аналог - for читабельность не увеличил, по мне даже уменьшил ссылками и прочим...
|
|
|
|
|
Nov 28 2014, 16:26
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Slash @ Nov 28 2014, 22:07)  Очень хотелось бы поверить. Но слишком много неизвестных - какой был компилятор (сейчас компиляторы совершенствуются), каков Ваш уровень мастерства как С++ программиста, каков уровень как С программиста, в каком конкретно месте была потеря скорости? Компилятор - CCS, cgtools - довольно свежие. И дело не в компиляторе. Плюсовой код порождал множественные вызовы функций, что страшное зло для DSP-циклов, плюс - как я понимаю из-за сложности для оптимизатора, он не мог правильно рассчитать кол-во проходов циклов, распараллелить обработку так как не мог просчитать зависимости переменных друг от друга и т.п. Потеря была во всех критичных местах - фильтрах, обработках массивов сэмплов и т.д.. Остальные места меня не интересовали. Цитата(Slash @ Nov 28 2014, 22:07)  Задач, где нужна оптимальность кода, не так много. Чаще пишется обычный, не критичный ко времени исполнения код, развесистая логика. Тут важнее читабельность исходников. И не только для автора. Мы вообще-то находимся в области embedded, где и размер кода и скорость его выполнения всегда будут важны, так как при улучшении этих показателей, позволяют впихнуть приложение в менее мощный, а значит - более дешёвый МК.
|
|
|
|
|
Nov 28 2014, 22:41
|
Знающий
   
Группа: Свой
Сообщений: 526
Регистрация: 5-08-05
Пользователь №: 7 390

|
Цитата(AlexandrY @ Nov 28 2014, 14:58)  Вопрос был как сохранить контекст, а не где его хранить! Для более сложных задач нужно применять минимум кооперативную многозадачность. Разве как и где не взаимосвязаны? Очевидно, что данные сохраняются по указателю в структуре данных задачи. И это разжевывать не нужно. Для "минимум коопертивной многозадачности" задача как раз таки вырождается в машину состояния. Для примера можно, например, посмотреть выход с препроцессора для protothreads Цитата(Slash @ Nov 28 2014, 17:38)  Извините, но это жесть что влезаю из топика "С vs C++", но этот код можно сделать еще лучше. Непонятен смысл рефакторинга указателей. Мало того, что массив превращается в контейнер, так еще указатели превращаются в объекты с перегруженным operator []. Ради чего это сделано? Кто и кому будет кидать исключение для .at() на микроконтроллере? Или все это ради for на наборе? Как-то избыточно.
|
|
|
|
|
Dec 1 2014, 07:29
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Чтобы внести конкретики. Зачем мне это надо и почему не RTOS: нужно написать библиотеку, которую можно будет помещать, в тело задачи, а можно просто в суперлупе. Не заталкивать же в библиотеку, RTOSину, если есть необходимость в многопоточности. Классическое решение, конечно, это автоматы состояний. Но по ходу обсуждений, выяснилось, что вариаций на эту тему, причем очень интересных в том числе, очень много. Я думал, и еще не совсем отказался от этой затеи - разбираюсь, попробовать отгрузку контекста таким же механизмом как в RTOS, но тогда возникают трудности с кроссплатформенностью. Но при этом все предложения в области автоматов мне тоже интересны, спасибо всем кто делится своим опытом на эту тему.
|
|
|
|
|
Dec 1 2014, 08:12
|
Местный
  
Группа: Свой
Сообщений: 321
Регистрация: 23-12-11
Из: Уфа
Пользователь №: 69 041

|
Цитата(scifi @ Dec 1 2014, 12:55)  Кстати, в lwip решена ровно эта задача. Всё может работать без оси. Ось нужна только для sockets API, и то только потому, что это самое sockets API предполагает наличие оси. На автоматах?
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|