Coda di messaggi rapida (FMQ)

Se cerchi il supporto AIDL, vedi anche FMQ con AIDL.

L'infrastruttura di chiamata di procedura remota (RPC) di HIDL utilizza i meccanismi Binder, vale a dire che le chiamate comportano l'overhead, richiedono operazioni del kernel e possono scheduler. Tuttavia, nei casi in cui i dati devono essere trasferiti tra con meno overhead e senza coinvolgimento del kernel, la Fast Message Queue (FMQ).

FMQ crea code di messaggi con le proprietà desiderate. Un L'oggetto MQDescriptorSync o MQDescriptorUnsync può essere inviato tramite una chiamata RPC HIDL e utilizzato dal processo di ricezione per accedere nella coda di messaggi.

Le code di messaggi rapide sono supportate solo in C++ e sui dispositivi con Android 8.0 o versioni successive.

Tipi di code di messaggi

Android supporta due tipi di coda (noti come flavor):

  • Le code non sincronizzate possono eseguire l'overflow e possono avere molti lettori; ogni lettore deve leggere i dati in tempo o perderli.
  • Le code sincronizzate non possono essere soggette a overflow e possono avere solo un lettore.

Entrambi i tipi di coda non possono avere un flusso insufficiente (lettura da una coda vuota) non riesce) e può avere un solo writer.

Non sincronizzato

Una coda non sincronizzata ha un solo writer, ma può avere un numero qualsiasi di lettori. È presente una posizione di scrittura per la coda; ma ciascun lettore mantiene della propria posizione di lettura indipendente.

