Si vous avez besoin d'aide pour AIDL, consultez également FMQ avec AIDL :
L'infrastructure d'appel de procédure à distance (RPC) de HIDL utilise des mécanismes de liaison, Cela signifie que les appels impliquent des frais généraux, nécessitent des opérations de noyau et peuvent déclencher l'action du planificateur. Toutefois, pour les cas où les données doivent être transférées avec moins de frais généraux et sans implication du noyau, le service Fast Message Queue (FMQ) est utilisé.
FMQ crée des files d'attente de messages avec les propriétés souhaitées. Une
L'objet MQDescriptorSync
ou MQDescriptorUnsync
peut être
envoyé via un appel RPC HIDL et utilisé par le processus de réception pour accéder au
à la file d'attente de messages.
Les files d'attente de messages rapides ne sont compatibles qu'avec C++ et sur les appareils. équipés d'Android 8.0 ou version ultérieure.
Types MessageQueue
Android accepte deux types de files d'attente (appelés flavors):
- Les files d'attente désynchronisées sont autorisées à déborder et peuvent comporter de nombreuses lecteurs ; chaque lecteur doit lire les données à temps ou les perdre.
- Les files d'attente synchronisées ne sont pas autorisées à déborder et ne peuvent contenir que un lecteur.
Le dépassement de capacité négatif n'est pas autorisé pour les deux types de files d'attente (lecture à partir d'une file d'attente vide) échoue) et ne peut avoir qu'un seul écrivain.
Désynchronisé
Une file d'attente désynchronisée n'a qu'un seul rédacteur, mais peut avoir un nombre illimité de lecteurs. Il y a une position d'écriture pour la file d'attente ; mais chaque lecteur garde de sa propre position de lecture indépendante.
Les écritures dans la file d'attente réussissent toujours (sans vérification de dépassement) tant que elles ne dépassent pas la capacité de la file d'attente configurée (les écritures sont supérieures au la capacité de la file d'attente échoue immédiatement). Comme chaque lecteur peut avoir une lecture différente au lieu d'attendre que chaque lecteur lise toutes les données, peut tomber de la file d'attente chaque fois que les nouvelles écritures nécessitent de l'espace.
Les lecteurs sont chargés de récupérer les données avant qu'elles ne finissent de dans la file d'attente. Une lecture qui tente de lire plus de données que disponible échoue immédiatement (s'il s'agit d'une règle non bloquante) ou attend qu'une quantité suffisante de données soit disponible (si le blocage). Lecture qui tente toujours de lire plus de données que la capacité de la file d'attente échoue immédiatement.
Si un lecteur ne parvient pas à suivre le rythme de l'auteur, de sorte que la quantité de données écrit et pas encore lu par le lecteur dépasse la capacité de la file d'attente, la lecture suivante ne renvoie pas de données ; au lieu de cela, il réinitialise le temps de lecture du lecteur pour qu'elle soit égale à la dernière position d'écriture, puis renvoie l'échec. Si le les données disponibles en lecture sont vérifiées après un dépassement, mais avant la lecture suivante, elles affiche plus de données disponibles en lecture que la capacité de la file d'attente, ce qui indique un débordement s'est produit. (Si la file d'attente déborde entre la vérification des données disponibles et que vous tentez de lire ces données, la seule indication de dépassement est que échec de lecture.)
Les lecteurs d'une file d'attente désynchronisée ne veulent probablement pas réinitialiser les pointeurs de lecture et d'écriture de la file d'attente. Lors de la création de la file d'attente les lecteurs de descripteurs doivent utiliser un argument "false" pour "resetPointers" .
Synchronisé
Une file d'attente synchronisée contient un rédacteur et un lecteur avec une seule écriture et une seule position de lecture. Il est impossible d'écrire plus de données que la file d'attente peut lire plus de données que ce qu'elle contient actuellement. Selon que la fonction d'écriture ou de lecture bloquante ou non bloquante est est appelé, les tentatives de dépassement de l'espace disponible ou les données renvoient un échec immédiatement ou le bloquer jusqu'à ce que l'opération souhaitée puisse être effectuée. Tentatives de lire ou écrire plus de données que la capacité de la file d'attente échoue toujours immédiatement.
Configurer un FMQ
Une file d'attente de messages nécessite plusieurs objets MessageQueue
: un à
et un ou plusieurs éléments à lire. Il n'y a pas de texte
la configuration de l'objet utilisé pour l'écriture ou la lecture ; c'est à vous
pour s'assurer qu'aucun objet n'est utilisé à la fois pour la lecture et l'écriture, qu'il existe
au maximum un écrivain et, pour les files d'attente synchronisées, il n'y a pas plus d'un
en lecture seule.
Créer le premier objet MessageQueue
Une file d'attente de messages est créée et configurée avec un seul appel:
#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'initialiseur
MessageQueue<T, flavor>(numElements)
crée et initialise un objet compatible avec la fonctionnalité de file d'attente de messages. - L'initialiseur
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
crée et initialise un objet. qui prend en charge la fonctionnalité de file d'attente de messages avec le blocage. flavor
peut êtrekSynchronizedReadWrite
pour oukUnsynchronizedWrite
pour une file d'attente non synchronisée, file d'attente.uint16_t
(dans cet exemple) peut être n'importe quelle Type HIDL défini qui n'implique pas de tampons imbriqués (nistring
, nivec
) types), de poignées ou d'interfaces.kNumElementsInQueue
indique la taille de la file d'attente en nombre de les entrées ; il détermine la taille du tampon de mémoire partagée allouée pour la file d'attente.
Créer le deuxième objet MessageQueue
Le second côté de la file d'attente de messages est créé à l'aide d'un
Objet MQDescriptor
obtenu du premier côté. La
L'objet MQDescriptor
est envoyé via un appel RPC HIDL ou AIDL au processus
qui contient la seconde extrémité
de la file d'attente des messages. La
MQDescriptor
contient des informations sur la file d'attente, y compris:
- Informations permettant de mapper le pointeur de tampon et d'écriture.
- Informations pour mapper le pointeur de lecture (si la file d'attente est synchronisée).
- Informations permettant de mapper l'indicateur d'événement (si la file d'attente est bloquante).
- Type d'objet (
<T, flavor>
), qui inclut le Type défini par HIDL de et le type de file d'attente (synchronisé ou non synchronisé).
L'objet MQDescriptor
permet de construire
Objet MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
Le paramètre resetPointers
indique s'il faut réinitialiser la lecture
et écrivez les positions sur 0 lors de la création de cet objet MessageQueue
.
Dans une file d'attente désynchronisée, la position de lecture (qui est locale pour chaque
MessageQueue
dans les files d'attente non synchronisées) est toujours défini sur 0.
lors de la création. En règle générale, MQDescriptor
est initialisé pendant
la création du premier objet de file d'attente de messages. Pour un contrôle accru sur les
mémoire, vous pouvez configurer le MQDescriptor
manuellement
(MQDescriptor
est défini dans
system/libhidl/base/include/hidl/MQDescriptor.h
).
Ensuite, créez chaque objet MessageQueue
comme décrit dans cette section.
Files d'attente de blocage et indicateurs d'événement
Par défaut, les files d'attente ne prennent pas en charge les lectures/écritures bloquantes. Il existe deux types des appels bloqués en lecture/écriture:
- Format court, avec trois paramètres (pointeur de données, nombre d'éléments,
le délai avant expiration). Permet le blocage d'opérations de lecture/écriture individuelles sur un seul
file d'attente. Lorsque vous utilisez ce formulaire, la file d'attente gère l'indicateur d'événement et les masques de bits.
en interne. Le premier objet de file d'attente de messages doit
être initialisé avec un deuxième paramètre de
true
. Par exemple:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Version longue, avec six paramètres (indicateur d'événement et masques de bits inclus).
Prise en charge de l'utilisation d'un objet
EventFlag
partagé entre plusieurs files d'attente et permet de spécifier les masques de bits de notification à utiliser. Dans ce cas, Un indicateur d'événement et des masques de bits doivent être fournis à chaque appel de lecture et d'écriture.
Pour le format long, le EventFlag
peut être fourni explicitement dans
chaque appel readBlocking()
et writeBlocking()
. L'une des valeurs suivantes :
les files d'attente peuvent être initialisées avec un indicateur d'événement interne, qui doit ensuite être
extraites des objets MessageQueue
de cette file d'attente à l'aide de
getEventFlagWord()
et utilisé pour créer EventFlag
objets dans chaque processus afin de
les utiliser avec d'autres FMQ. Le champ
Les objets EventFlag
peuvent être initialisés avec n'importe quel élément partagé approprié.
mémoire.
En général, chaque file d'attente ne doit utiliser qu'un seul type de vidéos longues ou de vidéos longues. Ce n'est pas une erreur, mais faites attention la programmation est nécessaire pour obtenir le résultat souhaité.
Marquer le souvenir en lecture seule
Par défaut, la mémoire partagée dispose d'autorisations de lecture et d'écriture. Pour les appareils désynchronisés
files d'attente (kUnsynchronizedWrite
), le rédacteur peut supprimer les autorisations d'écriture pour toutes
des lecteurs avant de distribuer les objets MQDescriptorUnsync
. Cela garantit que l'autre
les processus ne peuvent pas écrire dans la file d'attente, ce qui est recommandé pour se protéger contre les bugs ou les comportements insatisfaisants
les processus
du lecteur.
Si le rédacteur souhaite que les lecteurs puissent réinitialiser la file d'attente à chaque fois qu'ils utilisent la commande
MQDescriptorUnsync
pour créer le côté lecture de la file d'attente, la mémoire ne peut alors pas être marquée
en lecture seule. Il s'agit du comportement par défaut du constructeur "MessageQueue". Donc, s’il y a déjà
aux utilisateurs existants de cette file d'attente, leur code doit être modifié pour construire la file d'attente avec
resetPointer=false
- Rédacteur: appelez
ashmem_set_prot_region
avec un descripteur de fichierMQDescriptor
. et la région définies en lecture seule (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Lecteur: crée une file d'attente de messages avec
resetPointer=false
(le la valeur par défaut esttrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Utiliser MessageQueue
L'API publique de l'objet MessageQueue
est la suivante:
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);
Vous pouvez utiliser availableToWrite()
et availableToRead()
pour déterminer la quantité de données pouvant
être transférées en une seule opération. Dans un
file d'attente désynchronisée:
availableToWrite()
renvoie toujours la capacité de la file d'attente.- Chaque lecteur a sa propre position de lecture et effectue son propre calcul pour
availableToRead()
- Du point de vue d'un lecteur lent, la file d'attente peut déborder ;
availableToRead()
peut donc renvoyer une valeur supérieure à la taille de la file d'attente. La première lecture après un débordement échoue et aboutit la position de lecture de ce lecteur étant définie sur le pointeur d'écriture actuel, si le débordement a été signalé ou non viaavailableToRead()
Les méthodes read()
et write()
renvoient
true
si toutes les données demandées peuvent être (et ont été) transférées vers/depuis
dans la file d'attente. Ces méthodes ne bloquent pas : soit elles réussissent (et renvoient
true
) ou échouer immédiatement (false
).
Les méthodes readBlocking()
et writeBlocking()
attendent
jusqu'à ce que l'opération demandée puisse être terminée, ou jusqu'à ce qu'elles expirent (une
la valeur 0 pour timeOutNanos
signifie qu'il n'y a jamais de délai avant expiration).
Les opérations de blocage sont implémentées à l'aide d'un indicateur d'événement. Par défaut,
chaque file d'attente crée et utilise son propre indicateur pour prendre en charge la forme abrégée de
readBlocking()
et writeBlocking()
. Il est possible que
plusieurs files d'attente pour partager un seul mot, de sorte qu'un processus puisse attendre des écritures ou
dans l'une des files d'attente. Un pointeur sur l'indicateur d'événement d'une file d'attente peut être
obtenu en appelant getEventFlagWord()
, et ce pointeur (ou tout autre
(un pointeur vers un emplacement de mémoire partagée approprié) peut être utilisé pour créer
un objet EventFlag
à transmettre dans la forme longue de
readBlocking()
et writeBlocking()
pour un
file d'attente. readNotification
et writeNotification
les paramètres indiquent quels bits de l'indicateur d'événement doivent être utilisés pour signaler les lectures et
écrit sur cette file d'attente. readNotification
et
Les writeNotification
sont des masques de bits 32 bits.
readBlocking()
attend les writeNotification
bits ;
Si ce paramètre est défini sur 0, l'appel échoue toujours. Si le
la valeur de readNotification
est 0, l'appel n'échoue pas, mais
la lecture réussie ne définit aucun bit de notification. Dans une file d'attente synchronisée,
cela signifie que l'appel writeBlocking()
correspondant
ne se réveille jamais, sauf si le bit est défini ailleurs. Dans une file d'attente non synchronisée,
writeBlocking()
n'attend pas (il doit tout de même être utilisé pour définir le
d'écriture), et il est approprié pour les lectures de ne pas définir
bits de notification. De même, writeblocking()
échoue si
readNotification
est défini sur 0, et une écriture réussie définit la valeur
writeNotification
bits.
Pour attendre sur plusieurs files d'attente à la fois, utilisez un objet EventFlag
wait()
pour attendre un masque de bits des notifications. La
La méthode wait()
renvoie un mot d'état avec les bits à l'origine du problème
l'heure du réveil. Ces informations sont ensuite utilisées pour vérifier que la file d'attente correspondante a
d'espace ou de données pour effectuer l'opération d'écriture/lecture souhaitée,
write()
/read()
non bloquantes. Pour obtenir une opération post
, appelez de nouveau la fonction EventFlag
wake()
. Pour connaître la définition de EventFlag
d'abstraction, désignez
system/libfmq/include/fmq/EventFlag.h
Opérations sans copie
La
write
read
/readBlocking
/writeBlocking()
Les API utilisent un pointeur vers un tampon d'entrée/sortie en tant qu'argument et utilisent
des appels memcpy()
en interne pour copier des données entre celui-ci et
Tampon circulaire FMQ. Pour améliorer les performances, Android 8.0 et les versions ultérieures incluent un ensemble
Les API qui fournissent un accès direct du pointeur dans le tampon circulaire, éliminant ainsi les
besoin d'utiliser les appels memcpy
.
Utilisez les API publiques suivantes pour les opérations FMQ sans copie:
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);
- La méthode
beginWrite
fournit des pointeurs de base dans l'anneau FMQ. tampon. Une fois les données écrites, validez-les à l'aide decommitWrite()
. Les méthodesbeginRead
/commitRead
fonctionnent de la même manière. - Les méthodes
beginRead
/Write
prennent en entrée la le nombre de messages à lire/écrire et renvoie une valeur booléenne indiquant si le lecture/écriture est possible. Si la lecture ou l'écriture est possible, lememTx
struct contient des pointeurs de base pouvant être utilisés pour le pointeur direct l'accès à la mémoire partagée du tampon de l'anneau. - La structure
MemRegion
contient des détails sur un bloc de mémoire, y compris le pointeur de base (adresse de base du bloc mémoire) et la longueur en en termes deT
(longueur du bloc de mémoire par rapport au paramètre HIDL défini type de la file d'attente des messages). - La structure
MemTransaction
contient deux objetsMemRegion
structs,first
etsecond
en tant que lecture ou écriture dans le tampon circulaire peut nécessiter une encapsulation en début de file d'attente. Ce signifierait que deux pointeurs de base sont nécessaires pour lire/écrire des données dans le FMQ de la mémoire tampon en anneau.
Pour obtenir l'adresse de base et la longueur à partir d'une structure MemRegion
, procédez comme suit:
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
Pour obtenir des références aux premier et deuxième éléments MemRegion
dans une
Objet MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
Exemple d'écriture dans FMQ à l'aide d'API sans copie:
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 }
Les méthodes d'assistance suivantes font également partie de MemTransaction
:
T* getSlot(size_t idx);
Renvoie un pointeur vers l'emplacementidx
dans l'élémentMemRegions
qui font partie de ceMemTransaction
. Si l'objetMemTransaction
représente la mémoire régions pour lire/écrire N éléments de type T, puis la plage valide deidx
est compris entre 0 et N-1.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
ÉcrirenMessages
éléments de type T dans les régions de mémoire décrites par l'objet, à partir de l'indexstartIdx
. Cette méthode utilisememcpy()
et n'est pas destiné à être utilisé pour une copie sans zéro opération. Si l'objetMemTransaction
représente la mémoire à lire/écrire N éléments de type T, la plage valide pouridx
est compris entre 0 et N-1.bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Méthode d'assistance pour lirenMessages
éléments de type T à partir de régions de mémoire décrites par l'objet à partir destartIdx
. Ce utilisememcpy()
et n'est pas destinée à être utilisée pour une copie sans opération.
Envoyer la file d'attente via HIDL
Côté création:
- Créez un objet de file d'attente de messages comme décrit ci-dessus.
- Vérifiez que l'objet est valide avec
isValid()
. - Si vous attendez sur plusieurs files d'attente en transmettant une
EventFlag
en la forme longue dereadBlocking()
/writeBlocking()
, vous pouvez extraire Pointeur d'indicateur d'événement (avecgetEventFlagWord()
) à partir d'unMessageQueue
qui a été initialisé pour créer l'indicateur utilisez cette option pour créer l'objetEventFlag
nécessaire. - Utilisez la méthode
getDesc()
MessageQueue
pour obtenir une Descriptor. - Dans le fichier
.hal
, attribuez à la méthode un paramètre de type.fmq_sync
oufmq_unsync
, oùT
est un type défini par HIDL approprié. Utilisez ce champ pour envoyer l'objet renvoyé pargetDesc()
au processus de réception.
Côté réception:
- Utilisez l'objet descripteur pour créer un objet
MessageQueue
. Être assurez-vous d'utiliser le même type de file d'attente et le même type de données, sinon le modèle ne parvient pas compiler. - Si vous avez extrait un indicateur d'événement, vous devez l'extraire de la table
MessageQueue
dans le processus de réception. - Utilisez l'objet
MessageQueue
pour transférer les données.