Si busca soporte AIDL, consulte también FMQ con AIDL .
La infraestructura de llamada a procedimiento remoto (RPC) de HIDL utiliza mecanismos Binder, lo que significa que las llamadas implican una sobrecarga, requieren operaciones del kernel y pueden desencadenar una acción del programador. Sin embargo, para los casos en los que los datos deben transferirse entre procesos con menos gastos generales y sin participación del núcleo, se utiliza el sistema Fast Message Queue (FMQ).
FMQ crea colas de mensajes con las propiedades deseadas. Se puede enviar un objeto MQDescriptorSync
o MQDescriptorUnsync
a través de una llamada HIDL RPC y el proceso de recepción lo puede utilizar para acceder a la cola de mensajes.
Las colas de mensajes rápidas solo se admiten en C++ y en dispositivos con Android 8.0 y versiones posteriores.
Tipos de cola de mensajes
Android admite dos tipos de colas (conocidas como sabores ):
- Las colas no sincronizadas pueden desbordarse y pueden tener muchos lectores; cada lector debe leer los datos a tiempo o perderlos.
- Las colas sincronizadas no pueden desbordarse y solo pueden tener un lector.
No se permite el desbordamiento de ambos tipos de cola (la lectura de una cola vacía fallará) y solo pueden tener un escritor.
No sincronizado
Una cola no sincronizada tiene un solo escritor, pero puede tener cualquier número de lectores. Hay una posición de escritura para la cola; sin embargo, cada lector realiza un seguimiento de su propia posición de lectura independiente.
Las escrituras en la cola siempre se realizan correctamente (no se comprueba si hay desbordamiento) siempre que no superen la capacidad de la cola configurada (las escrituras superiores a la capacidad de la cola fallan inmediatamente). Como cada lector puede tener una posición de lectura diferente, en lugar de esperar a que cada lector lea cada dato, se permite que los datos salgan de la cola cada vez que nuevas escrituras necesiten espacio.
Los lectores son responsables de recuperar los datos antes de que caigan del final de la cola. Una lectura que intenta leer más datos de los disponibles falla inmediatamente (si no es bloqueo) o espera a que haya suficientes datos disponibles (si es bloqueo). Una lectura que intenta leer más datos que la capacidad de la cola siempre falla inmediatamente.
Si un lector no logra seguir el ritmo del escritor, de modo que la cantidad de datos escritos y aún no leídos por ese lector es mayor que la capacidad de la cola, la siguiente lectura no devuelve datos; en su lugar, restablece la posición de lectura del lector para igualar la última posición de escritura y luego devuelve un error. Si los datos disponibles para leer se verifican después del desbordamiento pero antes de la siguiente lectura, muestra más datos disponibles para leer que la capacidad de la cola, lo que indica que se ha producido un desbordamiento. (Si la cola se desborda entre la verificación de los datos disponibles y el intento de leerlos, la única indicación de desbordamiento es que la lectura falla).
Es probable que los lectores de una cola no sincronizada no quieran restablecer los punteros de lectura y escritura de la cola. Por lo tanto, al crear la cola a partir del descriptor, los lectores deben usar un argumento "falso" para el parámetro "resetPointers".
sincronizado
Una cola sincronizada tiene un escritor y un lector con una única posición de escritura y una única posición de lectura. Es imposible escribir más datos de los que la cola tiene espacio o leer más datos de los que la cola contiene actualmente. Dependiendo de si se llama a la función de escritura o lectura con o sin bloqueo, los intentos de exceder el espacio o los datos disponibles devuelven un error inmediatamente o se bloquean hasta que se pueda completar la operación deseada. Los intentos de leer o escribir más datos que la capacidad de la cola siempre fallarán inmediatamente.
Configurar una FMQ
Una cola de mensajes requiere varios objetos MessageQueue
: uno para escribir y uno o más para leer. No existe una configuración explícita de qué objeto se utiliza para escribir o leer; Depende del usuario asegurarse de que no se utilice ningún objeto para lectura y escritura, que haya como máximo un escritor y, para colas sincronizadas, que haya como máximo un lector.
Creando el primer objeto MessageQueue
Una cola de mensajes se crea y configura con una sola llamada:
#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 */);
- El inicializador
MessageQueue<T, flavor>(numElements)
crea e inicializa un objeto que admite la funcionalidad de la cola de mensajes. - El
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
crea e inicializa un objeto que admite la funcionalidad de cola de mensajes con bloqueo. -
flavor
puede serkSynchronizedReadWrite
para una cola sincronizada okUnsynchronizedWrite
para una cola no sincronizada. -
uint16_t
(en este ejemplo) puede ser cualquier tipo definido por HIDL que no involucre buffers anidados (sin tiposstring
ovec
), identificadores o interfaces. -
kNumElementsInQueue
indica el tamaño de la cola en número de entradas; Determina el tamaño del búfer de memoria compartida que se asignará a la cola.
Creando el segundo objeto MessageQueue
El segundo lado de la cola de mensajes se crea utilizando un objeto MQDescriptor
obtenido del primer lado. El objeto MQDescriptor
se envía a través de una llamada RPC HIDL o AIDL al proceso que contendrá el segundo extremo de la cola de mensajes. El MQDescriptor
contiene información sobre la cola, que incluye:
- Información para asignar el búfer y escribir el puntero.
- Información para asignar el puntero de lectura (si la cola está sincronizada).
- Información para asignar la palabra del indicador del evento (si la cola está bloqueando).
- Tipo de objeto (
<T, flavor>
), que incluye el tipo de elementos de cola definidos por HIDL y el tipo de cola (sincronizada o no sincronizada).
El objeto MQDescriptor
se puede utilizar para construir un objeto MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
El parámetro resetPointers
indica si se deben restablecer las posiciones de lectura y escritura a 0 al crear este objeto MessageQueue
. En una cola no sincronizada, la posición de lectura (que es local para cada objeto MessageQueue
en colas no sincronizadas) siempre se establece en 0 durante la creación. Normalmente, el MQDescriptor
se inicializa durante la creación del primer objeto de cola de mensajes. Para obtener control adicional sobre la memoria compartida, puede configurar MQDescriptor
manualmente ( MQDescriptor
se define en system/libhidl/base/include/hidl/MQDescriptor.h
) y luego crear cada objeto MessageQueue
como se describe en esta sección.
Bloqueo de colas e indicadores de eventos
De forma predeterminada, las colas no admiten el bloqueo de lecturas/escrituras. Hay dos tipos de bloqueo de llamadas de lectura/escritura:
- Forma corta , con tres parámetros (puntero de datos, número de elementos, tiempo de espera). Admite el bloqueo de operaciones de lectura/escritura individuales en una sola cola. Cuando se utiliza este formulario, la cola manejará internamente el indicador de evento y las máscaras de bits, y el primer objeto de la cola de mensajes debe inicializarse con un segundo parámetro de
true
. Por ejemplo:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Formato largo , con seis parámetros (incluye indicador de evento y máscaras de bits). Admite el uso de un objeto
EventFlag
compartido entre varias colas y permite especificar las máscaras de bits de notificación que se utilizarán. En este caso, se deben proporcionar el indicador de evento y las máscaras de bits a cada llamada de lectura y escritura.
Para el formato largo, EventFlag
se puede proporcionar explícitamente en cada llamada readBlocking()
y writeBlocking()
. Una de las colas puede inicializarse con un indicador de evento interno, que luego debe extraerse de los objetos MessageQueue
de esa cola usando getEventFlagWord()
y usarse para crear objetos EventFlag
en cada proceso para usar con otras FMQ. Alternativamente, los objetos EventFlag
se pueden inicializar con cualquier memoria compartida adecuada.
En general, cada cola debe usar solo uno de los siguientes: sin bloqueo, bloqueo de formato corto o bloqueo de formato largo. No es un error mezclarlos, pero se requiere una programación cuidadosa para obtener el resultado deseado.
Marcar la memoria como solo lectura
De forma predeterminada, la memoria compartida tiene permisos de lectura y escritura. Para colas no sincronizadas ( kUnsynchronizedWrite
), es posible que el escritor desee eliminar los permisos de escritura para todos los lectores antes de entregar los objetos MQDescriptorUnsync
. Esto garantiza que los otros procesos no puedan escribir en la cola, lo cual se recomienda para proteger contra errores o mal comportamiento en los procesos lectores. Si el escritor quiere que los lectores puedan restablecer la cola cada vez que utilicen MQDescriptorUnsync
para crear el lado de lectura de la cola, entonces la memoria no se puede marcar como de solo lectura. Este es el comportamiento predeterminado del constructor `MessageQueue`. Entonces, si ya hay usuarios de esta cola, es necesario cambiar su código para construir la cola con resetPointer=false
.
- Escritor: llame
ashmem_set_prot_region
con un descriptor de archivoMQDescriptor
y una región configurada como de solo lectura (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Lector: cree una cola de mensajes con
resetPointer=false
(el valor predeterminado estrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Usando la cola de mensajes
La API pública del objeto MessageQueue
es:
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()
y availableToRead()
se pueden utilizar para determinar cuántos datos se pueden transferir en una sola operación. En una cola no sincronizada:
-
availableToWrite()
siempre devuelve la capacidad de la cola. - Cada lector tiene su propia posición de lectura y realiza su propio cálculo para
availableToRead()
. - Desde el punto de vista de un lector lento, se permite que la cola se desborde; esto puede provocar que
availableToRead()
devuelva un valor mayor que el tamaño de la cola. La primera lectura después de un desbordamiento fallará y dará como resultado que la posición de lectura de ese lector se establezca igual al puntero de escritura actual, independientemente de si el desbordamiento se informó o no a través deavailableToRead()
.
Los métodos read()
y write()
devuelven true
si todos los datos solicitados pudieron (y fueron) transferidos hacia/desde la cola. Estos métodos no bloquean; tienen éxito (y devuelven true
) o devuelven un error ( false
) inmediatamente.
Los métodos readBlocking()
y writeBlocking()
esperan hasta que se pueda completar la operación solicitada, o hasta que se agote el tiempo de espera (un valor de timeOutNanos
de 0 significa que nunca se agota el tiempo de espera).
Las operaciones de bloqueo se implementan utilizando una palabra de indicador de evento. De forma predeterminada, cada cola crea y usa su propia palabra indicadora para admitir la forma corta de readBlocking()
y writeBlocking()
. Es posible que varias colas compartan una sola palabra, de modo que un proceso pueda esperar escrituras o lecturas en cualquiera de las colas. Se puede obtener un puntero a la palabra del indicador de evento de una cola llamando a getEventFlagWord()
, y ese puntero (o cualquier puntero a una ubicación de memoria compartida adecuada) se puede usar para crear un objeto EventFlag
para pasarlo a la forma larga de readBlocking()
y writeBlocking()
para una cola diferente. Los parámetros readNotification
y writeNotification
indican qué bits del indicador de evento deben usarse para señalar lecturas y escrituras en esa cola. readNotification
y writeNotification
son máscaras de bits de 32 bits.
readBlocking()
espera los bits writeNotification
; si ese parámetro es 0, la llamada siempre falla. Si el valor readNotification
es 0, la llamada no fallará, pero una lectura exitosa no establecerá ningún bit de notificación. En una cola sincronizada, esto significaría que la llamada writeBlocking()
correspondiente nunca se activará a menos que el bit esté configurado en otro lugar. En una cola no sincronizada, writeBlocking()
no esperará (aún debe usarse para configurar el bit de notificación de escritura) y es apropiado que las lecturas no establezcan ningún bit de notificación. De manera similar, writeblocking()
fallará si readNotification
es 0 y una escritura exitosa establece los bits writeNotification
especificados.
Para esperar en varias colas a la vez, use el método wait()
de un objeto EventFlag
para esperar una máscara de bits de notificaciones. El método wait()
devuelve una palabra de estado con los bits que provocaron el despertar configurados. Luego, esta información se utiliza para verificar que la cola correspondiente tenga suficiente espacio o datos para la operación de escritura/lectura deseada y realizar una write()
/ read()
sin bloqueo. Para recibir una notificación posterior a la operación, utilice otra llamada al método wake()
de EventFlag
. Para obtener una definición de la abstracción EventFlag
, consulte system/libfmq/include/fmq/EventFlag.h
.
Operaciones de copia cero
Las API read
/ write
/ readBlocking
/ writeBlocking()
toman un puntero a un búfer de entrada/salida como argumento y usan llamadas memcpy()
internamente para copiar datos entre el mismo y el búfer de anillo FMQ. Para mejorar el rendimiento, Android 8.0 y versiones posteriores incluyen un conjunto de API que brindan acceso directo al puntero al búfer en anillo, lo que elimina la necesidad de usar llamadas memcpy
.
Utilice las siguientes API públicas para operaciones FMQ de copia cero:
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);
- El método
beginWrite
proporciona punteros base al búfer de anillo FMQ. Una vez escritos los datos, confírmelos usandocommitWrite()
. Los métodosbeginRead
/commitRead
actúan de la misma manera. - Los métodos
beginRead
/Write
toman como entrada el número de mensajes a leer/escribir y devuelven un valor booleano que indica si la lectura/escritura es posible. Si la lectura o escritura es posible, la estructuramemTx
se completa con punteros base que se pueden usar para el acceso directo del puntero a la memoria compartida del búfer en anillo. - La estructura
MemRegion
contiene detalles sobre un bloque de memoria, incluido el puntero base (dirección base del bloque de memoria) y la longitud en términos deT
(longitud del bloque de memoria en términos del tipo de cola de mensajes definido por HIDL). - La estructura
MemTransaction
contiene dos estructurasMemRegion
,first
ysecond
ya que una lectura o escritura en el búfer circular puede requerir un ajuste al principio de la cola. Esto significaría que se necesitan dos punteros base para leer/escribir datos en el búfer de anillo FMQ.
Para obtener la dirección base y la longitud de una estructura 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
Para obtener referencias a la primera y segunda MemRegion
dentro de un objeto MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
Ejemplo de escritura en FMQ utilizando API de copia cero:
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 }
Los siguientes métodos auxiliares también forman parte de MemTransaction
:
-
T* getSlot(size_t idx);
Devuelve un puntero alidx
slot dentro de lasMemRegions
que forman parte de este objetoMemTransaction
. Si el objetoMemTransaction
representa las regiones de memoria para leer/escribir N elementos de tipo T, entonces el rango válido deidx
está entre 0 y N-1. -
bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
Escriba elementosnMessages
de tipo T en las regiones de memoria descritas por el objeto, comenzando desde el índicestartIdx
. Este método utilizamemcpy()
y no debe utilizarse para una operación de copia cero. Si el objetoMemTransaction
representa memoria para leer/escribir N elementos de tipo T, entonces el rango válido deidx
está entre 0 y N-1. -
bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Método auxiliar para leer elementosnMessages
de tipo T de las regiones de memoria descritas por el objeto a partir destartIdx
. Este método usamemcpy()
y no está diseñado para usarse en una operación de copia cero.
Envío de la cola a través de HIDL
Del lado creador:
- Cree un objeto de cola de mensajes como se describe anteriormente.
- Verifique que el objeto sea válido con
isValid()
. - Si va a esperar en varias colas pasando un
EventFlag
a la forma larga dereadBlocking()
/writeBlocking()
, puede extraer el puntero del indicador del evento (usandogetEventFlagWord()
) de un objetoMessageQueue
que se inicializó para crear el indicador, y use esa bandera para crear el objetoEventFlag
necesario. - Utilice el método
MessageQueue
getDesc()
para obtener un objeto descriptor. - En el archivo
.hal
, asigne al método un parámetro de tipofmq_sync
o fmq_unsync
donde T
es un tipo adecuado definido por HIDL. Úselo para enviar el objeto devuelto porgetDesc()
al proceso de recepción.
Del lado receptor:
- Utilice el objeto descriptor para crear un objeto
MessageQueue
. Asegúrese de utilizar el mismo tipo de cola y tipo de datos, o la plantilla no se podrá compilar. - Si extrajo un indicador de evento, extraiga el indicador del objeto
MessageQueue
correspondiente en el proceso de recepción. - Utilice el objeto
MessageQueue
para transferir datos.