|
Логирование данных во внешнюю память. |
|
|
|
Feb 4 2015, 13:32
|

Гуру
     
Группа: Свой
Сообщений: 2 957
Регистрация: 19-09-06
Из: Москва
Пользователь №: 20 514

|
Цитата(Jenya7 @ Feb 4 2015, 12:25)  мне нужно логировать данные во внешнюю память. каждая запись это структура. думал сделать circular buffer чтоб можно было читать/писать из разных потоков. вопрос есть ли какая нибудь готовая библиотека чтоб не городить свои велосипеды? что-то типа: Код #define IBUF_LENGTH (32) #define IBUF_MASK (IBUF_LENGTH-1)
#define OBUF_LENGTH (32) #define OBUF_MASK (OBUF_LENGTH-1)
uint32_t ibus_start, ibuf_end, obuf_start, obuf_end;
char BufOut[OBUF_LENGTH]; char BufIn[IBUF_LENGTH];
void obuf_init() { obuf_start = obuf_end = 0; }
void ibuf_init() { ibuf_start = ibuf_end = 0; }
void put(char c) { while((ibuf_start+IBUF_LENGTH) == ibuf_end); BufIn[ibuf_end++ & IBUF_MASK] = c; }
char get(void) { while(obuf_end == obuf_start); return BufOut[obuf_start++ & OBUF_MASK]; } на скорую руку, так что проверяйте  недостаток - размер массивов по степени двойки, тип, разумеется, можно сделать какой нужно
|
|
|
|
|
Feb 4 2015, 14:14
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
Цитата(toweroff @ Feb 4 2015, 19:32)  что-то типа: Код #define IBUF_LENGTH (32) #define IBUF_MASK (IBUF_LENGTH-1)
#define OBUF_LENGTH (32) #define OBUF_MASK (OBUF_LENGTH-1)
uint32_t ibus_start, ibuf_end, obuf_start, obuf_end;
char BufOut[OBUF_LENGTH]; char BufIn[IBUF_LENGTH];
void obuf_init() { obuf_start = obuf_end = 0; }
void ibuf_init() { ibuf_start = ibuf_end = 0; }
void put(char c) { while((ibuf_start+IBUF_LENGTH) == ibuf_end); BufIn[ibuf_end++ & IBUF_MASK] = c; }
char get(void) { while(obuf_end == obuf_start); return BufOut[obuf_start++ & OBUF_MASK]; } на скорую руку, так что проверяйте  недостаток - размер массивов по степени двойки, тип, разумеется, можно сделать какой нужно у меня затык как послать структуру. память 32к*8. Структура struct record { uint32_t timestamp; uint16_t delta; } распаковывать и посылать по байтам?
|
|
|
|
|
Feb 4 2015, 14:20
|

Местный
  
Группа: Участник
Сообщений: 329
Регистрация: 23-04-14
Пользователь №: 81 502