Le scritture nella coda hanno sempre esito positivo (non sono selezionate per l'overflow) purché non superano la capacità di coda configurata (scritture superiori alla la capacità della coda non riesce immediatamente). Ogni lettore potrebbe avere letture diverse piuttosto che attendere che ogni lettore legga ogni dato, possono uscire dalla coda ogni volta che nuove scritture hanno bisogno di spazio.

I lettori sono responsabili del recupero dei dati prima che non superino in coda. Una lettura che tenta di leggere più dati di quelli disponibili non funziona immediatamente (se non blocca) o attende che siano disponibili dati sufficienti (se blocco). Una lettura che tenta di leggere più dati rispetto a sempre la capacità della coda non riesce immediatamente.

Se un lettore non riesce a stare al passo con chi scrive, in modo che la quantità scritto e non ancora letto da quel lettore è maggiore della capacità di coda, alla lettura successiva non restituisce dati; ma reimposta la lettura in modo che corrisponda all'ultima posizione di scrittura, poi restituisce un errore. Se i dati disponibili per la lettura vengono controllati dopo l'overflow ma prima della lettura successiva, mostra più dati disponibili da leggere rispetto alla capacità della coda, il che indica si è verificato un overflow. (Se la coda supera il limite tra il controllo dei dati disponibili e tentare di leggere i dati, l'unica indicazione di overflow è che non riesce a leggere.)

È probabile che i lettori di una coda non sincronizzata non vogliano reimpostarla i puntatori di lettura e scrittura della coda. Quindi, quando crei la coda I lettori di descrittori devono utilizzare un argomento "false" per il parametro "resetPointers" .

Sincronizzata

Una coda sincronizzata ha un scrittore e un lettore con un'unica scrittura e una singola posizione di lettura. È impossibile scrivere più dati di la coda ha spazio per o ha letto più dati rispetto a quelli attualmente contenuti nella coda. A seconda che la funzione di scrittura o lettura di blocco o non blocco sia chiamata, i tentativi di superare lo spazio disponibile o i dati restituiscono un errore immediatamente o bloccare fino al completamento dell'operazione desiderata. Tenta di in lettura o scrittura più dati rispetto alla capacità della coda viene sempre interrotta immediatamente.

Configura un FMQ

Una coda di messaggi richiede più oggetti MessageQueue: uno per e uno o più da cui leggere. Non sono presenti contenuti configurazione di quale oggetto viene utilizzato per la scrittura o la lettura; dipende per assicurarsi che nessun oggetto venga utilizzato sia per la lettura che per la scrittura e che è al massimo un writer e, per le code sincronizzate, c'è al massimo un Reader.

Crea il primo oggetto MessageQueue

Viene creata e configurata una coda di messaggi con una singola chiamata:

#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 */);
  • L'inizializzazione di MessageQueue<T, flavor>(numElements) crea e inizializza un oggetto che supporta la funzionalità di coda di messaggi.
  • L'inizializzatore MessageQueue<T, flavor>(numElements, configureEventFlagWord) crea e inizializza un oggetto che supporta la funzionalità delle code di messaggi con il blocco.
  • flavor può essere kSynchronizedReadWrite per un o kUnsynchronizedWrite per una coda non sincronizzata in coda.
  • uint16_t (in questo esempio) può essere qualsiasi tipo definito HIDL che non prevede buffer nidificati (non string o vec tipi di configurazione), handle o interfacce.
  • kNumElementsInQueue indica le dimensioni della coda nel numero di voci; determina la dimensione del buffer di memoria condivisa allocato per la coda.

Crea il secondo oggetto MessageQueue

Il secondo lato della coda di messaggi viene creato utilizzando Oggetto MQDescriptor ottenuto dal primo lato. La L'oggetto MQDescriptor viene inviato tramite una chiamata RPC HIDL o AIDL al processo che contiene la seconda estremità della coda di messaggi. La MQDescriptor contiene informazioni sulla coda, tra cui:

  • Informazioni per mappare il buffer e scrivere il puntatore.
  • Informazioni per mappare il puntatore di lettura (se la coda è sincronizzata).
  • Informazioni per mappare la parola relativa alla segnalazione dell'evento (se la coda si blocca).
  • Tipo di oggetto (<T, flavor>), che include Tipo definito dall'HIDL di gli elementi della coda e il tipo di coda (sincronizzato o non sincronizzato).

L'oggetto MQDescriptor può essere utilizzato per creare un Oggetto MessageQueue:

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

Il parametro resetPointers indica se reimpostare la lettura e scrivere le posizioni su 0 durante la creazione di questo oggetto MessageQueue. In una coda non sincronizzata, viene indicata la posizione di lettura (locale MessageQueue oggetto nelle code non sincronizzate) è sempre impostato su 0 durante la creazione. In genere, MQDescriptor viene inizializzato durante creazione del primo oggetto coda di messaggi. Per un maggiore controllo sui contenuti condivisi memoria, puoi configurare MQDescriptor manualmente (MQDescriptor è definito in system/libhidl/base/include/hidl/MQDescriptor.h) quindi crea ogni oggetto MessageQueue come descritto in questa sezione.

Blocca code e segnalazioni di eventi

Per impostazione predefinita, le code non supportano il blocco delle letture/scritture. Esistono due tipi di di bloccare le chiamate di lettura/scrittura:

  • Formato breve, con tre parametri (puntatore dei dati, numero di elementi, timeout). Supporta il blocco di singole operazioni di lettura/scrittura su un singolo in coda. Quando utilizzi questo modulo, la coda gestisce il flag dell'evento e le maschere di bit internamente e il primo oggetto coda di messaggi deve essere inizializzato con un secondo parametro di true. Ad esempio:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • Formato esteso, con sei parametri (inclusi flag di evento e maschere di bit). È supportato l'utilizzo di un oggetto EventFlag condiviso tra più code e consente di specificare le maschere di bit di notifica da utilizzare. In questo caso, e le maschere di bit devono essere forniti a ogni chiamata di lettura e scrittura.

Per il formato lungo, EventFlag può essere fornito esplicitamente in ogni chiamata readBlocking() e writeBlocking(). Uno di le code possono essere inizializzate con un flag di evento interno, che deve quindi essere estratti dagli oggetti MessageQueue della coda utilizzando getEventFlagWord() e utilizzati per creare EventFlag in ogni processo per l'utilizzo con altri FMQ. In alternativa, EventFlag oggetti possono essere inizializzati con qualsiasi file condiviso adatto la memoria.

In generale, ogni coda deve utilizzare una sola coda non bloccante o il blocco del formato lungo. Non è un errore mescolarli, ma fai attenzione è necessaria la programmazione per ottenere il risultato desiderato.

Contrassegna il ricordo come di sola lettura

Per impostazione predefinita, la memoria condivisa ha autorizzazioni di lettura e scrittura. Per non sincronizzati code (kUnsynchronizedWrite), l'autore potrebbe voler rimuovere le autorizzazioni di scrittura per dei lettori prima di distribuire gli oggetti MQDescriptorUnsync. In questo modo ci assicuriamo che processi non possono scrivere nella coda, metodo consigliato per evitare bug o comportamenti dannosi i processi del lettore. Se l'autore vuole che i lettori possano reimpostare la coda ogni volta che usano il MQDescriptorUnsync per creare il lato di lettura della coda, quindi non è possibile contrassegnare la memoria di sola lettura. Questo è il comportamento predefinito del costruttore "MessageQueue". Quindi, se c'è già utenti esistenti di questa coda, il loro codice deve essere modificato per costruire la coda resetPointer=false.

  • Writer: chiama ashmem_set_prot_region con un descrittore del file MQDescriptor e regione impostate su sola lettura (PROT_READ):
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • Lettore: crea una coda di messaggi con resetPointer=false (il il valore predefinito è true):
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

Utilizzare la coda dei messaggi

L'API pubblica dell'oggetto MessageQueue è:

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);

È possibile utilizzare availableToWrite() e availableToRead() per determinare quanti dati possono essere trasferiti in una singola operazione. In un coda non sincronizzata:

  • availableToWrite() restituisce sempre la capacità della coda.
  • Ogni lettore ha la propria posizione di lettura ed esegue il proprio calcolo availableToRead().
  • Dal punto di vista di un lettore lento, la coda può overflow; Ciò potrebbe comportare la restituzione di un valore maggiore di availableToRead() la dimensione della coda. La prima lettura dopo un overflow non riesce e genera la posizione di lettura per quel lettore impostata è uguale al puntatore di scrittura corrente, se l'overflow è stato segnalato o meno tramite availableToRead().

I metodi read() e write() restituiscono true se è stato possibile trasferire (ed essere) tutti i dati richiesti in coda. Questi metodi non bloccano, hanno successo (e restituiscono true) o restituire un errore (false) immediatamente.

I metodi readBlocking() e writeBlocking() attendono fino al completamento dell'operazione richiesta o fino al timeout (una Il valore 0 di timeOutNanos indica che non scade mai).

Le operazioni di blocco vengono implementate utilizzando una parola con flag evento. Per impostazione predefinita, ciascuna coda crea e utilizza la propria parola chiave a supporto della forma breve di readBlocking() e writeBlocking(). È possibile che più code per condividere una singola parola, in modo che un processo possa attendere scritture legge in qualsiasi coda. Un puntatore alla parola relativa a un flag evento di una coda può essere ottenuto chiamando getEventFlagWord() e tale puntatore (o qualsiasi un puntatore a una posizione di memoria condivisa adatta) può essere utilizzato per creare EventFlag oggetto da passare nella forma lunga readBlocking() e writeBlocking()per un altro in coda. readNotification e writeNotification indicano quali bit nel flag evento devono essere utilizzati per segnalare le letture e scrive in quella coda. readNotification e writeNotification sono maschere di bit a 32 bit.

readBlocking() attende i writeNotification bit; se questo parametro è 0, la chiamata non va sempre a buon fine. Se Il valore di readNotification è 0, la chiamata non riesce, ma la lettura riuscita non imposterà alcun bit di notifica. In una coda sincronizzata, significa che la chiamata writeBlocking() corrispondente non si sveglia mai a meno che il bit non sia impostato altrove. In una coda non sincronizzata, writeBlocking() non attende (dovrebbe comunque essere utilizzato per impostare il scrivere il bit di notifica) ed è opportuno che le letture non impostino bit di notifica. Analogamente, writeblocking() non riesce se readNotification è 0 e una scrittura riuscita imposta il valore specificato writeNotification bit.

Per attendere più code contemporaneamente, utilizza un oggetto EventFlag wait() metodo di attesa per una maschera di bit delle notifiche. La Il metodo wait() restituisce una parola di stato con i bit che hanno generato sveglia impostata. Queste informazioni vengono quindi utilizzate per verificare che la coda corrispondente sia stata abbastanza spazio o dati per l'operazione di scrittura/lettura desiderata ed eseguire write()/read() non bloccanti. Per ottenere un'operazione di pubblicazione notifica, usa un'altra chiamata al EventFlag wake(). Per una definizione di EventFlag dell'astrazione, fare riferimento system/libfmq/include/fmq/EventFlag.h.

Operazioni di copia pari a zero

La read/write/readBlocking/writeBlocking() Le API prendono come argomento un puntatore a un buffer di input/output e usano memcpy() chiama internamente per copiare i dati tra la stessa e la Buffer ad anello FMQ. Per migliorare le prestazioni, Android 8.0 e versioni successive includono un insieme di API che forniscono accesso diretto al puntatore nel buffer ad anello, eliminando è necessario utilizzare le chiamate memcpy.

Usa le seguenti API pubbliche per le operazioni FMQ che non richiedono la copia:

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);
  • Il metodo beginWrite fornisce puntatori di base nell'anello FMQ buffer. Dopo aver scritto i dati, esegui il commit dei dati utilizzando commitWrite(). I metodi beginRead/commitRead funzionano allo stesso modo.
  • I metodi beginRead/Write assumono come input numero di messaggi da leggere/scrivere e restituisce un valore booleano che indica se di lettura/scrittura. Se è possibile eseguire la lettura o la scrittura, memTx lo struct viene compilato con puntatori di base che possono essere usati per il puntatore diretto nella memoria condivisa del buffer ad anello.
  • Lo struct MemRegion contiene dettagli su un blocco di memoria, compreso il puntatore di base (indirizzo di base del blocco di memoria) e la lunghezza termini di T (lunghezza del blocco di memoria in termini di parametri tipo di coda di messaggi).
  • Lo struct MemTransaction contiene due MemRegion struct, first e second come lettura o scrittura il buffer ad anello potrebbe richiedere un wrap intorno all'inizio della coda. Questo significherebbe che sono necessari due puntatori di base per leggere/scrivere dati in FMQ ad anello.

