2010-12-28 7 views
1

Ich arbeite an der Embedded-Plattform, wo ich vollen Zugriff auf direkten Schreib-/Lese-Speicher haben.Terminal I/O (Wie emulieren Terminal lesen/Schreiben mit bestimmten Speicherbereich?)

Ich arbeite auch an der asymmetrischen Verarbeitung, in der ich eine Echtzeitanwendung habe, die gleichzeitig mit Linux läuft, aber völlig isoliert von Linux.

Ich möchte die Nachrichten von RT-App auf Linux-Konsole anzeigen (und möglicherweise Befehl von Linux an RT App senden). Meine derzeitige Lösung besteht darin, alles von der RT-App zur seriellen Schnittstelle auszugeben. Dann werde ich unter Linux die Eingabe des seriellen Ports lesen.

Dies funktioniert, aber es scheint unnötig, weil die RT-Anwendungen und Linux auf der gleichen physischen Maschine ist. Wenn Sie daran denken, wie der serielle Port funktioniert, hat er einen Speicherpuffer und die Anwendung kann in diesen Puffer schreiben/lesen. Daher habe ich mich gefragt, ob es möglich ist, eine Terminalanzeige mit einem bestimmten Speicherbereich (d. H. 0x10000) zu verbinden, und wenn RT App eine Nachricht an 0x10000 "druckt", würde das Linux-Terminal die Nachricht anzeigen?

Antwort

1

Sie könnten eine Art virtuellen seriellen Port mit "Mailbox" -Techniken erstellen. Ich beschreibe die Hälfte einer einfachen Arbeitsverbindung, wahrscheinlich wollen Sie eine davon in jede Richtung, damit Sie Befehle senden und Antworten erhalten können.

Einen Teil des Speichers während der Kernel-Initialisierung reservieren. Sagen wir 4k, da das oft eine Seite ist, aber 256 oder sogar 16 Bytes funktionieren auch. Reservieren Sie eine Sekunde für die andere Richtung.

Wenn der "Schreiber" eines Kanals etwas sagen will, prüft er zuerst, ob das erste 32-Bit-Wort Null ist. Wenn dies der Fall ist, schreibt es ab dem 5. Byte eine Nachricht - Text- oder Binärdaten, bis zu einem Maximum von 4k - 4 = 4092 Bytes. Dann setzt es das erste Wort gleich der Anzahl der Bytes, die es geschrieben hat.

Der Empfänger überwacht die Bytezahl im ersten Wort des empfangenen Kanals. Wenn es eine von Null verschiedene Anzahl von Bytes sieht, liest es so viele Bytes aus dem Speicher. Dann setzt er die Byteanzahl auf Null, um dem Schreiber zu signalisieren, dass eine neue Nachricht nun nach Belieben geschrieben werden kann.

Die einzige Sache, auf die es ankommt, ist, dass Sie tatsächlich auf den realen Speicher zugreifen oder durch den gleichen Cache arbeiten, und dass Sie eine atomare Schreiboperation zum Schreiben der Bytezahl haben (wenn Sie keine atomaren 32-Bit-Schreibvorgänge haben) , verwenden Sie eine 16-Bit-Zählung, die sowieso reichlich ist, oder machen Sie den Puffer kleiner und verwenden Sie eine 8-Bit-Zählung). Da der Schreiber es nur auf einen Wert ungleich null setzen kann, wenn es Null ist, und der Leser kann es nur auf einen Nullwert setzen, wenn es nicht Null ist, es alles funktioniert.

Dies ist natürlich ein einfacher Mechanismus, und beide Seiten können vom anderen blockiert werden. Sie können jedoch die Komponenten entwerfen, die die zu berücksichtigenden Nachrichten enthalten. Sie können es auch erweitern, indem Sie mehrere Nachrichten im Flug bereitstellen oder parallel einen zusätzlichen Prioritäts- oder Fehlermeldungskanal hinzufügen.

