DASM 0 December 7, 2017 Posted December 7, 2017 · Report post Я потихоньку начинаю проникаться сабжем, начну на днях проектик. Правда думаю допилить до 32 уровней приоритета. И чего то неуверен, что мне вытеснение вообще нужно, скорее только мешать будет Quote Share this post Link to post Share on other sites More sharing options...
DASM 0 December 9, 2017 Posted December 9, 2017 · Report post brag, а Вы дин. памятью пользуетесь? Выкинув эти топорные ждущие оси в связи с появлением памяти в предскауемых объемах задумался, а так ли страшна фрагментация как ее малюют? Жутко надоело на каждый чих типа контйенера и чего нить стандартного из boost / stl придумывать свои костыли глючные. 21 век на дворе, компилер для Silabs мой даж лямбды понимает и int qq = 0xb10111; (мечта железячника) :) Quote Share this post Link to post Share on other sites More sharing options...
brag 0 November 9, 2021 Posted November 9, 2021 · Report post On 12/7/2017 at 10:22 PM, DASM said: Я потихоньку начинаю проникаться сабжем, начну на днях проектик. Правда думаю допилить до 32 уровней приоритета. И чего то неуверен, что мне вытеснение вообще нужно, скорее только мешать будет Приоритетов app-level на самом деле много не нужно. В реальных проектах их не редко всего 1-2 )) IRQ-Приоритетов конечно больше, но этим занимается готовая аппаратная реализация, например NVIC. On 12/9/2017 at 7:06 AM, DASM said: brag, а Вы дин. памятью пользуетесь? Выкинув эти топорные ждущие оси в связи с появлением памяти в предскауемых объемах задумался, а так ли страшна фрагментация как ее малюют? Жутко надоело на каждый чих типа контйенера и чего нить стандартного из boost / stl придумывать свои костыли глючные. 21 век на дворе, компилер для Silabs мой даж лямбды понимает и int qq = 0xb10111; (мечта железячника) :) Конечно! Только дин память у меня своя (мой велик:))) Сцепления/дробления блоков нет, а значит нет и фрагментации, а главное - выделение\освобождение памяти выполняется за O(1). Подход segregated storage(segregated lists). Блоки выделяются любого размера кратно 8(или любого удобного, для моих задач 8-16 - оптимум) от 8 до 8*N в конце пула. Освобождения, как такового нет, но освобожденные( блоки хранятся в списке. Для каждого размера список свой, итого N списков(обычно хватает 32 шт). Накладные расходы массив указателей на начало списка - short head[N], все. И это работает, потому что в embedded если однажды был выделен блок размером, скажем 12 байт, значит скорее всего этот или такой же блок понадобится еще(много раз). On 12/7/2017 at 10:22 PM, DASM said: Я потихоньку начинаю проникаться сабжем, начну на днях проектик. Правда думаю допилить до 32 уровней приоритета. И чего то неуверен, что мне вытеснение вообще нужно, скорее только мешать будет Приоритетов app-level на самом деле много не нужно. В реальных проектах их не редко всего 1-2 )) IRQ-Приоритетов конечно больше, но этим занимается готовая аппаратная реализация, например NVIC. On 12/9/2017 at 7:06 AM, DASM said: brag, а Вы дин. памятью пользуетесь? Выкинув эти топорные ждущие оси в связи с появлением памяти в предскауемых объемах задумался, а так ли страшна фрагментация как ее малюют? Жутко надоело на каждый чих типа контйенера и чего нить стандартного из boost / stl придумывать свои костыли глючные. 21 век на дворе, компилер для Silabs мой даж лямбды понимает и int qq = 0xb10111; (мечта железячника) :) Конечно! Только дин память у меня своя (мой велик на 80 строк pure C кода :))) Сцепления/дробления блоков нет, а значит нет и фрагментации, а главное - выделение\освобождение памяти выполняется за O(1). Подход segregated storage(segregated lists). Блоки выделяются любого размера кратно 8(или любого удобного, для моих задач 8-16 - оптимум) от 8 до 8*N в конце пула. Освобождения, как такового нет, но освобожденные(freed) блоки хранятся в списке. Для каждого размера блока список свой, итого N списков(обычно хватает 16-32 шт). Накладные расходы - массив указателей на начало списка - short head[N]; и указатель на конец пула short pool_end;, все. И это работает, потому что в embedded если однажды был выделен блок размером, скажем 12 байт, значит скорее всего этот или такой же блок понадобится еще(много раз). enum { Size = MEMPOOL_SIZE, FreeLists = 16, ObjSizeStep = 8, MaxObjSize = FreeLists*ObjSizeStep }; struct MemPool { int16_t pool[Size]; // indexes of 8, 16, 24, 32..._size free lists int16_t idx_free_lists[FreeLists]; // index of the end of used pool int16_t idx_end; }; Quote Share this post Link to post Share on other sites More sharing options...
Aleksandr Baranov 1 November 9, 2021 Posted November 9, 2021 · Report post Off: Это какой-то спиритизм. Quote Share this post Link to post Share on other sites More sharing options...
brag 0 November 10, 2021 Posted November 10, 2021 (edited) · Report post Но с динамической памятью нужно аккуратно. Ее можно использовать только, если уверены, что пула хватит под всевозможные стечения обстоятельств. Просчитать это не всегда просто. Допустим есть асинхронная функция init, которая использует асинхронные функции read_register и write_register. write_register же использует read_register, и обе используют i2c_read и i2c_write. Довольно типичная ситуация. Все функции, ессно, асинхронные, 21й век же :)) Pure C... Spoiler struct Callback { void(*fn)(void*); void *that; }; struct Init { struct Callback cbk; }; void init(struct Callback cbk) { struct Init *inst = //??? inst->cbk = cbk; read_register((struct Callback){ &init__on_read_register_completed, inst }); } static void init__on_read_register_completed(void *that) { write_register((struct Callback){ &init__on_write_register_completed, that }); } static void init__on_write_register_completed(void *that) { struct Init *inst = that; inst->cbk.fn(inst->cbk.that); } Все классно, только где хранить данные(например struct Init), которые нужны для работы асинхронной функции(например init), иными словами state? В языках, типа Javascritpt они хранятся в динамической памяти. Первое, что приходит на ум - сделать так же. Главное, не забыть освободить память - сборки мусора, как у Javascript у нас нет. Spoiler void init(struct Callback cbk) { struct Init *inst; inst = malloc(sizeof(*inst)); inst->cbk = cbk; read_register((struct Callback){ &init__on_read_register_completed, inst }); } //.... static void init__on_write_register_completed(void *that) { struct Init *inst = that; const struct Callback cbk = inst->cbk; free(inst); cbk.fn(cbk.that); } Остальные функции будут выглядеть аналогично: Spoiler struct ReadRegister { struct Callback cbk; }; static void read_register(struct Callback cbk) { struct ReadRegister *inst; inst = malloc(sizeof(*inst)); inst->cbk = cbk; i2c_write((struct Callback){ &read_register__on_i2c_write_completed, inst }); } static void read_register__on_i2c_write_completed(void *that) { struct ReadRegister *inst = that; i2c_read((struct Callback){ &read_register__on_i2c_read_completed, inst }); } static void read_register__on_i2c_read_completed(void *that) { struct ReadRegister *inst = that; const struct Callback cbk = inst->cbk; free(inst); cbk.fn(cbk.that); } struct WriteRegister { struct Callback cbk; }; static void write_register(struct Callback cbk) { struct WriteRegister *inst; inst = malloc(sizeof(*inst)); inst->cbk = cbk; read_register((struct Callback){ &write_register__on_read_register_completed, inst }); } static void write_register__on_read_register_completed(void *that) { struct WriteRegister *inst = that; i2c_write((struct Callback){ &write_register__on_i2c_write_completed, inst }); } static void write_register__on_i2c_write_completed(void *that) { struct WriteRegister *inst = that; const struct Callback cbk = inst->cbk; free(inst); cbk.fn(cbk.that); } Вроде все ок, динамическая память у нас O(1), операции выделения\освобождения вкладываются в 5-10 CPU-инструкций, но! Не забыть освободить память, boilerplate-код - это еще 1/2 беды. А вот посчитать, сколько нам понадобится памяти, хватит ли(а может не хватит?) - вот это беда! Поэтому я предлагаю другое, более гибкое, автоматизированное решение - статическую память :)) Идея - передавать асинхронной функции указатель на память, где функция сможет хранить свой state. В C++ это может быть просто указатель this. Память для вложенных функций пихаем в родительские. Spoiler struct ReadRegister { struct Callback cbk; struct I2C_Read i2c_read; struct I2C_Write i2c_write; }; struct WriteRegister { struct Callback cbk; struct ReadRegister read_register; struct I2C_Write i2c_write; }; struct Init { struct Callback cbk; struct ReadRegister read_register; struct WriteRegister write_register; }; void init(struct Init *inst, struct Callback cbk) { inst->cbk = cbk; read_register(&inst->read_register, (struct Callback){ &init__on_read_register_completed, inst }); } static void init__on_read_register_completed(void *that) { struct Init *inst = that; // поскольку необходимости освобождать память нет, init__on_write_register_completed больше не нужна write_register(&inst->write_register, inst->cbk); } static void read_register(struct ReadRegister *inst, struct Callback cbk) { inst->cbk = cbk; i2c_write(&inst->i2c_write, (struct Callback){ &read_register__on_i2c_write_completed, inst }); } static void read_register__on_i2c_write_completed(void *that) { struct ReadRegister *inst = that; i2c_read(&inst->i2c_read, inst->cbk); } static void write_register(struct WriteRegister *inst, struct Callback cbk) { inst->cbk = cbk; read_register(&inst->read_register, (struct Callback){ &write_register__on_read_register_completed, inst }); } static void write_register__on_read_register_completed(void *that) { struct WriteRegister *inst = that; i2c_write(&inst->i2c_write, inst->cbk); } Видно на сколько такое решение короче и практически лишено boilerplate кода, а главное - компилятор теперь сам считает необходимый объем памяти! Тут можно оптимизировать память, если мы точно знаем, что наши асинхронные функции не запускают другие (конечно же асинхронные:) функции одновременно. В данном, и в подавляющем большинстве случаев так и есть. Можно пихнуть "вложенный state" в union, сэкономив (на практике довольно много) памяти: Spoiler struct ReadRegister { struct Callback cbk; union { struct I2C_Read i2c_read; struct I2C_Write i2c_write; }; }; struct WriteRegister { struct Callback cbk; union { struct ReadRegister read_register; struct I2C_Write i2c_write; }; }; struct Init { struct Callback cbk; union { struct ReadRegister read_register; struct WriteRegister write_register; }; }; Короче, смысл, думаю понятный - если можем прогнать указатель на "объект" - используем статику и смело гоним, код будет проще и короче. Если не можем или муторно(бывают такие случаи, например асинхронный Observer Pattern) - тогда ничего не остается, как использовать динамику. Edited November 10, 2021 by brag убрал длинный код под спойлеры Quote Share this post Link to post Share on other sites More sharing options...
haker_fox 25 November 10, 2021 Posted November 10, 2021 · Report post 9 minutes ago, brag said: Pure C... Off: убирайте длинный код под спойлеры) Это такой глазик на панеле редактирования поста. Quote Share this post Link to post Share on other sites More sharing options...
ig_z 0 November 11, 2021 Posted November 11, 2021 · Report post brag, спасибо за ответы, жаль что так долго пришлось ждать. Тема была очень интересная, ваши идеи просто огонь. Нужно вернуться в начало дискуссии, освежить память. Ну и вопрошавший уже вроде не с нами увы Quote Share this post Link to post Share on other sites More sharing options...
brag 0 November 11, 2021 Posted November 11, 2021 · Report post :(( Уведомления слетели, тему пропустил видимо, и вот.. Quote Share this post Link to post Share on other sites More sharing options...
x893 6 November 11, 2021 Posted November 11, 2021 · Report post Что то мне это сильно напоминает RT-11 Quote Share this post Link to post Share on other sites More sharing options...
brag 0 November 13, 2021 Posted November 13, 2021 · Report post Возможно, я не видел ее код. Но если это так, значит RT-11 была крутая идея ;))) Лично я треды вообще не признаю, считаю их ненужной фигней )) Процессы да, нужны, но только если Ваш проект рассчитан на запуск сторонних приложений(стороннего кода, и то это еще под вопросом). Все остальные задачи асинхронный подход решает на ура, в том числе и на многопроцессорных/многоядерных системах. Процессоры могут располагаться не только на одном кристалле или на одной плате, но даже в разных местах земного шара ;)) Quote Share this post Link to post Share on other sites More sharing options...
amaora 2 November 13, 2021 Posted November 13, 2021 (edited) · Report post У меня не получилось уложить код содержащий printf в такую асинхронную модель. Внутри printf вызывается putc, который может сказать "не могу", и это вынуждает сохранять весь контекст на стеке. Пока такая задача одна, она может быть самой низкоприоритетной, но если больше то никак, надо несколько стеков. Edited November 13, 2021 by amaora Quote Share this post Link to post Share on other sites More sharing options...
brag 0 November 13, 2021 Posted November 13, 2021 · Report post Еще раз освежим суть... Простейшая задача: Когда юзер нажмет кнопку 'e' нужно через секунду зажечь светодиод. Когда нажмет 'd' - сразу потушить его. 2 раза подряд диод зажигать нельзя, равно как и тушить потушенный. Решить эту примитивнейшую задачу на тредах(в синхронном - блокирующем стиле) можно, но решение будет крайне замысловатое, стремное и тяжелое: Spoiler enum State { St_Initial, St_Led_ThreadStart, St_LedOn }; enum State state = St_Initial; Mutex mutex; void thread_led(void) { sleep(1); // blocking mutex_lock(&mutex); switch(state) { // shared state case St_LedThreadStart: led_on(); // shared resource! state = St_LedOn; break; } mutex_unlock(&mutex); } void main(void) { Thread *t; while(true) { int ch = getchar(); // blocking mutex_lock(&mutex); switch(state) { case St_Initial: if(ch == 'e') { state = St_Led_ThreadStart; t = create_thread(&thread_led); } break; case St_Led_ThreadStart: if(ch == 'd') { thread_kill(t); thread_join(t); // wait for thread killed state = St_Initial; } break; case St_LedOn: if(ch == 'd') { thread_join(t); // blocks led_off(); // shared resource } break; } mutex_unlock(&mutex); } } Итого 48 строк кода с кучей перекрестных зависимостей и необходимостью нехилой поддержки со стороны ОС. Сколько понадобится стека?? Да, посчитать можно притянув какой-то специальный софт, но ради диода и двух кнопок! По другому особо и не сделаешь. Реализация на столько сложная и запутанная, что где-то допустить ошибку будет очень просто, а найти ее непросто. Не факт, что в моей реализации выше нет ошибок :)) Что будет, если сюда добавить парочку i2c шин по пяток устройств на каждой, парочку can-шин с аналогичным числом девайсов, итд? Даже представить страшно о) А теперь асинхронное решение. Простейшая реализация, поймет каждый. Найти ошибку гораздо проще. Работает на любом железе, bare metal. Без всяких ОС вовсе. Даже SST тут не нужен ;)) И главное - сюда легко можно добавить любое количество устройств с любым функционалом. Spoiler enum State { St_Off, St_Timer, St_On }; enum State state = St_Off; Timer timer; static void on_timer(void) { switch(state) { case St_Timer: led_on(); state = St_On; break; } } static void on_key(int ch) { switch(state) { case St_Off: if(ch == 'e') { timer_start(&timer, 1, &on_timer); // async state = St_Timer; } break; case St_Timer: if(ch == 'd') { timer_stop(&timer); state = St_Off; } break; case St_On: if(ch == 'd') { led_off(); state = St_Off; } } getchar(&on_key); // async! } void main(void) { getchar(&on_key); // async! } Quote Share this post Link to post Share on other sites More sharing options...
jcxz 81 November 13, 2021 Posted November 13, 2021 · Report post 15 минут назад, brag сказал: А теперь асинхронное решение. Простейшая реализация, поймет каждый. Найти ошибку гораздо проще. Работает на любом железе, bare metal. Без всяких ОС вовсе. Вместо пустых разглагольствований, лучше покажите как в вашем супер-мега-решении разрешить проблему изложенную amaora постом выше. 15 минут назад, brag сказал: И главное - сюда легко можно добавить любое количество устройств с любым функционалом. Не надо "любое количество устройств". Достаточно только одного printf(), пытающегося вывести в поток вывода строку, размер которой превышает размер свободного места в буфере потока. И ваш метод сядет в лужу. Хотя решение и тут можно придумать. Но будет оно ОЧЕНЬ монстроидальным! Вытесняющие ОС придумали не потому что все вокруг такие дураки как вы думаете. Quote Share this post Link to post Share on other sites More sharing options...
brag 0 November 13, 2021 Posted November 13, 2021 · Report post 1 hour ago, amaora said: У меня не получилось уложить код содержащий printf в такую асинхронную модель. Внутри printf вызывается putc, который может сказать "не могу", и это вынуждает сохранять весь контекст на стеке. Пока такая задача одна, она может быть самой низкоприоритетной, но если больше то никак, надо несколько стеков. Вам нужен асинхронный printf. Реализации тут может быть 3: 1. Простейшая, то есть пока printf не отработает, второй запускать нельзя: void printf(void(*on_completion)(void), const char *format, ...); 2. Javascript-овая, аля console.log() - можно запускать сколько угодно, данные падают в очередь. Я почти всегда использую именно ее. static struct Queue_512_chars_long q; void putc(char c){ queue_push(&q, c); } 3. Промежуточная, когда в очередь падают не символы, а аргументы. Тут без шаблонов C++ обойтись трудно. static IntrusiveQueue printf_queue; template<class ... Args> class Printf : public IntrusiveQueueItem { public: void pritnf(const char* format, Args ... args) { this->format = format; this->args = args; printf_queue.push(this); } private: const char *format; Args ... args; }; // USE Printf<int, const char*> p1; void main(void) { p1.printf("%d, %s\n", 1234, "ssadffasdf"); } jcxz, Когда писал первый пост вопроса amaora еще не было Quote Share this post Link to post Share on other sites More sharing options...
jcxz 81 November 13, 2021 Posted November 13, 2021 · Report post 7 минут назад, brag сказал: Вам нужен асинхронный printf. Реализации тут может быть 3: Вопрос был: Как быть когда printf("%0100u", 1) выполняет печать в буфер потока, выводящего символы в UART, а в момент вызова этого printf() в буфере потока свободно всего например 10 байт? Что будет делать ваша система? Quote Share this post Link to post Share on other sites More sharing options...