Per ottenere l'indirizzo di base e la lunghezza da uno struct MemRegion:

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

Per ottenere riferimenti al primo e al secondo MemRegion all'interno di un Oggetto MemTransaction:

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

Esempio di scrittura in FMQ utilizzando API zero-copy:

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
}

Anche i seguenti metodi helper fanno parte di MemTransaction:

  • T* getSlot(size_t idx);
    Restituisci un puntatore all'area idx all'interno di MemRegions che fanno parte di MemTransaction . Se l'oggetto MemTransaction rappresenta la memoria regioni per leggere/scrivere N elementi di tipo T, l'intervallo valido idx è compreso tra 0 e N-1.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    Scrivi nMessages elementi di tipo T nelle regioni di memoria descritto dall'oggetto, a partire dall'indice startIdx. Questo metodo usa memcpy() e non è da utilizzare per una copia zero operativa. Se l'oggetto MemTransaction rappresenta la memoria a lettura/scrittura di N elementi di tipo T, l'intervallo valido di idx è tra 0 e N-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Metodo di supporto per leggere nMessages elementi di tipo T dal regioni di memoria descritte dall'oggetto a partire da startIdx. Questo utilizza memcpy() e non è destinato a essere utilizzato per una copia zero operativa.

Invia la coda tramite HIDL