Oh, bevor Sie springen und dies zu codieren, tun Sie einige Web-Suche. Ich bin mir sicher, dass es bereits einen Mechanismus wie diesen oder etwas anderes gibt, mit dem Sie Ihre RT- und Linux-Komponenten verbinden können. Aber es ist auch interessant zu lernen, es selbst zu tun - und notwendig, wenn Sie auf ein kleineres Embedded-System ohne ein Betriebssystem stoßen, das die Funktionalität für Sie bietet.

+0

Dies ist ein guter Vorschlag (einfach genug zu verstehen), ich werde in dieser Richtung untersuchen! – Patrick

1

Es gibt einige Möglichkeiten, IPC in Linux auszuführen, und normalerweise sind Dateideskriptoren beteiligt. Meiner Meinung nach ist es am besten, das zu tun, was du tust, es ist wahrscheinlich zu viel, wie du gesagt hast, aber der Versuch, deine eigene Shared-Memory-Lösung zu implementieren, ist definitiv noch mehr Overkill.

EDIT:

Wie in den Kommentaren erwähnt, ist die Tatsache, dass Sie eine Echtzeit-Prozess laufen wirft Sachen weg und native IPC ist wahrscheinlich nicht die beste Wahl. Here ist ein Artikel, den ich gerade gegoogelt habe, der die Antwort zu bieten scheint, die Sie suchen.

Wenn Sie nicht alles lesen wollen, schlägt es entweder FIFOs oder Shared Memory als Parallelitätsprimitiv vor, die Sie verwenden sollten, je nachdem, welche Art von Kommunikation Sie benötigen. Aus eigener Erfahrung führen FIFOs langfristig zu weniger Kopfschmerzen, da Sie sich viel weniger Gedanken über die Synchronisation machen müssen.

Sie müssten wahrscheinlich ein kleines Programm schreiben, das aus dem FIFO/Shared Memory liest und Nachrichten an stdout sendet, wenn Sie das Programm in einem Terminal überwachen wollen.

+1

Normale IPC-Methoden und ähnliche Kernel-Dienste sind wahrscheinlich nicht anwendbar, da der RT-Prozess nicht unter der Kontrolle des Linux-Kernels läuft (mehr umgekehrt - der Kernel wird vom Realtime-Scheduler als Task mit der niedrigsten Priorität ausgeführt) –

+0

