Fast Message Queue (FMQ)

Wenn Sie AIDL-Unterstützung benötigen, lesen Sie auch FMQ mit AIDL

Die Infrastruktur von HIDL für Remoteprozeduraufrufe (Remote Procedure Call, RPC) nutzt Binder-Mechanismen, Das bedeutet, dass Aufrufe mit Aufwand verbunden sind, Kernel-Vorgänge erfordern und möglicherweise Planeraktion. Wenn jedoch Daten zwischen verschiedenen mit weniger Overhead und ohne Kernel-Beteiligung, ist die Fast Message Queue verwendet wird.

FMQ erstellt Nachrichtenwarteschlangen mit den gewünschten Attributen. Eine Das MQDescriptorSync- oder MQDescriptorUnsync-Objekt kann die über einen HIDL RPC-Aufruf gesendet und vom empfangenden Prozess für den Zugriff Nachrichtenwarteschlange.

Schnelle Nachrichtenwarteschlangen werden nur in C++ und auf Geräten unterstützt. mit Android 8.0 oder höher.

MessageQueue-Typen

Android unterstützt zwei Warteschlangentypen (sogenannte flavors):

  • Nicht synchronisierte Warteschlangen können einen Überlauf ausführen und viele Lesern; Jeder Leser muss Daten rechtzeitig lesen oder verlieren.
  • Synchronisierte Warteschlangen dürfen nicht überlaufen und dürfen einem einzigen Leser.

Beide Warteschlangentypen dürfen keinen Unterlauf haben (Lesezugriff aus einer leeren Warteschlange) schlägt fehl) und kann nur einen Writer haben.

Nicht synchronisiert

Eine nicht synchronisierte Warteschlange hat nur einen Writer, kann aber beliebig viele Lesern. Es gibt eine Schreibposition für die Warteschlange: Jeder Leser sieht sich jedoch unabhängig von Leseposition.

Schreibvorgänge in die Warteschlange sind immer erfolgreich (nicht auf Überlauf überprüft), solange Folgendes gilt: sie nicht größer sind als die konfigurierte Warteschlangenkapazität (Schreibvorgänge sind größer als die Warteschlangenkapazität sofort ausfallen). Jeder Leser kann verschiedene Lesematerialien anstatt darauf zu warten, dass jeder Leser alle Daten liest, kann aus der Warteschlange ausfallen, wenn neue Schreibvorgänge den Speicherplatz benötigen.

Leser sind dafür verantwortlich, Daten abzurufen, bevor diese am Ende des in der Warteschlange. Ein Lesevorgang, der versucht, mehr Daten zu lesen, als verfügbar sind schlägt sofort fehl (bei Nichtblockierung) oder wartet, bis genügend Daten verfügbar sind (falls Blockierung). Lesevorgang, der immer versucht, mehr Daten als die Warteschlangenkapazität zu lesen schlägt sofort fehl.

Wenn Lesende nicht mit dem Schreibenden Schritt halten können, sodass die Datenmenge die von diesem Lesegerät geschrieben und noch nicht gelesen wurden, größer als die Kapazität der Warteschlange ist, Beim nächsten Lesevorgang werden keine Daten zurückgegeben. wird der Lesemodus des Lesers zurückgesetzt, Position, die der letzten Schreibposition entspricht, und gibt dann "Fehler" zurück. Wenn die wird nach dem Überlauf geprüft, aber vor dem nächsten Lesen mehr Daten zum Lesen als die Warteschlangenkapazität anzeigt, Überlauf ist aufgetreten. (Wenn die Warteschlange zwischen der Überprüfung verfügbarer Daten überläuft und versucht, diese Daten zu lesen, ist das einzige Anzeichen für Overflow, dass der kann nicht gelesen werden.)

Leser einer nicht synchronisierten Warteschlange möchten die Warteschlange wahrscheinlich nicht zurücksetzen. die Lese- und Schreibzeiger der Warteschlange. Wenn Sie die Warteschlange über das Deskriptorlesegeräte sollten für „resetPointers“ das Argument „false“ verwenden .

Synchronisiert

