Ringpuffer

Ein Ringpuffer bzw. Ringspeicher ist ein Verfahren in der Informatik, bei dem Daten kontinuierlich in einen Speicher mit fester Größe gespeichert werden. Wenn der Speicher voll ist, werden die ältesten Inhalte überschrieben. Dies führt dazu, dass die Daten nur für einen begrenzten Zeitraum zur Verfügung stehen. Bei grafischer Darstellung dieses Vorgangs erhält man zwangsläufig eine Ringform, woher diese Technik ihren Namen hat.

Verwendung als Pufferspeicher

Typischerweise sind Tastatur-Eingaben bei PCs über Ringpuffer realisiert. Eingaben werden zunächst asynchron im Interrupt-Verfahren entgegengenommen und als Rohdaten im Tastaturpuffer abgelegt, danach im Programmablauf bei Bedarf per API-Funktion abgerufen. Wenn der Zeiger auf Ende den Zeiger auf Anfang erreicht und damit beide Zeiger identisch werden, ist der Puffer leer. Im umgekehrten Fall ist der Puffer voll, die betreffende Eingabe wird im Interrupt verworfen und akustisch signalisiert.

Verwendung als Speichermedium

Ringpuffer werden vorwiegend in der elektronischen Verarbeitung von Sensorendaten verwendet. In der Sicherungstechnik werden Ringpuffer verwendet, da das Verfahren die Kosten für das Aufnahmemedium um einen erheblichen Faktor senkt. Videoüberwachungssysteme sind meist mit einem Ringspeicher ausgestattet, der z. B. die letzten 30 Minuten speichert.

Auch Flugschreiber in Flugzeugen sind in der Regel Ringspeicher, sodass nach einem Absturz die in den letzten Tagen aufgezeichneten Messwerte beziehungsweise die in den letzten Flugminuten aufgezeichneten Sprachaufzeichnungen vorhanden sind.

Programmierung

C

Folgendes Beispiel zeigt eine mögliche naive Implementation eines Ringpuffers in C am Beispiel eines temorären Logs. Dafür werden vier Zeiger verwendet, zwei um die Enden des verwendeten unterliegenden Speicherstücks zu repräsentieren und zwei um die Lese- und Schreibposition festzuhalten.

#include<stdio.h>
#include<stdlib.h>

/**
 * Datentyp der in diesem Beispiel als Ringpuffer verwendet wird.
 */
typedef struct ring_buffer_s ring_buffer_t;
struct ring_buffer_s {
    void *lo, *hi; // Anfang und Ende des Speicherbereichs
    char *read, *write; // Lese- und Schreibzeiger
};

/**
 * Initiiert ein Ringpuffer-Objekt mit einem dynamisch allozierten Speicherblock
 */
void ring_buffer_init(ring_buffer_t *rb, long capacity) {
    // Allocieren des Speicherbereichs
    rb->lo = malloc(capacity);
    if (rb->lo == NULL)
        abort();
    rb->hi = (char*) rb->lo + capacity;
    rb->read = rb->lo;
    rb->write = rb->lo;
}

/**
 * Diese Hilfsfunktion erlaubt das Weiterruecken eines Zeigers innerhalb des Rings.
 */
static inline void ring_buffer_ptr_advance(ring_buffer_t *rb, char **ptr) {
    ++*ptr;
    // Falls das Ende des Speichersegments erreicht wurde,
    // wird vorne wieder angefangen.
    if (*ptr == rb->hi)
        *ptr = rb->lo;
}

/**
 * Der Inhalt von `message` wird Byte fuer Byte in den Ringpuffer geschrieben.
 * Dabei wird jede Iteration der Schreib- und ggf. der Lesezeiger weitergerueckt.
 */
void ring_buffer_write(ring_buffer_t *rb, const char *message) {
    // Daten in den Ringspeicher kopieren.
    while (*message) {
        *rb->write = *message++;
        ring_buffer_ptr_advance(rb, &rb->write);
        // Sollte über den Lesezeiger hinweg geschrieben werden,
        // muss dieser auch weiterrücken, um nicht das Ende der
        // neusten Nachricht zuerst zu lesen.
        if (rb->write == rb->read)
            ring_buffer_ptr_advance(rb, &rb->read);
    }
}

/**
 * Diese Funktion erlaubt das stueckweise Auslesen eines Ringpuffers.
 * Sollte beim Lesen die Schreibposition erreicht werden, ist nichts mehr zu lesen.
 */
long ring_buffer_read(ring_buffer_t *rb, long read_max, char* output_buffer) {
    for (long i = 0; i < read_max; i++) {
        // Überprüfe ob alle Daten des Ringpuffers ausgelesen wurden sind.
        // Wenn ja, beende das Auslesen.
        if (rb->read == rb->write)
            return i;
        output_buffer[i] = *rb->read;
        ring_buffer_ptr_advance(rb, &rb->read);
    }
    return read_max;
}

Dies ist ein minimales Anwendungsbeispiel, in dem mehr Zeichen in dem Ringpuffer geschrieben werden, als in den Speicherabschnitt passen. Das sorgt dafür, das Teile der früheren Nachrichten überschrieben werden.

int main() {
    ring_buffer_t recent_log;
    // Ringpuffer der Größe 128.
    ring_buffer_init(&recent_log, 128);

    ring_buffer_write(&recent_log,
        "[001] Info: Diese Nachricht wird unvollstaendig sein.\n");
    ring_buffer_write(&recent_log, "[002] Info: Dieses Programm ist ein Beispiel.\n");
    ring_buffer_write(&recent_log, 
        "[003] Info: Spaetestens jetzt, wird die erste Nachricht ueberschrieben.\n");

    char read_buffer[128];
    ring_buffer_read(&recent_log, 128, read_buffer);
    printf("%s", read_buffer);
}

Die Ausgabe des oberen Programms sieht wie folgt aus:

ig sein.
[002] Info: Dieses Programm ist ein Beispiel.
[003] Info: Spaetestens jetzt, wird die erste Nachricht ueberschrieben.

Man kann klar sehen, dass die erste Nachricht teilweise überschrieben wurde.

Siehe auch