@Chris das ist ein toller Punkt. Realtime-Systeme sind viel zu kompliziert zu ihrem eigenen Vorteil :(Bearbeiten meiner Antwort zu kompensieren. – num1

+0

Danke für den Link, es sieht aus wie die Antwort, die ich suche! Ich werde es lesen und die Lösung mit dem ersten Beitrag kombinieren. – Patrick

0

Ich habe erfolgreich ein Shared-Memory-Fifo-System verwendet, um zwischen Prozessen zu kommunizieren (obwohl nicht das gleiche Szenario, das Sie haben). Der Schlüssel ist, dass nur ein einzelner Thread der Produzent sein kann und ein einzelner Thread ein Konsument sein kann. Sie müssen auch sicherstellen, dass Chris Stratton erwähnt, dass Caching mit angemessenen Speicherbarrieren ordnungsgemäß behandelt wird. Linux hat eine ziemlich direkte API für Speicherbarrieren, ich weiß nicht, was Ihre Echtzeit-App dafür zur Verfügung haben könnte. Ich habe versucht herauszufinden, wo Speicherbarrieren erforderlich sein könnten.

Das Folgende ist eine ungetestete (und vollständig nicht optimierte) Implementierung eines gemeinsam genutzten Fifos. Ihre RT-App kann Zeichen in den FIFO schreiben, und die Linux-App oder der Linux-Treiber kann Zeichen aus dem FIFO lesen. Idealerweise haben Sie einen Mechanismus für die Linux-Seite, der signalisiert wird, dass Daten bereit sind (vielleicht ein ansonsten nicht verwendeter GPIO, der einen Interrupt auslösen kann, wenn die RT-Seite ihn anstößt?). Andernfalls könnte die Linux-Seite Daten im Fifo abfragen, aber das ist wahrscheinlich aus den üblichen Gründen nicht ideal.

struct fifo { 
    char volatile* buf; 
    int buf_len; 
    int volatile head; // index to first char in the fifo 
    int volatile tail; // index to next empty slot in fifo 
         // if (head == tail) then fifo is empty 
         // if (tail < head) the fifo has 'wrapped' 
}; 

void fifo_init(struct fifo* pFifo, char* buf, int buf_len) 
{ 
    pFifo->buf = buf; 
    pFifo->buf_len = buf_len; 
    pFifo->head = 0; 
    pFifo->tail = 0; 
} 

int fifo_is_full(struct fifo* pFifo) 
{ 
    int head; 
    int tail; 

    // a read barrier may be required here 
    head = pFifo->head; 
    tail = pFifo->tail; 

    // fifo is full if ading another char would cause 
    // tail == head 
    ++tail; 
    if (tail == pFifo->buf_len) { 
     tail = 0; 
    } 

    return (tail == head); 
} 


int fifo_is_empty( struct fifo* pFifo) 
{ 
    int head; 
    int tail; 

    // a read barrier may be required here 
    head = pFifo->head; 
    tail = pFifo->tail; 

    return head == tail; 
} 


// this function is the only one that modifies 
// the pFifo->tail index. It can only be used 
// by a single writer thread. 
int fifo_putchar(struct fifo* pFifo, char c) 
{ 
    int tail = pFifo->tail; 

    if (fifo_is_full(pFifo)) return 0; 

    pFifo->buf[tail] = c; 
    ++tail; 
    if (tail == pFifo->buf_len) { 
     tail = 0; 
    } 

    //note: the newly placed character isn't actually 'in' the fifo 
    // as far as the reader thread is concerned until the following 
    // statement is run  
    pFifo->tail = tail; 

    // a write barrier may need to be placed here depending on 
    // the system. Microsoft compilers place a barrier by virtue of 
    // the volatile keyword, on a Linux system a `wmb()` may be needed 
    // other systems will have other requirements 
    return 1; 
} 


// this function is the only one that modified the 
// pFifo->head index. It can only be used by a single 
// reader thread. 
int fifo_getchar(struct fifo* pFifo, char* pC) 
{ 
    char c; 
    int head = pFifo->head; 

    if (fifo_is_empty(pFifo)) return 0; 

    // a read barrier may be required here depending on the system 
    c = pFifo->buf[head]; 

    ++head; 
    if (head == pFifo->buf_len) { 
     head = 0; 
    } 

    // as far as the write thread is concerned, the char 
    // hasn't been removed until this statement is executed 
    pFifo->head = head; 

    // a write barrier might be required 

    *pC = c; 
    return 1; 
} 

Bei der Aktualisierung der Indizes könnte es sinnvoller sein, plattformunabhängige APIs zu verwenden.

einige Optimierungen, die durchgeführt werden können:

  • wenn die Fifo-Größe auf eine Leistung von 2 beschränkt ist, kann die Verpackung durch Maskierung die Indizes entsprechend
  • der Put behandelt werden/Funktionen geändert werden lassen könnte oder zusätzliche get/put-Funktionen könnten hinzugefügt werden, um eine Zeichenkette oder ein Array von Datenbytes zu akzeptieren, und die Zeichenkette/das Array kann effizienter in den FIFO-Puffer kopiert werden (oder von diesem weg).

Der Schlüssel dazu eingerichtet ist, dass die Leser Daten im Fifo ohne Sorge lesen, dass der Autor es, solange der head Index aktualisiert wird erst, nachdem die Daten ausgelesen werden überschrieben. Ähnlich für den Schreiber - es kann in den "freien" Teil des Puffers schreiben, solange der tail Index nicht aktualisiert wird, bis die Daten in den Puffer geschrieben wurden. Die einzige wirkliche Komplikation besteht darin, sicherzustellen, dass geeignete Elemente mit volatile markiert sind und entsprechende Speicherbarrieren aufgerufen werden.