O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Fila de mensagens rápidas (FMQ)

A infraestrutura de chamada de procedimento remoto (RPC) do HIDL usa mecanismos Binder, o que significa que as chamadas envolvem sobrecarga, exigem operações do kernel e podem acionar a ação do planejador. No entanto, para os casos em que os dados devem ser transferidos entre processos com menos sobrecarga e nenhum envolvimento do kernel, o sistema Fast Message Queue (FMQ) é usado.

FMQ cria filas de mensagens com as propriedades desejadas. Um MQDescriptorSync ou MQDescriptorUnsync objeto pode ser enviado através de uma chamada HIDL RPC e usado pelo processo de recebimento para acessar a fila de mensagens.

As filas de mensagens rápidas são compatíveis apenas com C ++ e em dispositivos com Android 8.0 e superior.

Tipos de MessageQueue

Suportes Android dois tipos de fila (conhecidos como sabores):

  • Filas não sincronizadas estão autorizados a transbordar, e pode ter muitos leitores; cada leitor deve ler os dados a tempo ou perdê-los.
  • Filas sincronizadas não estão autorizados a transbordar, e pode ter apenas um leitor.

Ambos os tipos de fila não têm permissão para underflow (a leitura de uma fila vazia falhará) e podem ter apenas um gravador.

Não sincronizado

Uma fila não sincronizada tem apenas um gravador, mas pode ter qualquer número de leitores. Há uma posição de gravação para a fila; no entanto, cada leitor mantém o controle de sua própria posição de leitura independente.

As gravações na fila sempre são bem-sucedidas (não são verificadas quanto a estouro), desde que não sejam maiores do que a capacidade da fila configurada (as gravações maiores do que a capacidade da fila falham imediatamente). Como cada leitor pode ter uma posição de leitura diferente, em vez de esperar que cada leitor leia todos os dados, os dados podem cair da fila sempre que novas gravações precisarem de espaço.

As leituras são responsáveis ​​por recuperar os dados antes que eles caiam do fim da fila. Uma leitura que tenta ler mais dados do que o disponível falha imediatamente (se não for bloqueante) ou espera que dados suficientes estejam disponíveis (se não for bloqueadora). Uma leitura que tenta ler mais dados do que a capacidade da fila sempre falha imediatamente.

Se um leitor não consegue acompanhar o gravador, de forma que a quantidade de dados gravados e ainda não lidos por esse leitor é maior do que a capacidade da fila, a próxima leitura não retorna dados; em vez disso, ele redefine a posição de leitura do leitor para igualar a última posição de gravação e retorna a falha. Se os dados disponíveis para leitura forem verificados após o estouro, mas antes da próxima leitura, ele mostra mais dados disponíveis para ler do que a capacidade da fila, indicando que ocorreu o estouro. (Se a fila estourar entre a verificação dos dados disponíveis e a tentativa de leitura desses dados, a única indicação de estouro é que a leitura falha.)

Sincronizado

Uma fila sincronizada tem um gravador e um leitor com uma única posição de gravação e uma única posição de leitura. É impossível gravar mais dados do que a fila tem espaço ou ler mais dados do que a fila atualmente contém. Dependendo se a função de leitura ou gravação de bloqueio ou não-bloqueio é chamada, as tentativas de exceder o espaço ou os dados disponíveis retornam a falha imediatamente ou bloqueiam até que a operação desejada possa ser concluída. As tentativas de ler ou gravar mais dados do que a capacidade da fila sempre falharão imediatamente.

Configurando um FMQ

A fila de mensagens requer múltiplas MessageQueue objetos: um para ser escrito, e um ou mais para ser lido. Não há configuração explícita de qual objeto é usado para escrever ou ler; cabe ao usuário garantir que nenhum objeto seja utilizado para leitura e escrita, que haja no máximo um gravador e, para filas sincronizadas, haja no máximo um leitor.

Criação do primeiro objeto MessageQueue

Uma fila de mensagens é criada e configurada com uma única chamada:

#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 */);
  • O MessageQueue<T, flavor>(numElements) inicializador cria e inicializa um objecto que suporta a funcionalidade fila de mensagens.
  • O MessageQueue<T, flavor>(numElements, configureEventFlagWord) inicializador cria e inicializa um objecto que suporta a funcionalidade fila de mensagens com o bloqueio.
  • flavor pode ser kSynchronizedReadWrite para uma fila sincronizado ou kUnsynchronizedWrite para uma fila não sincronizado.
  • uint16_t (neste exemplo) pode ser qualquer tipo definido pelo HIDL que não envolve tampões aninhados (nenhuma string ou vec tipos), alças, ou interfaces.
  • kNumElementsInQueue indica o tamanho da fila no número de entradas; ele determina o tamanho do buffer de memória compartilhada que será alocado para a fila.

