Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.
Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Cola de mensajes rápidos (FMQ)

La infraestructura de llamada a procedimiento remoto (RPC) de HIDL utiliza mecanismos Binder, lo que significa que las llamadas implican gastos generales, requieren operaciones del kernel y pueden desencadenar acciones del programador. Sin embargo, para los casos en los que los datos deben transferirse entre procesos con menos sobrecarga y sin participación del kernel, se utiliza el sistema Fast Message Queue (FMQ).

FMQ crea colas de mensajes con las propiedades deseadas. Un objeto MQDescriptorSync o MQDescriptorUnsync se puede enviar a través de una llamada HIDL RPC y el proceso de recepción puede utilizarlo para acceder a la cola de mensajes.

Las colas de mensajes rápidos solo se admiten en C ++ y en dispositivos que ejecutan Android 8.0 y versiones posteriores.

Tipos de MessageQueue

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 que ambos tipos de cola se desborden (la lectura de una cola vacía fallará) y solo pueden tener un escritor.

Sin sincronizar

Una cola no sincronizada tiene solo un 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 el desbordamiento) siempre que no superen la capacidad de la cola configurada (las escrituras más grandes que 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 todos los datos, se permite que los datos se salgan de la cola siempre que las nuevas escrituras necesiten espacio.

Las lecturas son responsables de recuperar los datos antes de que caigan al final de la cola. Una lectura que intenta leer más datos de los disponibles falla inmediatamente (si no se bloquea) o espera a que haya suficientes datos disponibles (si se bloquea). Una lectura que intenta leer más datos que la capacidad de la cola siempre falla inmediatamente.

Si un lector no logra mantenerse al día con el 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 cambio, restablece la posición de lectura del lector para que sea igual a la última posición de escritura y luego devuelve el 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 leer esos datos, la única indicación de desbordamiento es que la lectura falla).

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 para o leer más datos de los que la cola tiene actualmente. Dependiendo de si se llama a la función de lectura o escritura con bloqueo o sin bloqueo, los intentos de exceder el espacio disponible o los datos devuelven la falla 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 un FMQ

Una cola de mensajes requiere varios objetos MessageQueue : uno para escribir y uno o más para leer. No hay una configuración explícita de qué objeto se usa para escribir o leer; Depende del usuario asegurarse de que no se utilice ningún objeto tanto para la lectura como para la escritura, que haya como máximo un escritor y, para las colas sincronizadas, que haya como máximo un lector.

Creando 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 MessageQueue<T, flavor>(numElements) crea e inicializa un objeto que admite la funcionalidad de 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 ser kSynchronizedReadWrite para una cola sincronizada o kUnsynchronizedWrite para una cola no sincronizada.
  • uint16_t (en este ejemplo) puede ser cualquier tipo definido por HIDL que no involucre búferes anidados (sin string o tipos de vec ), 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 HIDL RPC al proceso que mantendrá el segundo extremo de la cola de mensajes. MQDescriptor contiene información sobre la cola, que incluye:

  • Información para mapear el búfer y escribir puntero.
  • Información para mapear el puntero de lectura (si la cola está sincronizada).
  • Información para mapear la palabra del indicador de evento (si la cola está bloqueada).
  • Tipo de objeto ( <T, flavor> ), que incluye el tipo de elementos de cola definido por HIDL y el tipo de cola (sincronizado o no sincronizado).

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 restablecer las posiciones de lectura y escritura a 0 mientras se crea 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, MQDescriptor se inicializa durante la creación del primer objeto de cola de mensajes. Para un 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 y banderas 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. Al usar este formulario, la cola manejará el indicador de eventos 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 */);
    
  • Long form, with six parameters (includes event flag and bitmasks). Supports using a shared EventFlag object between multiple queues and allows specifying the notification bit masks to be used. In this case, the event flag and bitmasks must be supplied to each read and write call.

For the long form, the EventFlag can be supplied explicitly in each readBlocking() and writeBlocking() call. One of the queues may be initialized with an internal event flag, which must then be extracted from that queue's MessageQueue objects using getEventFlagWord() and used to create EventFlag objects in each process for use with other FMQs. Alternatively, the EventFlag objects can be initialized with any suitable shared memory.

In general, each queue should use only one of non-blocking, short-form blocking, or long-form blocking. It is not an error to mix them, but careful programming is required to get the desired result.

Using the MessageQueue