Eine synchronisierte Warteschlange hat einen Writer und einen Reader mit einem einzigen Schreibvorgang und einer Leseposition. Es ist unmöglich, mehr Daten als Die Warteschlange verfügt über mehr Daten oder hat mehr Daten gelesen, als sie derzeit enthält. Je nachdem, ob die blockierende oder nicht blockierende Schreib- oder Lesefunktion aufgerufen, wird versucht, den verfügbaren Speicherplatz zu überschreiten, oder es werden Fehler zurückgegeben. oder blockieren, bis der gewünschte Vorgang abgeschlossen ist. Versuche, dass mehr Daten gelesen oder geschrieben werden, als die Kapazität der Warteschlange erreicht, schlägt immer sofort fehl.

FMQ einrichten

Für eine Nachrichtenwarteschlange sind mehrere MessageQueue-Objekte erforderlich: eines bis in die geschrieben werden und aus denen mindestens eins gelesen werden soll. Es gibt keine explizite Konfiguration des Objekts, das zum Schreiben oder Lesen verwendet wird liegt es an der verwenden, um sicherzustellen, dass kein Objekt gleichzeitig zum Lesen und Schreiben verwendet wird, entspricht höchstens einem Schreiber und bei synchronisierten Warteschlangen maximal einen Leser.

Erstes MessageQueue-Objekt erstellen

Eine Nachrichtenwarteschlange wird in einem einzigen Aufruf erstellt und konfiguriert:

#include <fmq/MessageQueue.h>
using android::hardware::kSynchronizedReadWrite;
using android::hardware::kUnsynchronizedWrite;
using android::hardware::MQDescriptorSync;
using android::hardware::MQDescriptorUnsync;
using android::hardware::MessageQueue;
....
// For a synchronized non-blocking FMQ
mFmqSynchronized =
  new (std::nothrow) MessageQueue<uint16_t, kSynchronizedReadWrite>
      (kNumElementsInQueue);
// For an unsynchronized FMQ that supports blocking
mFmqUnsynchronizedBlocking =
  new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
      (kNumElementsInQueue, true /* enable blocking operations */);
  • MessageQueue<T, flavor>(numElements)-Initialisierer erstellt und initialisiert ein -Objekt, das die Funktionalität der Nachrichtenwarteschlange unterstützt.
  • Der MessageQueue<T, flavor>(numElements, configureEventFlagWord)-Initialisierer erstellt und initialisiert ein Objekt die die Blockierfunktion der Nachrichtenwarteschlange unterstützt.
  • flavor kann entweder kSynchronizedReadWrite für synchronisierte Warteschlange oder kUnsynchronizedWrite für eine nicht synchronisierte in die Warteschlange stellen.
  • uint16_t (in diesem Beispiel) kann eine beliebige HIDL-definierter Typ, der beinhaltet keine verschachtelten Zwischenspeicher (keine string oder vec -Typen), Handles oder Schnittstellen.
  • kNumElementsInQueue gibt die Größe der Warteschlange in Form von Einträge; bestimmt die Größe des Zwischenspeichers des gemeinsamen Arbeitsspeichers, der für die Warteschlange.

Zweites MessageQueue-Objekt erstellen

Die zweite Seite der Nachrichtenwarteschlange wird mithilfe eines MQDescriptor-Objekt, das von der ersten Seite abgerufen wird. Die Das MQDescriptor-Objekt wird über einen HIDL- oder AIDL-RPC-Aufruf an den Prozess gesendet an dem sich das zweite Ende der Nachrichtenwarteschlange befindet. Die MQDescriptor enthält unter anderem folgende Informationen zur Warteschlange:

  • Informationen zum Zuordnen des Zwischenspeichers und Schreibzeigers.
  • Informationen zum Zuordnen des Lesezeigers (wenn die Warteschlange synchronisiert ist).
  • Informationen zum Zuordnen des Ereignis-Flag-Worts (wenn die Warteschlange blockiert)
  • Objekttyp (<T, flavor>), einschließlich des HIDL-definierter Typ von und der Warteschlangen-Flavor (synchron oder nicht synchronisiert) zu definieren.