Per quanto riguarda la creazione:

  1. Crea l'oggetto coda di messaggi come descritto sopra.
  2. Verifica che l'oggetto sia valido con isValid().
  3. Se sei in attesa in più code trasmettendo un EventFlag nel formato lungo di readBlocking()/writeBlocking(), puoi estrarre puntatore flag evento (utilizzando getEventFlagWord()) da un MessageQueue oggetto inizializzato per creare il flag e utilizza questo flag per creare l'oggetto EventFlag necessario.
  4. Usa il metodo getDesc() MessageQueue per ottenere un encoder-decoder.
  5. Nel file .hal, fornisci al metodo un parametro di tipo fmq_sync o fmq_unsync dove T è un un tipo definito HIDL idoneo. Utilizzalo per inviare l'oggetto restituito getDesc() al processo di ricezione.

Sul lato ricevente:

  1. Utilizza l'oggetto descrittore per creare un oggetto MessageQueue. Essere di utilizzare la stessa versione di coda e lo stesso tipo di dati, altrimenti il modello non riesce di addestramento tramite il metodo compile.
  2. Se hai estratto un flag di evento, estrai il flag dal corrispondente MessageQueue oggetto nel processo di ricezione.
  3. Utilizza l'oggetto MessageQueue per trasferire i dati.