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ò esserekSynchronizedReadWrite
per un okUnsynchronizedWrite
per una coda non sincronizzata in coda.uint16_t
(in questo esempio) può essere qualsiasi tipo definito HIDL che non prevede buffer nidificati (nonstring
ovec
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 idoneo
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 fileMQDescriptor
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 tramiteavailableToRead()
.
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 utilizzandocommitWrite()
. I metodibeginRead
/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 diT
(lunghezza del blocco di memoria in termini di parametri tipo di coda di messaggi). - Lo struct
MemTransaction
contiene dueMemRegion
struct,first
esecond
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'areaidx
all'interno diMemRegions
che fanno parte diMemTransaction
. Se l'oggettoMemTransaction
rappresenta la memoria regioni per leggere/scrivere N elementi di tipo T, l'intervallo validoidx
è compreso tra 0 e N-1.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
ScrivinMessages
elementi di tipo T nelle regioni di memoria descritto dall'oggetto, a partire dall'indicestartIdx
. Questo metodo usamemcpy()
e non è da utilizzare per una copia zero operativa. Se l'oggettoMemTransaction
rappresenta la memoria a lettura/scrittura di N elementi di tipo T, l'intervallo valido diidx
è tra 0 e N-1.bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Metodo di supporto per leggerenMessages
elementi di tipo T dal regioni di memoria descritte dall'oggetto a partire dastartIdx
. Questo utilizzamemcpy()
e non è destinato a essere utilizzato per una copia zero operativa.
Invia la coda tramite HIDL
Per quanto riguarda la creazione:
- Crea l'oggetto coda di messaggi come descritto sopra.
- Verifica che l'oggetto sia valido con
isValid()
. - Se sei in attesa in più code trasmettendo un
EventFlag
nel formato lungo direadBlocking()
/writeBlocking()
, puoi estrarre puntatore flag evento (utilizzandogetEventFlagWord()
) da unMessageQueue
oggetto inizializzato per creare il flag e usa questo flag per creare l'oggettoEventFlag
necessario. - Usa il metodo
getDesc()
MessageQueue
per ottenere un encoder-decoder. - Nel file
.hal
, fornisci al metodo un parametro di tipofmq_sync
ofmq_unsync
doveT
è un un tipo definito HIDL idoneo. Utilizzalo per inviare l'oggetto restituitogetDesc()
al processo di ricezione.
Sul lato ricevente:
- 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. - Se hai estratto un flag di evento, estrai il flag dal corrispondente
MessageQueue
oggetto nel processo di ricezione. - Utilizza l'oggetto
MessageQueue
per trasferire i dati.