пятница, 23 сентября 2011 г.

Кольцевой буфер

Синхронный режим передачи данных предполагает некоторую гибкость участников. Прежде всего это отображается в реализации буферов. При асинхронной передаче процесс записи будет выглядеть примерно так:
  1. Принял (максимальное или заранее известное количество) данных;
  2. Обработка данных; 
  3. Отправил подтверждение;
  4. GOTO 1
При синхронной же передаче приёмник представляет из себя "конвеер", обработка данных на котором должна происходить по мере их поступления. В зависимости от ценности этой информации при слишком активном входящем трафике рабочие на конвеере могут поступать по-разному: либо они нажмут на "стоп" и не запустят конвеер, пока не разгребут текущие данные (сообщив отправителю об этом или нет), либо все необработанные данные будут вылетать в трубу, освобождая место для новых.
Если организовать последний сценарий с помощью "обычного" буфера, который можно представить такой структурой
struct buf {
  unsigned char *data;
  unsigned long lenth;
};
, то появляется некоторая негибкость или потеря производительности при чтении буфера, если мы берём за правило тот факт, что мы хотим читать произвольное количество данных: если читать самые старые данные, то после нужно смещать остаток буфера, длинной (buf.lenth - read_data_size) к его началу, освобождая место в "хвосте" для новых данных. Такая операция с памятью займёт очень много времени, которое зависит от размера имеющихся данных. Либо же мы можем читать последние данные, декрементируя счётчик размера буфера. Понятно, что далеко не всегда такой подход удовлетворяет логику процесса.
Для увеличения производительности буфера можно организовать его в кольцо. Аттрибуты такого кольцевого буфера: собственно, сам буфер, представляющий собой выделенную область памяти определённой длинны; счётчик записи; счётчик чтения.
Логику работы буфера можно описать следующими положениями:
  1. Счётчик чтения всегда "догоняет" счётчик записи;
  2. Если значения счётчиков достигло размера выделенной памяти, то они зануляются (уходят на следующий круг);
  3. Если счётчик записи обогнал счётчик чтения на круг, то запись должна прекратиться (возможно, теряя поступающие данные), либо счётчик чтения должен инкрементироваться вслед за записью (теряя самые старые данные).
Отмечу лишь, что, если размер буфера равен размерности счётчиков (data[256], unsigned char counter), то следить за значением последних не обязательно: они будут зануляться "естественным путём".

Пример реализации процедур записи и чтения:
/* Round-robin buffer */
struct rr_buf {
  unsigned char *data;
  unsigned short wrPos; // счётчик записи
  unsigned short rdPos; // счётчик чтения
  unsigned char rounded; // счётчик записи ушёл на второй круг, а чтения - нет
  unsigned char overflow; // может быть использовано, если допустима потеря данных
};

struct rr_buf buf;

unsigned short
buf_write(unsigned char *data, unsigned long size)
{
  /* Bytes have been written */
  unsigned short bwritten = 0;

  while(bwritten < size) {
    /* Not enough space */
    if(g_buf.wrPos == g_buf.rdPos && g_buf.rounded)
      break;

    /* Writing byte by byte */
    *(g_buf.data + g_buf.wrPos) = *(data + bwritten);
    g_buf.wrPos++;

    if(g_buf.wrPos == SC_BUF_SIZE) {
      g_buf.wrPos = 0;
      g_buf.rounded = 1;
    }

    bwritten++;
  }

  return bwritten;
}

unsigned short
buf_read(unsigned char *data, unsigned long size)
{
  unsigned short bread = 0;

  while(bread %lt size) {
    /* End of data */
    if(buf.wrPos == buf.rdPos && !buf.rounded)
      break;

    *(data + bread) = *(buf.data + buf.rdPos);
    buf.rdPos++;

    if(buf.rdPos == BUF_SIZE) {
      buf.rdPos = 0;
      buf.rounded = 0;
    }

    bread++;
  }

  return bread;
}

/* May be useful */
unsigned short
get_data_size(void)
{
  unsigned short size;

  if(buf.rounded)
    size = BUF_SIZE - buf.rdPos + buf.wrPos;
  else
    size = buf.wrPos - buf.rdPos;

  return size;
}

Комментариев нет:

Отправить комментарий