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

Система, управляемая событиями и SST(super-simple tasker)

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

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


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

brag, а Вы дин. памятью пользуетесь? Выкинув эти топорные ждущие оси в связи с появлением памяти в предскауемых объемах задумался, а так ли страшна фрагментация как ее малюют? Жутко надоело на каждый чих типа контйенера и чего нить стандартного из boost / stl придумывать свои костыли глючные. 21 век на дворе, компилер для Silabs мой даж лямбды понимает и int qq = 0xb10111; (мечта железячника) :)

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


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

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;
};

 

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


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

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

Допустим есть асинхронная функция 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) - тогда ничего не остается, как использовать динамику.

Изменено пользователем brag
убрал длинный код под спойлеры

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


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

9 minutes ago, brag said:

Pure C...

Off: убирайте длинный код под спойлеры) Это такой глазик на панеле редактирования поста.

image.png.6dce35f53443416a0e0175f04d2fd4f0.png

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


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

brag, спасибо за ответы, жаль что так долго пришлось ждать. Тема была очень интересная, ваши идеи просто огонь. Нужно вернуться в начало дискуссии, освежить память. Ну и вопрошавший уже вроде не с нами увы

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


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

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

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


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

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

Изменено пользователем amaora

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


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

Еще раз освежим суть... Простейшая задача: Когда юзер нажмет кнопку '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!
}

 

 

 

 

 

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


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

15 минут назад, brag сказал:

А теперь асинхронное решение. Простейшая реализация, поймет каждый. Найти ошибку гораздо проще. Работает на любом железе, bare metal. Без всяких ОС вовсе.

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

15 минут назад, brag сказал:

И главное - сюда легко можно добавить любое количество устройств с любым функционалом.

Не надо "любое количество устройств". Достаточно только одного printf(), пытающегося вывести в поток вывода строку, размер которой превышает размер свободного места в буфере потока. И ваш метод сядет в лужу.  :sarcastic:

Хотя решение и тут можно придумать. Но будет оно ОЧЕНЬ монстроидальным! Вытесняющие ОС придумали не потому что все вокруг такие дураки как вы думаете.  :unknw:

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


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

 

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  еще не было

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


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

7 минут назад, brag сказал:

Вам нужен асинхронный printf. Реализации тут может быть 3:

Вопрос был: Как быть когда printf("%0100u", 1) выполняет печать в буфер потока, выводящего символы в UART, а в момент вызова этого printf() в буфере потока свободно всего например 10 байт?

Что будет делать ваша система?

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


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

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

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

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

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

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

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

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

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

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