|
Цитата(toweroff @ Feb 4 2015, 13:32)  что-то типа: на скорую руку, так что проверяйте  недостаток - размер массивов по степени двойки, тип, разумеется, можно сделать какой нужно "...иногда лучше молчать, чем говорить" (с) Топикстартер явно упомянул многопоточность, а тут конструкции, типа "data[obuf_start++ & OBUF_MASK]". Треды, атомарность, синхронизация.. слышали про такое ? Можно сделать закат солнца вручную, т.е. написать свою очередь. Это не сложно. Сложно сделать так, чтобы оно работало правильно. В самом тупом случае берутся примитивы синхронизации, типа мютексов, пишется тупая кольцевая очередь, где PutData() / GetData() обвешаны этими мютексами как новогодние елки. Тупо, примитивно, не сильно оптимально по быстродействию и требует наличия ОС. Если ОС нет или нужно выжать производительность, пишется неблокирующая очередь (а потом еще полгода отлаживается). Тут уже в ход идут такие вещи, как intrinsic функции для атомарного инкремента/декремента, реализация CAS, спинлоки и memory barriers если хотим, шоб оно еще не глючило на многоядерных системах.. .. Это один из любимых вопросов на собеседованиях в конторах типа АРМа.. В промежуточном случае пишутся свои мютексы. Но тут опять же надо очень хорошо разбираться в том, что делаешь. Можно положиться на факт, что присваивание 32-битных значений атомарное, но для многоядерных систем все сильно сложнее с чтением и записью в одну переменную с разных ядер.
|
|
|
|
|
Feb 4 2015, 15:15
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
упрощенно, в первом приближении написал так. как минимум проверю живая ли память. Код uint32_t LOGGER_SaveData(uint32_t data) { uint32_t low_byte, high_byte; low_byte = data & 0xFF; high_byte = data >> 8; SPIEEPROM_WriteByte(current_address, low_byte); SPIEEPROM_WriteByte(current_address+1, high_byte); current_address += 2; if(current_address>=EEPROM_SIZE) current_address = 0;
return current_address; }
uint32_t LOGGER_GetData(uint32_t address) { uint32_t low_byte, high_byte; low_byte = SPIEEPROM_ReadByte(address); high_byte = SPIEEPROM_ReadByte(address+1); return (high_byte << 8) | low_byte; } Цитата(CrimsonPig @ Feb 4 2015, 20:20)  "...иногда лучше молчать, чем говорить" (с) Топикстартер явно упомянул многопоточность, а тут конструкции, типа "data[obuf_start++ & OBUF_MASK]". Треды, атомарность, синхронизация.. слышали про такое ?
Можно сделать закат солнца вручную, т.е. написать свою очередь. Это не сложно. Сложно сделать так, чтобы оно работало правильно. В самом тупом случае берутся примитивы синхронизации, типа мютексов, пишется тупая кольцевая очередь, где PutData() / GetData() обвешаны этими мютексами как новогодние елки. Тупо, примитивно, не сильно оптимально по быстродействию и требует наличия ОС.
Если ОС нет или нужно выжать производительность, пишется неблокирующая очередь (а потом еще полгода отлаживается). Тут уже в ход идут такие вещи, как intrinsic функции для атомарного инкремента/декремента, реализация CAS, спинлоки и memory barriers если хотим, шоб оно еще не глючило на многоядерных системах.. .. Это один из любимых вопросов на собеседованиях в конторах типа АРМа..
В промежуточном случае пишутся свои мютексы. Но тут опять же надо очень хорошо разбираться в том, что делаешь.
Можно положиться на факт, что присваивание 32-битных значений атомарное, но для многоядерных систем все сильно сложнее с чтением и записью в одну переменную с разных ядер. не горячитесь. вот есть приличный буфер. вопрос как увязать все вместе. Код typedef struct struct_cBuffer { unsigned char *dataptr; ///< the physical memory address where the buffer is stored unsigned short size; ///< the allocated size of the buffer unsigned short datalength; ///< the length of the data currently in the buffer unsigned short dataindex; ///< the index into the buffer where the data starts } cBuffer;
void bufferInit(cBuffer* buffer, unsigned char *start, unsigned short size) { // begin critical section CRITICAL_SECTION_START; // set start pointer of the buffer buffer->dataptr = start; buffer->size = size; // initialize index and length buffer->dataindex = 0; buffer->datalength = 0; // end critical section CRITICAL_SECTION_END; }
// access routines unsigned char bufferGetFromFront(cBuffer* buffer) { unsigned char data = 0; // begin critical section CRITICAL_SECTION_START; // check to see if there's data in the buffer if(buffer->datalength) { // get the first character from buffer data = buffer->dataptr[buffer->dataindex]; // move index down and decrement length buffer->dataindex++; if(buffer->dataindex >= buffer->size) { buffer->dataindex -= buffer->size; } buffer->datalength--; } // end critical section CRITICAL_SECTION_END; // return return data; }
void bufferDumpFromFront(cBuffer* buffer, unsigned short numbytes) { // begin critical section CRITICAL_SECTION_START; // dump numbytes from the front of the buffer // are we dumping less than the entire buffer? if(numbytes < buffer->datalength) { // move index down by numbytes and decrement length by numbytes buffer->dataindex += numbytes; if(buffer->dataindex >= buffer->size) { buffer->dataindex -= buffer->size; } buffer->datalength -= numbytes; } else { // flush the whole buffer buffer->datalength = 0; } // end critical section CRITICAL_SECTION_END; }
unsigned char bufferGetAtIndex(cBuffer* buffer, unsigned short index) { // begin critical section CRITICAL_SECTION_START; // return character at index in buffer unsigned char data = buffer->dataptr[(buffer->dataindex+index)%(buffer->size)]; // end critical section CRITICAL_SECTION_END; return data; }
unsigned char bufferAddToEnd(cBuffer* buffer, unsigned char data) { // begin critical section CRITICAL_SECTION_START; // make sure the buffer has room if(buffer->datalength < buffer->size) { // save data byte at end of buffer buffer->dataptr[(buffer->dataindex + buffer->datalength) % buffer->size] = data; // increment the length buffer->datalength++; // end critical section CRITICAL_SECTION_END; // return success return -1; } // end critical section CRITICAL_SECTION_END; // return failure return 0; }
unsigned short bufferIsNotFull(cBuffer* buffer) { // begin critical section CRITICAL_SECTION_START; // check to see if the buffer has room // return true if there is room unsigned short bytesleft = (buffer->size - buffer->datalength); // end critical section CRITICAL_SECTION_END; return bytesleft; }
void bufferFlush(cBuffer* buffer) { // begin critical section CRITICAL_SECTION_START; // flush contents of the buffer buffer->datalength = 0; // end critical section CRITICAL_SECTION_END; }
|
|
|
|
|
Feb 4 2015, 15:18
|
Знающий
   
Группа: Свой
Сообщений: 558
Регистрация: 26-11-14
Из: Зеленоград
Пользователь №: 83 842

|
Цитата(CrimsonPig @ Feb 4 2015, 17:20)  Если ОС нет или нужно выжать производительность, пишется неблокирующая очередь (а потом еще полгода отлаживается). А зачем самому писать, если можно изучить готовые варианты: http://www.liblfds.org/ - C, вроде как работает на армах из коробки http://libcds.sourceforge.net/ - C++, придется запилитить свою прокладку для атомарных операций
|
|
|
|
|
Feb 4 2015, 15:30
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
Цитата(Kabdim @ Feb 4 2015, 21:18)  А зачем самому писать, если можно изучить готовые варианты: http://www.liblfds.org/ - C, вроде как работает на армах из коробки http://libcds.sourceforge.net/ - C++, придется запилитить свою прокладку для атомарных операций мда...я понимаю человек в вижуал студии писал у него размах недетский. нам бы под ембедед. оптимизированный по скорости.
|
|
|
|
|
Feb 4 2015, 15:43
|

Местный
  
Группа: Участник
Сообщений: 329
Регистрация: 23-04-14
Пользователь №: 81 502

|
Цитата(Jenya7 @ Feb 4 2015, 15:15)  не горячитесь. вот есть приличный буфер. вопрос как увязать все вместе. То есть макросы CRITICAL_SECTION_START CRITICAL_SECTION_END уже есть и работают ? В чем вопрос-то тогда ? Берем лопату и заменяем char на свой тип данных (это если по тупому). Насчет "приличности" можно сильно поспорить. Это пример того, что я выше назвал "увешан мютексами" как новогодняя елка. Как указывалось выше, если сделать длину буфера (в единицах данных) равной степени двойки, то адресация по модулю очень сильно упрощается. Применение /, % для таких вещей заставляет меня вздрогнуть. Далее. Если немного подумать, то можно понять, что увешивать мютексами можно только место, где происходит работа с индексами в буфере. Копирование _данных_ в буфер и из буфера не требует блокировки. Т.е. при помещении данных в буфер сначала копируем данные на незанятое место, только потом обновляем индекс (атомарно). При вынимании данных - в обратном порядке. > нам бы под ембедед. оптимизированный по скорости. Гы-гы-гы, с операцией деления и взятия остатка вы далеко уйдете в этом направлении, особенно если данные в буфер будут класться в прерываниях
Сообщение отредактировал CrimsonPig - Feb 4 2015, 15:46
|
|
|
|
|
Feb 4 2015, 15:47
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
Цитата(CrimsonPig @ Feb 4 2015, 21:43)  То есть макросы CRITICAL_SECTION_START CRITICAL_SECTION_END уже есть и работают ? В чем вопрос-то тогда ? Берем лопату и заменяем char на свой тип данных (это если по тупому).
Насчет "приличности" можно сильно поспорить. Это пример того, что я выше назвал "увешан мютексами" как новогодняя елка. Как указывалось выше, если сделать длину буфера (в единицах данных) равной степени двойки, то адресация по модулю очень сильно упрощается. Применение /, % для таких вещей заставляет меня вздрогнуть. Далее. Если немного подумать, то можно понять, что увешивать мютексами можно только место, где происходит работа с индексами в буфере. Копирование _данных_ в буфер и из буфера не требует блокировки. Т.е. при помещении данных в буфер сначала копируем данные на незанятое место, только потом обновляем индекс (атомарно). При вынимании данных - в обратном порядке.
> нам бы под ембедед. оптимизированный по скорости.
Гы-гы-гы, с операцией деления и взятия остатка вы далеко уйдете в этом направлении, особенно если данные в буфер будут класться в прерываниях ок. а как передать структуру в буфер. или тупо нарезать ее на байты и посылать побайтово? а при принятии склеивать обратно? я не спорю буфер можно пооптимизировать. это так сказать скелет.
Сообщение отредактировал Jenya7 - Feb 4 2015, 15:50
|
|
|
|
|
Feb 4 2015, 15:57
|

Местный
  
Группа: Участник
Сообщений: 329
Регистрация: 23-04-14
Пользователь №: 81 502

|
Цитата(Jenya7 @ Feb 4 2015, 15:47)  ок. а как передать структуру в буфер. или тупо нарезать ее на байты и посылать побайтово? а при принятии склеивать обратно? я не спорю буфер можно пооптимизировать. это так сказать скелет. Ну, в самом простейшем случае можно и по байтам туда загонять. Это должно работать (если код рабочий), но будет жутко неэффективно по определению. Если хочется пихать свои данные то делается так: - сидим, смотрим в существующий код. Понимаем как оно работает - выкидываем существующий код - пишем свой код с блекджеком и шлюхами (void* и memcpy). - Материмся, отлаживаем, пишем тесты Ну или можно попытаться найти уже существующие имплементации поторкобезопасной очереди, которая работает с данными более-менее произвольных размеров. Где взять - не знаю
|
|
|
|
|
Feb 4 2015, 16:15
|

Местный
  
Группа: Участник
Сообщений: 329
Регистрация: 23-04-14
Пользователь №: 81 502

|
Цитата(Jenya7 @ Feb 4 2015, 16:07)  вот я и пытаюсь "смотреть". только пока некуда.  А как же реализация буфера из сообщений выше ? void bufferInit(cBuffer* buffer, unsigned char *start, unsigned short size) и товарищи ? Выглядит как рабочая (на первый взгляд)... ну, со своими заморочками и недостатками. Какая разница, что в абстрактный буфер копировать, байт или кусок данных ? Общий принцип-то тот же.
|
|
|
|
|
Feb 4 2015, 16:26
|
Местный
  
Группа: Свой
Сообщений: 271
Регистрация: 6-12-11
Из: Taganrog
Пользователь №: 68 701

|
Я бы порекомендовал от вечных шлюх последнего времени (многопроцессорности) удалиться, тогда реализация мьютекса будет одной командой -- запрещение/разрешение прерываний, которая обычно выносится в интринсики производителем. Вряд ли при запрещённом прерывании сможет случиться нечто, которое переключит задачи и внесёт сумятицу в процесс инкремента указателя, в котором обычно "собака роется". Упоминание "SPIEEPROM" у ТС говорит о достаточной медлительности самой операции записи, так что разбить исходную структуру на байты или хотя бы short-ы будет нестрашно. Хотя в идеале -- захочется несколько типов данных пихать рано или поздно, тогда в "систему" "прирастёт" длина "пакета", его "тип", время его "случения", где-то рядом с кольцевым буфером надо будет хранить его текущие позиции "начала" и "конца"... Короче, получится настоящая трассировка всего, чего "приспичит" отладить "вживую", а потом "выгрузиться" в удобной форме... Эта тема очень объёмная в идеале, даже если не трогать "настоящих шлюх", а для них легче тогда устроить отдельные ЦБ для каждого ядра, потом сливать... Лучше бы "EEPROM" делать в конце всего исполнения -- когда "усё пропало", а до того складироваться в ОЗУ -- оно обычно быстрее. Ну и если опереции "EEPROM" нереентерабельные -- их тоже придётся под мьютекс затягивать.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|