Mit dem MQDescriptor-Objekt kann ein MessageQueue-Objekt:

MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)

Der Parameter resetPointers gibt an, ob der Lesevorgang zurückgesetzt werden soll und die Positionen in 0 schreiben, während Sie dieses MessageQueue-Objekt erstellen. In einer nicht synchronisierten Warteschlange ist die Leseposition (die zu jedem MessageQueue-Objekt in nicht synchronisierten Warteschlangen) ist immer auf 0 festgelegt während der Erstellung. Normalerweise wird MQDescriptor während Erstellung des ersten Warteschlangenobjekts. Für zusätzliche Kontrolle über die gemeinsamen Arbeitsspeicher hinzufügen, kannst du den MQDescriptor manuell einrichten (MQDescriptor ist definiert in system/libhidl/base/include/hidl/MQDescriptor.h) Erstellen Sie dann jedes MessageQueue-Objekt wie in diesem Abschnitt beschrieben.

Warteschlangen und Ereignis-Flags blockieren

Standardmäßig unterstützen Warteschlangen das Blockieren von Lese-/Schreibvorgängen nicht. Es gibt zwei Arten von blockierender Lese-/Schreibaufrufe:

  • Kurzform mit drei Parametern (Datenpunkt, Anzahl der Elemente, Zeitüberschreitung). Unterstützt das Blockieren von einzelnen Lese-/Schreibvorgängen auf einem einzigen in die Warteschlange stellen. Bei Verwendung dieses Formulars verarbeitet die Warteschlange das Ereignis-Flag und die Bitmasken intern und das erste Warteschlangenobjekt muss mit dem zweiten Parameter true initialisiert werden. Hier einige Beispiele:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • Lange Version mit sechs Parametern (einschließlich Ereignis-Flag und Bitmasken). Unterstützt die Verwendung eines gemeinsamen EventFlag-Objekts für mehrere Warteschlangen und ermöglicht die Angabe der zu verwendenden Bitmasken für Benachrichtigungen. In diesem Fall Ereignis-Flag und Bitmasken müssen an jeden Lese- und Schreibaufruf übergeben werden.

Für die Langform kann das EventFlag explizit in readBlocking()- und writeBlocking()-Aufruf. Eine von Die Warteschlangen können mit einem internen Ereignis-Flag initialisiert werden, das dann extrahiert aus den MessageQueue-Objekten dieser Warteschlange mithilfe von getEventFlagWord() und zum Erstellen von EventFlag verwendet -Objekte in jedem Prozess zur Verwendung mit anderen FMQs. Die Methode EventFlag Objekte können mit beliebigen geeigneten freigegebenen Elementen initialisiert werden. zu speichern.

Im Allgemeinen sollte für jede Warteschlange nur eine nicht blockierende, kurze Blockierung von Videos im Langformat. Es ist kein Fehler, sie zu mischen, eine Programmierung erforderlich ist, um das gewünschte Ergebnis zu erzielen.

Erinnerung als schreibgeschützt markieren

Standardmäßig hat der gemeinsame Arbeitsspeicher Lese- und Schreibberechtigungen. Für nicht synchronisierte Warteschlangen (kUnsynchronizedWrite) möchten, kann der Autor die Schreibberechtigungen für alle der Leser, bevor er die MQDescriptorUnsync-Objekte ausgibt. So wird sichergestellt, dass die anderen Prozesse können nicht in die Warteschlange schreiben. Dies wird zum Schutz vor Fehlern oder unerwünschtem Verhalten was die Lesenden verarbeiten. Wenn der Autor möchte, dass die Leser die Warteschlange zurücksetzen können, sobald sie die MQDescriptorUnsync zum Erstellen der Leseseite der Warteschlange. Der Speicher kann dann nicht markiert werden. schreibgeschützt sein. Dies ist das Standardverhalten des Konstruktors "MessageQueue". Wenn es also bereits bestehenden Usern dieser Warteschlange verwendet werden, muss ihr Code geändert werden, um die Warteschlange mit resetPointer=false

  • Autor: ashmem_set_prot_region mit dem Dateideskriptor MQDescriptor aufrufen und Region, die schreibgeschützt sind (PROT_READ):
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • Reader: Erstellen Sie eine Nachrichtenwarteschlange mit resetPointer=false (die Standardeinstellung ist true):
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