Criação do segundo objeto MessageQueue

O segundo lado da fila de mensagens é criado usando uma MQDescriptor objecto obtido a partir do primeiro lado. O MQDescriptor objeto é enviado através de uma chamada HIDL RPC para o processo que vai realizar a segunda final da fila de mensagens. O MQDescriptor contém informações sobre a fila, incluindo:

  • Informações para mapear o buffer e o ponteiro de gravação.
  • Informações para mapear o ponteiro de leitura (se a fila estiver sincronizada).
  • Informações para mapear a palavra do sinalizador de evento (se a fila estiver bloqueando).
  • Tipo de objecto ( <T, flavor> ), que inclui o tipo definido pelo HIDL de elementos de fila e o sabor fila (sincronizado ou não sincronizado).

O MQDescriptor objecto pode ser utilizado para construir um MessageQueue objeto:

MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)

O resetPointers parâmetro indica se a repor as posições de leitura e gravação para 0 ao criar este MessageQueue objeto. Em uma fila não sincronizado, a posição de leitura (que é local para cada MessageQueue objeto em filas não sincronizadas) é sempre definido como 0 durante a criação. Tipicamente, o MQDescriptor é inicializado durante a criação do primeiro objecto fila de mensagens. Para o controle extra sobre a memória compartilhada, você pode configurar o MQDescriptor manualmente ( MQDescriptor está definido no system/libhidl/base/include/hidl/MQDescriptor.h ), em seguida, criar cada MessageQueue objeto como descrito nesta seção.

Bloqueio de filas e sinalizadores de evento

Por padrão, as filas não oferecem suporte ao bloqueio de leituras / gravações. Existem dois tipos de bloqueio de chamadas de leitura / gravação:

  • Formulário curto, com três parâmetros (ponteiro de dados, número de artigos, o tempo limite). Oferece suporte ao bloqueio de operações individuais de leitura / gravação em uma única fila. Ao usar esta forma, a fila vai lidar com a bandeira evento e bitmasks internamente, eo primeiro objeto fila de mensagens deve ser inicializado com um segundo parâmetro de true . Por exemplo:
    // 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() e availableToRead() pode ser utilizado para determinar a quantidade de dados pode ser transferida em uma única operação. Em uma fila não sincronizada:

  • availableToWrite() retorna sempre a capacidade da fila.
  • Cada leitor tem a sua própria posição de leitura e faz seu próprio cálculo para availableToRead() .
  • Do ponto de vista de um leitor lento, a fila pode transbordar; isto pode resultar em availableToRead() devolver um valor maior do que o tamanho da fila. A primeira leitura após um estouro irá falhar e resultar na posição de leitura para esse leitor ser definido igual ao ponteiro de gravação atual, se o estouro foi relatado através availableToRead() .

O read() e write() métodos retornam true se todos os dados solicitados poderia ser (e foi) transferido de / para a fila. Esses métodos não bloqueiam; que quer ter sucesso (e retornar true ), ou falha de retorno ( false ) imediatamente.

O readBlocking() e writeBlocking() métodos de esperar até que a operação solicitada pode ser concluída, ou até que eles tempo limite (a timeOutNanos valor 0 significa que não tempo limite).

As operações de bloqueio são implementadas usando uma palavra de sinalizador de evento. Por padrão, cada fila cria e utiliza a sua própria palavra de flag para apoiar a forma abreviada de readBlocking() e writeBlocking() . É possível que várias filas compartilhem uma única palavra, de modo que um processo possa aguardar gravações ou leituras em qualquer uma das filas. Um ponteiro para palavra de flag evento de uma fila podem ser obtidas pelo telefone getEventFlagWord() , e que ponteiro (ou qualquer ponteiro para um local de memória compartilhada adequado) pode ser usado para criar um EventFlag objeto para passar para a forma longa de readBlocking() e writeBlocking() para uma fila diferente. Os readNotification e writeNotification parâmetros dizer quais bits na bandeira evento deve ser usado para sinal lê e escreve sobre essa fila. readNotification e writeNotification são bitmasks 32 bits.

readBlocking() esperas nos writeNotification bits; se esse parâmetro for 0, a chamada sempre falha. Se o readNotification valor for 0, a chamada não irá falhar, mas uma leitura bem sucedida não irá definir todos os bocados de notificação. Em uma fila sincronizado, isso significaria que o correspondente writeBlocking() chamada nunca irá acordar a menos que o bit for definido noutro local. Em uma fila não sincronizada, writeBlocking() não vai esperar (que ainda deve ser usado para definir o bit de notificação escrita), e é apropriado para lê não definir todos os bocados de notificação. Da mesma forma, writeblocking() falhará se readNotification é 0, e uma gravação bem-sucedida define os especificados writeNotification pedaços.