The public API of the MessageQueue object is:

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 hace su propio cálculo para availableToRead() .
  • Desde el punto de vista de un lector lento, se permite que la cola se desborde; esto puede resultar en 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 para ese lector se establezca igual al puntero de escritura actual, ya sea que el desbordamiento se haya informado o no a través de availableToRead() .

Los métodos read() y write() devuelven true si todos los datos solicitados pudieron (y fueron) transferidos a / desde la cola. Estos métodos no bloquean; tienen éxito (y devuelven true ) o devuelven error ( false ) inmediatamente.

Los readBlocking() y writeBlocking() esperan hasta que se pueda completar la operación solicitada, o hasta que se timeOutNanos tiempo de espera (un valor timeOutNanos de 0 significa que nunca se timeOutNanos tiempo de espera).

Las operaciones de bloqueo se implementan utilizando una palabra de marca de evento. De forma predeterminada, cada cola crea y usa su propia palabra de readBlocking() para admitir la forma abreviada 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 eventos 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 pasar 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 de 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 writeBlocking() correspondiente a writeBlocking() nunca se activará a menos que el bit se establezca en otro lugar. En una cola no sincronizada, writeBlocking() no esperará (aún debe usarse para establecer 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 de writeNotification especificados.

Para esperar en varias colas a la vez, use el EventFlag wait() un objeto EventFlag para esperar en una máscara de bits de notificaciones. El método wait() devuelve una palabra de estado con los bits que provocaron la activación. Esta información se utiliza luego para verificar que la cola correspondiente tenga suficiente espacio o datos para la operación de escritura / lectura deseada y realizar una write() / read() bloqueo. Para obtener una notificación posterior a la operación, use otra llamada al EventFlag wake() 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 de puntero directo al búfer de anillo, lo que elimina la necesidad de usar llamadas de 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 en el búfer de anillo FMQ. Después de que los datos commitWrite() escritos, commitWrite() usando commitWrite() . Los métodos beginRead / 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 booleano que indica si la lectura / escritura es posible. Si la lectura o escritura es posible, la estructura memTx se llena con punteros base que se pueden usar para el acceso directo del puntero a la memoria compartida del búfer de 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 de T (longitud del bloque de memoria en términos del tipo definido por HIDL de la cola de mensajes).
  • La estructura MemTransaction contiene dos estructuras MemRegion , la first y la second ya que una lectura o escritura en el búfer de anillo puede requerir una vuelta 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 a la ranura idx dentro de MemRegions que son parte de este objeto MemTransaction . Si el objeto MemTransaction representa las regiones de memoria para leer / escribir N elementos de tipo T, entonces el rango válido de idx está entre 0 y N-1.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    Escriba elementos de nMessages de tipo T en las regiones de memoria descritas por el objeto, comenzando desde el índice startIdx . Este método usa memcpy() y no debe usarse para una operación de copia cero. Si el objeto MemTransaction representa memoria para leer / escribir N elementos de tipo T, entonces el rango válido de idx está entre 0 y N-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Método de ayuda para leer elementos de nMessages de tipo T de las regiones de memoria descritas por el objeto a partir de startIdx . Este método usa memcpy() y no está diseñado para usarse para una operación de copia cero.

Envío de la cola a través de HIDL

En el lado de la creación:

  1. Cree un objeto de cola de mensajes como se describe arriba.
  2. Verifique que el objeto sea válido con isValid() .
  3. Si esperará en varias colas pasando un EventFlag en la forma larga de readBlocking() / writeBlocking() , puede extraer el puntero del indicador de evento (usando getEventFlagWord() ) de un objeto MessageQueue que se inicializó para crear el indicador, y use esa bandera para crear el objeto EventFlag necesario.
  4. Utilice el MessageQueue getDesc() para obtener un objeto descriptor.
  5. En el archivo .hal , .hal al método un parámetro de tipo fmq_sync o fmq_unsync donde T es un tipo definido por HIDL adecuado. Use esto para enviar el objeto devuelto por getDesc() al proceso de recepción.

En el lado receptor:

  1. Utilice el objeto descriptor para crear un objeto MessageQueue . Asegúrese de utilizar el mismo tipo de datos y tipo de cola, o la plantilla no se podrá compilar.
  2. Si extrajo un indicador de evento, extraiga el indicador del objeto MessageQueue correspondiente en el proceso de recepción.
  3. Utilice el objeto MessageQueue para transferir datos.