MessageQueue verwenden

Die öffentliche API des MessageQueue-Objekts ist:

size_t availableToWrite()  // Space available (number of elements).
size_t availableToRead()  // Number of elements available.
size_t getQuantumSize()  // Size of type T in bytes.
size_t getQuantumCount() // Number of items of type T that fit in the FMQ.
bool isValid() // Whether the FMQ is configured correctly.
const MQDescriptor<T, flavor>* getDesc()  // Return info to send to other process.

bool write(const T* data)  // Write one T to FMQ; true if successful.
bool write(const T* data, size_t count) // Write count T's; no partial writes.

bool read(T* data);  // read one T from FMQ; true if successful.
bool read(T* data, size_t count);  // Read count T's; no partial reads.

bool writeBlocking(const T* data, size_t count, int64_t timeOutNanos = 0);
bool readBlocking(T* data, size_t count, int64_t timeOutNanos = 0);

// Allows multiple queues to share a single event flag word
std::atomic<uint32_t>* getEventFlagWord();

bool writeBlocking(const T* data, size_t count, uint32_t readNotification,
uint32_t writeNotification, int64_t timeOutNanos = 0,
android::hardware::EventFlag* evFlag = nullptr); // Blocking write operation for count Ts.

bool readBlocking(T* data, size_t count, uint32_t readNotification,
uint32_t writeNotification, int64_t timeOutNanos = 0,
android::hardware::EventFlag* evFlag = nullptr) // Blocking read operation for count Ts;

//APIs to allow zero copy read/write operations
bool beginWrite(size_t nMessages, MemTransaction* memTx) const;
bool commitWrite(size_t nMessages);
bool beginRead(size_t nMessages, MemTransaction* memTx) const;
bool commitRead(size_t nMessages);

availableToWrite() und availableToRead() können verwendet werden um zu ermitteln, wie viele Daten in einem einzigen Vorgang übertragen werden können. In einer nicht synchronisierte Warteschlange:

  • availableToWrite() gibt immer die Kapazität der Warteschlange zurück.
  • Jeder Leser hat seine eigene Leseposition und berechnet availableToRead()
  • Aus Sicht eines langsamen Lesers darf die Warteschlange überlaufen. Dies kann dazu führen, dass availableToRead() einen Wert zurückgibt, der größer ist als die Größe der Warteschlange. Der erste Lesevorgang nach einem Überlauf schlägt fehl und führt zu dass die Leseposition für diesen Leser dem aktuellen Schreibzeiger entspricht. ob der Überlauf gemeldet wurde oder nicht availableToRead()

Die Methoden read() und write() geben true, wenn alle angeforderten Daten von/zu/von dieser übertragen werden konnten (und wurden) in der Warteschlange. Diese Methoden blockieren Folgendes nicht: entweder erfolgreich true) oder eine fehlgeschlagene Rückgabe (false) sofort erfolgt.

Die Methoden readBlocking() und writeBlocking() warten bis der angeforderte Vorgang abgeschlossen werden kann oder bis das Zeitlimit (ein Der timeOutNanos-Wert 0 bedeutet, dass keine Zeitüberschreitung auftritt).

Blockierende Vorgänge werden mit einem Ereignis-Flag-Wort implementiert. Standardmäßig jede Warteschlange erstellt und verwendet ihr eigenes Flagswort, um die Kurzform readBlocking() und writeBlocking(). Es ist möglich, dass mehrere Warteschlangen für ein einzelnes Wort, damit ein Prozess auf Schreibvorgänge oder Lesevorgänge in eine der Warteschlangen. Ein Zeiger auf das Ereignis-Flag-Wort einer Warteschlange durch Aufrufen von getEventFlagWord() und diesem Zeiger (oder einem beliebigen Zeiger zu einem geeigneten Speicherort des gemeinsamen Speichers), können verwendet werden, EventFlag-Objekt, das in die Long-Form übergeben wird readBlocking() und writeBlocking() für eine andere in die Warteschlange stellen. readNotification und writeNotification bestimmen, welche Bits im Ereignis-Flag verwendet werden sollen, um Lesevorgänge und in diese Warteschlange schreibt. readNotification und writeNotification sind 32-Bit-Bitmasken.

