Si buscas compatibilidad con AIDL, consulta también FMQ con AIDL:
La infraestructura de llamada de procedimiento remoto (RPC) de HIDL usa mecanismos Binder, lo que significa que las llamadas implican sobrecarga, requieren operaciones de kernel y pueden activar acción de programador. Sin embargo, para los casos en los que los datos deben transferirse entre con menos sobrecarga y sin participación del kernel, la cola de mensajes rápidos (FMQ).
FMQ crea colas de mensajes con las propiedades deseadas. Los
Los objetos MQDescriptorSync
o MQDescriptorUnsync
pueden
enviados a través de una llamada RPC HIDL y usada por el proceso receptor para
de la fila de mensajes.
Las colas de mensajes rápidos solo son compatibles con C++ y en dispositivos con Android 8.0 y versiones posteriores.
Tipos de MessageQueue
Android admite dos tipos de cola (conocidos como variantes):
- Las colas no sincronizadas pueden desbordarse y pueden tener muchas 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 subdesbordamiento en ninguno de los tipos de colas (leer desde una cola vacía) falla) y solo puede tener un escritor.
Sin sincronizar
Una cola no sincronizada tiene solo un escritor, pero puede tener cualquier cantidad de lectores. Hay una posición de escritura para la cola; sin embargo, cada lector mantiene su propia posición de lectura independiente.
Las operaciones de escritura en la cola siempre se realizan de manera correcta (no se verifica el desbordamiento), siempre y cuando no superen la capacidad de la cola configurada (escrituras mayores que el la capacidad de la cola falla de inmediato). Como cada lector puede tener una lectura diferente en lugar de esperar a que cada lector lea cada dato, dato, puede caer de la cola cada vez que nuevas escrituras necesiten espacio.
Los lectores son responsables de recuperar los datos antes de que caigan en la fila. Una lectura que intenta leer más datos de los que están disponibles falla inmediatamente (si no se bloquea) o espera a que haya suficientes datos disponibles (si bloqueo). Una lectura que intenta leer más datos que la capacidad de la cola siempre falla de inmediato.
Si un lector no se mantiene al día con el escritor, para que la cantidad de datos que el lector escribe y aún no lee es mayor que la capacidad de la cola, next read no muestra datos; en su lugar, restablece la configuración de lectura para que sea igual a la última posición de escritura y, luego, muestra un error. Si el botón los datos disponibles para leer se comprueban tras el desbordamiento, pero antes de la próxima lectura, muestra más datos disponibles para leer que la capacidad de la cola, lo que indica se produjo un desbordamiento. (Si la cola se desborda entre la comprobación de los datos disponibles y tratas de leerlos, la única indicación de desbordamiento es que la lectura falla).
Es probable que los lectores de una cola sin sincronizar no quieran restablecer los punteros de lectura y escritura de la cola. Por lo tanto, cuando crees la cola desde el los lectores del descriptor deben usar un argumento "false" para el argumento "resetPointers" parámetro.
Sincronizado
Una cola sincronizada tiene un escritor y un lector con una sola escritura y una única posición de lectura. Es imposible escribir más datos que Si la cola tiene espacio para leer más datos o los que contiene actualmente, Según si la función de escritura o lectura con bloqueo o sin bloqueo se Si se llama o excede el espacio disponible, o los datos muestran un error. de inmediato o bloqueas hasta que se pueda completar la operación deseada. Intentos de leer o escribir más datos de los que la capacidad de la cola siempre falla de inmediato.
Configurar una FMQ
Una cola de mensajes requiere varios objetos MessageQueue
: uno para
en los que se puede escribir
y uno o más para leer. No se incluyen
la configuración de qué objeto se usa para escritura o lectura depende de la
se asegura de que no se use ningún objeto para lectura y escritura, que
es como máximo un escritor y, para las colas sincronizadas, que hay como máximo uno
de lectura.
Crea el primer objeto MessageQueue
Se crea y configura una cola de mensajes 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 cola de mensajes. - El inicializador
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
crea e inicializa un objeto que admita la funcionalidad de cola de mensajes con bloqueo. flavor
puede serkSynchronizedReadWrite
para una cola sincronizada okUnsynchronizedWrite
para una cola en la fila.uint16_t
(en este ejemplo) puede ser cualquier Tipo definido por HIDL que No involucra búferes anidados (nostring
nivec
). tipos), controladores o interfaces.kNumElementsInQueue
indica el tamaño de la cola en cantidad de entradas; determina el tamaño del búfer de memoria compartida que se asigna para la fila.
Crea el segundo objeto MessageQueue
El segundo lado de la cola de mensajes se crea usando un
Objeto MQDescriptor
obtenido del primer lado. El
El objeto MQDescriptor
se envía a través de una llamada RPC HIDL o AIDL al proceso
que contiene el segundo extremo de la cola de mensajes. El
MQDescriptor
contiene información sobre la cola, incluida la siguiente:
- Información para asignar el búfer y el puntero de escritura.
- Información para asignar el puntero de lectura (si la cola está sincronizada).
- Información para asignar la palabra de la marca del evento (si la cola está bloqueada).
- El tipo de objeto (
<T, flavor>
), que incluye el elemento Tipo definido por HIDL de los elementos de cola y la variante de cola (sincronizada o no sincronizada).
El objeto MQDescriptor
se puede usar para construir un
Objeto MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
El parámetro resetPointers
indica si se debe restablecer la lectura
y escribirás posiciones en 0 mientras creas este objeto MessageQueue
.
En una cola no sincronizada, la posición de lectura (que es local para cada
MessageQueue
en colas sin sincronizar) siempre está configurada en 0.
durante la creación. Por lo general, MQDescriptor
se inicializa
del primer objeto de la cola de mensajes. Para tener más control
puedes configurar el MQDescriptor
de forma manual
(MQDescriptor
se define en
system/libhidl/base/include/hidl/MQDescriptor.h
)
Luego, crea cada objeto MessageQueue
como se describe en esta sección.
Colas de bloqueo y marcas de eventos
De forma predeterminada, las colas no admiten el bloqueo de operaciones de lectura o escritura. Existen dos tipos de bloqueo de llamadas de lectura/escritura:
- Formato corto, con tres parámetros (puntero de datos, cantidad de elementos,
tiempo de espera). Admite el bloqueo de operaciones individuales de lectura y escritura en un solo
en la fila. Cuando se usa este formulario, la cola controla la marca del evento y las máscaras de bits
internamente, 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 la marca del evento y las máscaras de bits)
Admite el uso de un objeto
EventFlag
compartido entre varias colas y permite especificar las máscaras binarias de notificación que se usarán. En este caso, el de evento y máscaras de bits a cada llamada de lectura y escritura.
Para la forma larga, EventFlag
se puede suministrar de manera explícita en
cada llamada a readBlocking()
y writeBlocking()
. Uno de
las colas pueden inicializarse con una marca de evento interna, que luego debe
extraídas de los objetos MessageQueue
de esa cola con
getEventFlagWord()
y se usa para crear EventFlag
en cada proceso para usarlos con otras FMQ. Como alternativa, el
Los objetos EventFlag
se pueden inicializar con cualquier recurso compartido adecuado.
memoria.
En general, cada cola debe usar solo una de estas reglas bloqueos o bloqueos de formato largo. No es un error mezclarlos, pero ten cuidado se requiere para obtener el resultado deseado.
Marca la memoria como de solo lectura
De forma predeterminada, la memoria compartida tiene permisos de lectura y escritura. Para no sincronizados
(kUnsynchronizedWrite
), el escritor podría querer quitar los permisos de escritura de todos
de los lectores antes de que entregue los objetos MQDescriptorUnsync
. Esto garantiza que las otras
procesos no pueden escribir en la cola, lo que se recomienda para proteger contra errores o comportamientos inadecuados en
los procesos del lector.
Si el escritor quiere que los lectores puedan restablecer la cola cada vez que usen el
MQDescriptorUnsync
para crear el lado de lectura de la cola; luego, no se podrá marcar la memoria
como de solo lectura. Este es el comportamiento predeterminado del constructor `MessageQueue`. Entonces, si ya hay
usuarios existentes de esta cola, se debe cambiar su código para construir la cola con
resetPointer=false
- Escritor: Llama a
ashmem_set_prot_region
con un descriptor de archivoMQDescriptor
. y la región establecida en solo lectura (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Lector: crea una cola de mensajes con
resetPointer=false
(el el valor predeterminado estrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Usa MessageQueue
La API pública del objeto MessageQueue
es la siguiente:
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);
Se pueden usar availableToWrite()
y availableToRead()
para determinar cuántos datos se pueden transferir en una sola operación. En una
no sincronizada:
availableToWrite()
siempre muestra 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, la cola puede desbordarse;
Esto puede hacer que
availableToRead()
muestre un valor mayor que el tamaño de la cola. La primera lectura después de un desbordamiento falla y da como resultado que la posición de lectura para ese lector sea igual al puntero de escritura actual si el desbordamiento se informó o no a través deavailableToRead()
Los métodos read()
y write()
devuelven
true
si se pudieron transferir (y se) transfirieron todos los datos solicitados desde y hacia este
en la fila. Estos métodos no bloquean. tienen éxito (y devolverán
true
) o mostrar una falla (false
) de inmediato.
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
El valor 0 de timeOutNanos
significa que nunca se agotó el tiempo de espera.
Las operaciones de bloqueo se implementan mediante una palabra marca de evento. De forma predeterminada,
cada cola crea y usa su propia palabra de marcación para admitir la forma abreviada de
readBlocking()
y writeBlocking()
. Es posible
varias colas para compartir una sola palabra, de modo que un proceso pueda esperar las escrituras o
operaciones de lectura en cualquiera de las colas. Se puede usar un puntero para la palabra
de marca de un evento de la cola
que se obtiene llamando a getEventFlagWord()
y ese puntero (o cualquier otro
puntero a una ubicación adecuada de memoria compartida) para crear una
EventFlag
para pasar al formato largo de
readBlocking()
y writeBlocking()
para un nombre diferente
en la fila. readNotification
y writeNotification
los parámetros indican qué bits en la marca de evento se deben usar para indicar las lecturas y
escribe 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 botón
readNotification
es 0, la llamada no falla, pero
la lectura correcta no establecerá ningún bit de notificación. En una cola sincronizada,
esto significa que la llamada writeBlocking()
correspondiente
nunca se activa a menos que el bit esté establecido en otro lugar. En una cola sin sincronizar,
writeBlocking()
no espera (aún debe usarse para establecer la
bit de notificación de escritura), y es apropiado que las lecturas no establezcan
bits de notificación. Del mismo modo, writeblocking()
falla si
readNotification
es 0, y una escritura correcta establece el valor especificado
writeNotification
bits.
Para esperar en varias colas a la vez, usa la función de un objeto EventFlag
Método wait()
para esperar una máscara de bits de notificaciones. El
El método wait()
muestra una palabra de estado con los bits que causaron el
Despertar establecido. Luego, esa información se usa para verificar que se haya
espacio o datos suficientes para la operación deseada de escritura y lectura y realizar
write()
/read()
sin bloqueo. Cómo obtener una operación de publicación
notificación, usa otra llamada a los EventFlag
wake()
. Para una definición de EventFlag
abstracción, consulta
system/libfmq/include/fmq/EventFlag.h
Operaciones sin copia
El
read
/write
/readBlocking
/writeBlocking()
Las APIs toman un puntero hacia un búfer de entrada/salida como argumento y usan
memcpy()
llama de forma interna para copiar los datos entre la misma y la
Búfer de tono de FMQ. Para mejorar el rendimiento, Android 8.0 y versiones posteriores incluyen un conjunto de
que proporcionan acceso directo al puntero en el búfer de anillo, lo que elimina la
debes usar las llamadas de memcpy
.
Usa las siguientes APIs públicas para operaciones FMQ sin 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);
- El método
beginWrite
proporciona punteros base al anillo FMQ. tiempo de reserva. Después de escribir los datos, confírmalos concommitWrite()
. Los métodosbeginRead
ycommitRead
actúan de la misma manera. - Los métodos
beginRead
/Write
toman como entrada el la cantidad de mensajes para leer/escribir y devolver un valor booleano que indica si el operaciones de lectura y escritura. Si es posible realizar operaciones de lectura o escritura,memTx
La struct se propaga con punteros base que se pueden usar para punteros directos acceso a la memoria compartida del búfer de anillo. - El struct
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 de la longitud definida por HIDL de la cola de mensajes). - El struct
MemTransaction
contiene dosMemRegion
structs,first
ysecond
como una operación de lectura o escritura Es posible que el búfer de anillo requiera que se ajuste al inicio de la cola. Esta significaría que se necesitan dos punteros base para leer/escribir datos en la FMQ búfer de anillo.
Para obtener la dirección base y la longitud de un struct MemRegion
, haz lo siguiente:
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 la segunda MemRegion
en 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 con APIs sin copia:
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 al espacioidx
dentro delMemRegions
que forman parte de esteMemTransaction
. Si el objetoMemTransaction
representa la memoria regiones para leer/escribir N elementos de tipo T, luego el rango válido deidx
está entre 0 y N-1.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
EscribenMessages
elementos de tipo T en las regiones de la memoria que describe el objeto, a partir del índicestartIdx
Este método usamemcpy()
y no está diseñada para usarse como copia cero una sola operación. Si el objetoMemTransaction
representa la memoria lee/escribe N elementos de tipo T, entonces el rango válido deidx
es entre 0 y N-1.bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Es un método de ayuda para leernMessages
elementos de tipo T desde el regiones de memoria que describe el objeto a partir destartIdx
. Esta método usamemcpy()
y no está diseñado para usarse como copia cero una sola operación.
Enviar la cola por HIDL
Del lado de la creación:
- Crea un objeto de cola de mensajes como se describió anteriormente.
- Verifica que el objeto sea válido con
isValid()
. - Si estás esperando varias filas pasando un
EventFlag
en la forma larga dereadBlocking()
/writeBlocking()
, puedes extraer la puntero de marca de evento (congetEventFlagWord()
) de un un objetoMessageQueue
que se inicializó para crear la marca. usa esa marca para crear el objetoEventFlag
necesario. - Usa el método
MessageQueue
getDesc()
para obtener un objeto descriptor de la aplicación. - En el archivo
.hal
, asigna al método un parámetro de tipo.fmq_sync
ofmq_unsync
, dondeT
es una definido por el HIDL adecuado. Úsalo para enviar el objeto que devuelvegetDesc()
al proceso receptor
En el lado receptor:
- Usa el objeto descriptor para crear un objeto
MessageQueue
. Sé asegúrate de usar la misma clase de cola y tipo de datos, o la plantilla no podrá compilar. - Si extrajiste la marca de un evento, extráela del objeto de escucha
MessageQueue
en el proceso receptor. - Usa el objeto
MessageQueue
para transferir datos.