Para esperar em filas múltiplas de uma só vez, use um EventFlag do objeto wait() método que esperar em uma máscara de bits de notificações. O wait() método retorna uma palavra de estado com os bits que causaram a acordar set. Esta informação é então usada para verificar a fila correspondente tem espaço suficiente ou de dados para a operação de gravação desejada / leitura e executar um de não bloqueio write() / read() . Para receber uma notificação pós-operação, use outra chamada para o EventFlag de wake() método. Para uma definição do EventFlag abstração, consulte o system/libfmq/include/fmq/EventFlag.h .

Operações de cópia zero

A read / write / readBlocking / writeBlocking() APIs levar um ponteiro para uma memória intermédia de entrada / saída como um argumento e utilização memcpy() internamente chama para copiar dados entre o mesmo e o anel de tampão FMQ. Para melhorar o desempenho, Android 8,0 e superior incluem um conjunto de APIs que fornecem acesso directo ponteiro para o buffer de anel, eliminando a necessidade de utilizar memcpy chamadas.

Use as seguintes APIs públicas para operações FMQ de cópia zero:

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);
  • O beginWrite método fornece ponteiros de base para o buffer de anel FMQ. Após os dados são gravados, cometê-lo usando commitWrite() . Os beginRead / commitRead métodos agir do mesmo modo.
  • Os beginRead / Write métodos tomar como entrada o número de mensagens para ser lido / escrito e retornar um booleano que indica se a leitura / gravação é possível. Se a leitura ou gravação é possível a memTx struct é preenchida com ponteiros de base que podem ser usados para acesso ponteiro direto para a memória compartilhada buffer de anel.
  • O MemRegion struct contém detalhes sobre um bloco de memória, incluindo o ponteiro de base (endereço de base do bloco de memória) e o comprimento em termos de T (comprimento do bloco de memória em termos do tipo definido pelo HIDL da fila de mensagem).
  • O MemTransaction struct contém dois MemRegion estruturas, first e second como uma leitura ou escrita para a memória intermédia de anel pode exigir um rodeia para o início da fila. Isso significaria que dois ponteiros de base são necessários para ler / gravar dados no buffer de anel FMQ.

Para obter o endereço base e comprimento de um MemRegion struct:

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 obter referências para o primeiro e segundo MemRegion s dentro de um MemTransaction objeto:

const MemRegion& getFirstRegion(); // get a reference to the first MemRegion
const MemRegion& getSecondRegion(); // get a reference to the second MemRegion

Exemplo de gravação no FMQ usando APIs de cópia zero:

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
}

Os seguintes métodos auxiliares são também parte do MemTransaction :

  • T* getSlot(size_t idx);
    Retorna um ponteiro para slot de idx dentro dos MemRegions que fazem parte deste MemTransaction objeto. Se o MemTransaction objeto está representando as regiões de memória para ler itens / write N do tipo T, então o intervalo válido de idx está entre 0 e N-1.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    Escrever nMessages itens do tipo T, nas regiões de memória descritos por objecto, a partir do índice de startIdx . Este usa o método memcpy() e não deve feito para ser usado para uma operação de cópia zero. Se o MemTransaction objeto representa a memória de leitura / gravação N itens do tipo T, então o intervalo válido de idx está entre 0 e N-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Helper método para ler nMessages itens do tipo T das regiões de memória descritos pelo objeto a partir de startIdx . Este usa o método memcpy() e não se destina a ser utilizado para uma operação de cópia zero.

Enviando a fila por HIDL

Do lado da criação:

  1. Crie o objeto de fila de mensagens conforme descrito acima.
  2. Verifique se o objeto é válido com isValid() .
  3. Se você vai estar à espera de várias filas, passando uma EventFlag na forma longa readBlocking() / writeBlocking() , você pode extrair o ponteiro bandeira evento (usando getEventFlagWord() ) a partir de um MessageQueue objeto que foi inicializado para criar a bandeira, e usar essa bandeira para criar o necessário EventFlag objeto.
  4. Use o MessageQueue getDesc() método para obter um objeto descritor.
  5. No .hal arquivo, dar o método de um parâmetro do tipo fmq_sync ou fmq_unsync em que T é um tipo definido pelo HIDL adequado. Utilize este para enviar o objeto retornado por getDesc() para o processo de recebimento.

Do lado receptor:

  1. Use o objeto descritor de criar um MessageQueue objeto. Certifique-se de usar o mesmo tipo de fila e tipo de dados, ou o modelo não será compilado.
  2. Se você extraiu uma bandeira evento, extrair a bandeira do correspondente MessageQueue objeto no processo de recebimento.
  3. Use o MessageQueue objeto a transferência de dados.