readBlocking() wartet auf die writeNotification-Bits. Wenn dieser Parameter 0 ist, schlägt der Aufruf immer fehl. Wenn die readNotification den Wert 0 hat, schlägt der Aufruf nicht fehl, aber ein Durch erfolgreiches Lesen werden keine Benachrichtigungsbits gesetzt. In einer synchronisierten Warteschlange würde der entsprechende writeBlocking()-Aufruf wird nie aktiviert, es sei denn, das Bit ist an anderer Stelle gesetzt. In einer nicht synchronisierten Warteschlange writeBlocking() wartet nicht (sollte trotzdem verwendet werden, um Schreiben von Benachrichtigungsbit) und es eignet sich für Lesevorgänge, um keine Benachrichtigungs-Bits. Gleichermaßen schlägt writeblocking() fehl, wenn readNotification ist 0 und ein erfolgreicher Schreibvorgang legt die angegebene writeNotification Bit.

Wenn Sie auf mehrere Warteschlangen gleichzeitig warten möchten, verwenden Sie die Methode des EventFlag-Objekts wait()-Methode, um auf eine Bitmaske mit Benachrichtigungen zu warten. Die Die Methode wait() gibt ein Statuswort mit den Bits zurück, die den zum Aufwachen eingerichtet sein. Anhand dieser Informationen wird dann geprüft, ob die entsprechende Warteschlange genügend Speicherplatz oder Daten für den gewünschten Schreib-/Lesevorgang write()/read() nicht blockieren. So rufen Sie einen Vorgang zum Hochladen ab: Benachrichtigung: Anderen Anruf von EventFlag verwenden wake()-Methode. Für eine Definition von EventFlag Abstraktion, beziehen sich system/libfmq/include/fmq/EventFlag.h.

Null-Kopiervorgänge

Die read/write/readBlocking/writeBlocking() APIs nehmen einen Zeiger auf einen Eingabe-/Ausgabepuffer als Argument und verwenden memcpy() ruft intern auf, Daten zwischen der gleichen und der FMQ-Ringzwischenspeicher. Zur Verbesserung der Leistung enthalten Android 8.0 und höher eine Reihe von APIs, die direkten Zeigerzugriff auf den Ringzwischenspeicher ermöglichen, memcpy-Aufrufe erforderlich.

Verwenden Sie die folgenden öffentlichen APIs für FMQ-Zero-Copy-Vorgänge:

bool beginWrite(size_t nMessages, MemTransaction* memTx) const;
bool commitWrite(size_t nMessages);

bool beginRead(size_t nMessages, MemTransaction* memTx) const;
bool commitRead(size_t nMessages);
  • Die Methode beginWrite stellt Basiszeiger für den FMQ-Ring bereit Puffer. Nachdem die Daten geschrieben wurden, führen Sie mit commitWrite() ein Commit durch. Die Methoden beginRead/commitRead funktionieren auf die gleiche Weise.
  • Die Methoden beginRead/Write übernehmen als Eingabe Anzahl der zu lesenden bzw. zu schreibenden Nachrichten und gibt einen booleschen Wert zurück, der angibt, Lesen/Schreiben ist möglich. Wenn Lese- oder Schreibzugriff möglich ist, ist der memTx Struktur wird mit Basiszeigern gefüllt, die für direkte Zeiger verwendet werden können. Zugriff auf den gemeinsamen Ringzwischenspeicher.
  • Die Struktur MemRegion enthält Details zu einem Speicherblock. einschließlich des Basiszeigers (Basisadresse des Speicherblocks) und der Länge von T (Länge des Speicherblocks in Bezug auf das HIDL-definierte Typ der Nachrichtenwarteschlange).
  • Die Struktur MemTransaction enthält zwei MemRegion-Elemente Structs, first und second als Lese- oder Schreibvorgang in Der Ringpuffer erfordert unter Umständen einen Wrapping am Anfang der Warteschlange. Dieses würde bedeuten, dass zwei Basiszeiger erforderlich sind, um Daten in den FMQ zu lesen/zu schreiben, Ringpuffer.

