Fila de mensajes rápidos (FMQ)

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 ser kSynchronizedReadWrite para una cola sincronizada o kUnsynchronizedWrite para una cola en la fila.
  • uint16_t (en este ejemplo) puede ser cualquier Tipo definido por HIDL que No involucra búferes anidados (no string ni vec). 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 archivo MQDescriptor. 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 es true):
    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 de availableToRead().

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 con commitWrite(). Los métodos beginRead y commitRead 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 de T (longitud del bloque de memoria en términos de la longitud definida por HIDL de la cola de mensajes).
  • El struct MemTransaction contiene dos MemRegion structs, first y second 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 espacio idx dentro del MemRegions que forman parte de este MemTransaction . Si el objeto MemTransaction representa la memoria regiones para leer/escribir N elementos de tipo T, luego el rango válido de idx está entre 0 y N-1.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    Escribe nMessages elementos de tipo T en las regiones de la memoria que describe el objeto, a partir del índice startIdx Este método usa memcpy() y no está diseñada para usarse como copia cero una sola operación. Si el objeto MemTransaction representa la memoria lee/escribe N elementos de tipo T, entonces el rango válido de idx es entre 0 y N-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Es un método de ayuda para leer nMessages elementos de tipo T desde el regiones de memoria que describe el objeto a partir de startIdx. Esta método usa memcpy() 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:

  1. Crea un objeto de cola de mensajes como se describió anteriormente.
  2. Verifica que el objeto sea válido con isValid().
  3. Si estás esperando varias filas pasando un EventFlag en la forma larga de readBlocking()/writeBlocking(), puedes extraer la puntero de marca de evento (con getEventFlagWord()) de un un objeto MessageQueue que se inicializó para crear la marca. usa esa marca para crear el objeto EventFlag necesario.
  4. Usa el método MessageQueue getDesc() para obtener un objeto descriptor de la aplicación.
  5. En el archivo .hal, asigna al método un parámetro de tipo. fmq_sync o fmq_unsync, donde T es una definido por el HIDL adecuado. Úsalo para enviar el objeto que devuelve getDesc() al proceso receptor

En el lado receptor:

  1. 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.
  2. Si extrajiste la marca de un evento, extráela del objeto de escucha MessageQueue en el proceso receptor.
  3. Usa el objeto MessageQueue para transferir datos.