So rufen Sie die Basisadresse und die Länge aus einer MemRegion-Struktur ab:

T* getAddress(); // gets the base address
size_t getLength(); // gets the length of the memory region in terms of T
size_t getLengthInBytes(); // gets the length of the memory region in bytes

Zum Abrufen von Verweisen auf die ersten und zweiten MemRegions innerhalb eines MemTransaction-Objekt:

const MemRegion& getFirstRegion(); // get a reference to the first MemRegion
const MemRegion& getSecondRegion(); // get a reference to the second MemRegion

Beispiel für einen Schreibvorgang in FMQ mit Zero-Copy-APIs:

MessageQueueSync::MemTransaction tx;
if (mQueue->beginRead(dataLen, &tx)) {
    auto first = tx.getFirstRegion();
    auto second = tx.getSecondRegion();

    foo(first.getAddress(), first.getLength()); // method that performs the data write
    foo(second.getAddress(), second.getLength()); // method that performs the data write

    if(commitWrite(dataLen) == false) {
       // report error
    }
} else {
   // report error
}

Die folgenden Hilfsmethoden sind ebenfalls Teil von MemTransaction:

  • T* getSlot(size_t idx);
    Gibt einen Zeiger auf Slot idx innerhalb der MemRegions, die zu diesem MemTransaction gehören -Objekt enthält. Wenn das Objekt MemTransaction den Speicher darstellt Regionen zum Lesen/Schreiben von n Elementen des Typs T verwendet, idx liegt zwischen 0 und N-1.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    nMessages Elemente des Typs T in die Speicherregionen schreiben vom -Objekt beschrieben, ausgehend vom Index startIdx. Diese Methode verwendet memcpy() und ist nicht für eine Null-Kopie-Funktion gedacht . Wenn das Objekt MemTransaction den Arbeitsspeicher N Elemente des Typs T lesen/schreiben, dann ist der gültige Bereich von idx zwischen 0 und N-1 liegen.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Hilfsmethode zum Lesen von nMessages Elementen des Typs T aus dem vom Objekt beschriebene Speicherbereiche ab startIdx. Dieses -Methode verwendet memcpy() und ist nicht für eine Nullkopie gedacht .

Warteschlange über HIDL senden

Beim Erstellen:

  1. Erstellen Sie das Nachrichtenwarteschlangenobjekt wie oben beschrieben.
  2. Prüfen Sie mit isValid(), ob das Objekt gültig ist.
  3. Wenn Sie auf mehrere Warteschlangen warten, indem Sie eine EventFlag in die Langform von readBlocking()/writeBlocking(), können Sie extrahieren: Event-Flag-Zeiger (mithilfe von getEventFlagWord()) von einem MessageQueue-Objekt, das zum Erstellen des Flags initialisiert wurde, und verwenden Sie dieses Flag, um das erforderliche EventFlag-Objekt zu erstellen.
  4. Verwenden Sie die MessageQueue-Methode getDesc(), um eine Deskriptorobjekt.
  5. Geben Sie der Methode in der Datei .hal einen Parameter des Typs fmq_sync oder fmq_unsync, wobei T ein geeigneten HIDL-definierten Typ. Verwenden Sie dies, um das Objekt zu senden, das von getDesc() an den Empfangsprozess.

Auf der Empfängerseite:

  1. Verwenden Sie das Deskriptorobjekt, um ein MessageQueue-Objekt zu erstellen. Seien denselben Warteschlangen-Flavor und Datentyp verwenden, da die Vorlage sonst kompilieren.
  2. Wenn Sie ein Ereignis-Flag extrahiert haben, extrahieren Sie das Flag aus der entsprechenden MessageQueue-Objekt im Empfangsprozess.
  3. Verwenden Sie das Objekt MessageQueue, um Daten zu übertragen.