Se você procura suporte AIDL, veja também FMQ com AIDL .
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ções do agendador. Porém, para casos em que os dados devem ser transferidos entre processos com menos sobrecarga e sem envolvimento do kernel, o sistema Fast Message Queue (FMQ) é utilizado.
FMQ cria filas de mensagens com as propriedades desejadas. Um objeto MQDescriptorSync
ou MQDescriptorUnsync
pode ser enviado por meio de uma chamada RPC HIDL e usado pelo processo de recebimento para acessar a fila de mensagens.
O Fast Message Queues é compatível apenas com C++ e em dispositivos com Android 8.0 e superior.
Tipos de MessageQueue
O Android oferece suporte a dois tipos de fila (conhecidos como sabores ):
- As filas não sincronizadas podem transbordar e podem ter muitos leitores; cada leitor deve ler os dados a tempo ou perdê-los.
- As filas sincronizadas não podem transbordar e podem ter apenas um leitor.
Ambos os tipos de fila não podem ter estouro negativo (a leitura de uma fila vazia falhará) e só podem ter um gravador.
Não sincronizado
Uma fila não sincronizada possui apenas um gravador, mas pode ter qualquer número de leitores. Existe uma posição de gravação para a fila; entretanto, cada leitor acompanha 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 que a capacidade da fila configurada (as gravações maiores 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 cada dado, os dados podem sair da fila sempre que novas gravações precisarem de espaço.
Os leitores são responsáveis por recuperar os dados antes que eles caiam no final da fila. Uma leitura que tenta ler mais dados do que os disponíveis falha imediatamente (se não for bloqueadora) ou aguarda que dados suficientes estejam disponíveis (se for bloqueadora). Uma leitura que tenta ler mais dados do que a capacidade da fila sempre falha imediatamente.
Se um leitor não conseguir acompanhar o gravador, de modo que a quantidade de dados gravados e ainda não lidos por esse leitor seja maior que a capacidade da fila, a próxima leitura não retornará dados; em vez disso, ele redefine a posição de leitura do leitor para igualar a posição de gravação mais recente e retorna falha. Se os dados disponíveis para leitura forem verificados após o estouro, mas antes da próxima leitura, serão mostrados mais dados disponíveis para leitura do que a capacidade da fila, indicando que ocorreu um estouro. (Se a fila transbordar 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.)
Os leitores de uma fila não sincronizada provavelmente não desejam redefinir os ponteiros de leitura e gravação da fila. Portanto, ao criar a fila a partir do descritor, os leitores devem usar um argumento `false` para o parâmetro `resetPointers`.
Sincronizado
Uma fila sincronizada possui 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 contém atualmente. Dependendo se a função de gravação ou leitura com ou sem bloqueio for chamada, as tentativas de exceder o espaço ou os dados disponíveis retornarão falha imediatamente ou serão bloqueadas 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
Uma fila de mensagens requer vários objetos MessageQueue
: um para gravação e um ou mais para leitura. Não há configuração explícita de qual objeto é utilizado para escrita ou leitura; cabe ao usuário garantir que nenhum objeto seja utilizado tanto para leitura quanto para gravação, que haja no máximo um gravador e, para filas sincronizadas, que haja no máximo um leitor.
Criando o 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 inicializador
MessageQueue<T, flavor>(numElements)
cria e inicializa um objeto que dá suporte à funcionalidade de fila de mensagens. - O inicializador
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
cria e inicializa um objeto que suporta a funcionalidade de fila de mensagens com bloqueio. -
flavor
pode serkSynchronizedReadWrite
para uma fila sincronizada oukUnsynchronizedWrite
para uma fila não sincronizada. -
uint16_t
(neste exemplo) pode ser qualquer tipo definido por HIDL que não envolva buffers aninhados (semstring
ou tiposvec
), identificadores ou interfaces. -
kNumElementsInQueue
indica o tamanho da fila em número de entradas; determina o tamanho do buffer de memória compartilhada que será alocado para a fila.
Criando o segundo objeto MessageQueue
O segundo lado da fila de mensagens é criado utilizando um objeto MQDescriptor
obtido do primeiro lado. O objeto MQDescriptor
é enviado por meio de uma chamada RPC HIDL ou AIDL para o processo que manterá a segunda extremidade 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 do evento (se a fila estiver bloqueando).
- Tipo de objeto (
<T, flavor>
), que inclui o tipo de elementos de fila definido por HIDL e o tipo de fila (sincronizado ou não sincronizado).
O objeto MQDescriptor
pode ser usado para construir um objeto MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
O parâmetro resetPointers
indica se as posições de leitura e gravação devem ser redefinidas para 0 ao criar esse objeto MessageQueue
. Em uma fila não sincronizada, a posição de leitura (que é local para cada objeto MessageQueue
em filas não sincronizadas) é sempre definida como 0 durante a criação. Normalmente, o MQDescriptor
é inicializado durante a criação do primeiro objeto da fila de mensagens. Para obter controle extra sobre a memória compartilhada, é possível configurar o MQDescriptor
manualmente ( MQDescriptor
é definido em system/libhidl/base/include/hidl/MQDescriptor.h
) e, em seguida, criar cada objeto MessageQueue
conforme descrito nesta seção.
Bloqueio de filas e sinalizadores de eventos
Por padrão, as filas não suportam o bloqueio de leituras/gravações. Existem dois tipos de bloqueio de chamadas de leitura/gravação:
- Formato abreviado , com três parâmetros (ponteiro de dados, número de itens, tempo limite). Suporta bloqueio em operações individuais de leitura/gravação em uma única fila. Ao usar este formulário, a fila manipulará o sinalizador de evento e as máscaras de bits internamente, e o primeiro objeto da fila de mensagens deverá ser inicializado com um segundo parâmetro
true
. Por exemplo:// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- Formato longo , com seis parâmetros (inclui sinalizador de evento e máscaras de bits). Suporta o uso de um objeto
EventFlag
compartilhado entre várias filas e permite especificar as máscaras de bits de notificação a serem usadas. Neste caso, o sinalizador de evento e as máscaras de bits devem ser fornecidos para cada chamada de leitura e gravação.
Para o formato longo, o EventFlag
pode ser fornecido explicitamente em cada chamada readBlocking()
e writeBlocking()
. Uma das filas pode ser inicializada com um sinalizador de evento interno, que deve então ser extraído dos objetos MessageQueue
dessa fila usando getEventFlagWord()
e usado para criar objetos EventFlag
em cada processo para uso com outros FMQs. Alternativamente, os objetos EventFlag
podem ser inicializados com qualquer memória compartilhada adequada.
Em geral, cada fila deve usar apenas uma fila sem bloqueio, com bloqueio de formato curto ou com bloqueio de formato longo. Não é um erro misturá-los, mas é necessária uma programação cuidadosa para obter o resultado desejado.
Marcando a memória como somente leitura
Por padrão, a memória compartilhada possui permissões de leitura e gravação. Para filas não sincronizadas ( kUnsynchronizedWrite
), o gravador pode querer remover as permissões de gravação para todos os leitores antes de distribuir os objetos MQDescriptorUnsync
. Isso garante que os outros processos não possam gravar na fila, o que é recomendado para proteção contra bugs ou mau comportamento nos processos do leitor. Se o gravador desejar que os leitores possam reconfigurar a fila sempre que usarem o MQDescriptorUnsync
para criar o lado de leitura da fila, a memória não poderá ser marcada como somente leitura. Este é o comportamento padrão do construtor `MessageQueue`. Portanto, se já houver usuários nesta fila, seu código precisará ser alterado para construir a fila com resetPointer=false
.
- Gravador: chame
ashmem_set_prot_region
com um descritor de arquivoMQDescriptor
e região definida como somente leitura (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Leitor: crie fila de mensagens com
resetPointer=false
(o padrão étrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Usando o MessageQueue
A API pública do objeto MessageQueue
é:
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()
podem ser usados para determinar quantos dados podem ser transferidos em uma única operação. Em uma fila não sincronizada:
-
availableToWrite()
sempre retorna a capacidade da fila. - Cada leitor tem 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; isso pode fazer com que
availableToRead()
retorne um valor maior que o tamanho da fila. A primeira leitura após um estouro falhará e resultará na posição de leitura desse leitor sendo definida como igual ao ponteiro de gravação atual, independentemente de o estouro ter sido relatado ou não por meio deavailableToRead()
.
Os métodos read()
e write()
retornam true
se todos os dados solicitados puderem ser (e foram) transferidos de/para a fila. Esses métodos não bloqueiam; eles são bem-sucedidos (e retornam true
) ou retornam falha ( false
) imediatamente.
Os métodos readBlocking()
e writeBlocking()
aguardam até que a operação solicitada possa ser concluída ou até atingirem o tempo limite (um valor timeOutNanos
de 0 significa nunca atingir o tempo limite).
As operações de bloqueio são implementadas usando uma palavra de sinalização de evento. Por padrão, cada fila cria e usa sua própria palavra de sinalização para suportar 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 a palavra do sinalizador de evento de uma fila pode ser obtido chamando getEventFlagWord()
, e esse ponteiro (ou qualquer ponteiro para um local de memória compartilhada adequado) pode ser usado para criar um objeto EventFlag
para passar para a forma longa de readBlocking()
e writeBlocking()
para uma fila diferente. Os parâmetros readNotification
e writeNotification
informam quais bits no sinalizador de evento devem ser usados para sinalizar leituras e gravações nessa fila. readNotification
e writeNotification
são máscaras de bits de 32 bits.
readBlocking()
espera pelos bits writeNotification
; se esse parâmetro for 0, a chamada sempre falhará. Se o valor readNotification
for 0, a chamada não falhará, mas uma leitura bem-sucedida não definirá nenhum bit de notificação. Em uma fila sincronizada, isso significaria que a chamada writeBlocking()
correspondente nunca será ativada, a menos que o bit seja definido em outro lugar. Em uma fila não sincronizada, writeBlocking()
não irá esperar (ele ainda deve ser usado para definir o bit de notificação de gravação) e é apropriado para leituras não definir nenhum bit de notificação. Da mesma forma, writeblocking()
falhará se readNotification
for 0 e uma gravação bem-sucedida definir os bits writeNotification
especificados.
Para aguardar várias filas ao mesmo tempo, use o método wait()
de um objeto EventFlag
para aguardar uma máscara de bits de notificações. O método wait()
retorna uma palavra de status com os bits que causaram o conjunto de ativação. Essas informações são então usadas para verificar se a fila correspondente tem espaço ou dados suficientes para a operação de gravação/leitura desejada e executar uma write()
/ read()
sem bloqueio. Para obter uma notificação pós-operação, use outra chamada para o método wake()
de EventFlag
. Para obter uma definição da abstração EventFlag
, consulte system/libfmq/include/fmq/EventFlag.h
.
Operações de cópia zero
As APIs read
/ write
/ readBlocking
/ writeBlocking()
pegam um ponteiro para um buffer de entrada/saída como argumento e usam chamadas memcpy()
internamente para copiar dados entre o mesmo e o buffer de anel FMQ. Para melhorar o desempenho, o Android 8.0 e versões posteriores incluem um conjunto de APIs que fornecem acesso direto do ponteiro ao buffer de anel, eliminando a necessidade de usar chamadas memcpy
.
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 método
beginWrite
fornece ponteiros básicos para o buffer de anel FMQ. Depois que os dados forem gravados, confirme-os usandocommitWrite()
. Os métodosbeginRead
/commitRead
agem da mesma maneira. - Os métodos
beginRead
/Write
tomam como entrada o número de mensagens a serem lidas/escritas e retornam um booleano indicando se a leitura/escrita é possível. Se a leitura ou gravação for possível, a estruturamemTx
será preenchida com ponteiros base que podem ser usados para acesso direto do ponteiro à memória compartilhada do buffer de anel. - A estrutura
MemRegion
contém detalhes sobre um bloco de memória, incluindo o ponteiro base (endereço base do bloco de memória) e o comprimento em termos deT
(comprimento do bloco de memória em termos do tipo de fila de mensagens definido por HIDL). - A estrutura
MemTransaction
contém duas estruturasMemRegion
, afirst
esecond
, pois uma leitura ou gravação no buffer de anel pode exigir um retorno ao início da fila. Isso significaria que dois ponteiros base são necessários para ler/gravar dados no buffer de anel FMQ.
Para obter o endereço base e o comprimento de uma estrutura 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 obter referências ao primeiro e segundo MemRegion
s dentro de um objeto MemTransaction
:
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 também fazem parte de MemTransaction
:
-
T* getSlot(size_t idx);
Retorna um ponteiro para o slotidx
dentro dosMemRegions
que fazem parte deste objetoMemTransaction
. Se o objetoMemTransaction
estiver representando as regiões de memória para ler/gravar N itens do tipo T, então o intervalo válido deidx
estará entre 0 e N-1. -
bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
Escreva itensnMessages
do tipo T nas regiões de memória descritas pelo objeto, começando no índicestartIdx
. Este método usamemcpy()
e não deve ser usado para uma operação de cópia zero. Se o objetoMemTransaction
representa memória para ler/gravar N itens do tipo T, então o intervalo válido deidx
está entre 0 e N-1. -
bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Método auxiliar para ler itensnMessages
do tipo T das regiões de memória descritas pelo objeto a partir destartIdx
. Este método usamemcpy()
e não deve ser usado para uma operação de cópia zero.
Enviando a fila por HIDL
Do lado da criação:
- Crie o objeto de fila de mensagens conforme descrito acima.
- Verifique se o objeto é válido com
isValid()
. - Se você estiver aguardando em várias filas passando um
EventFlag
no formato longo dereadBlocking()
/writeBlocking()
, poderá extrair o ponteiro do sinalizador de evento (usandogetEventFlagWord()
) de um objetoMessageQueue
que foi inicializado para criar o sinalizador, e use esse sinalizador para criar o objetoEventFlag
necessário. - Use o método
MessageQueue
getDesc()
para obter um objeto descritor. - No arquivo
.hal
, forneça ao método um parâmetro do tipofmq_sync
ou fmq_unsync
onde T
é um tipo adequado definido por HIDL. Use isto para enviar o objeto retornado porgetDesc()
para o processo de recebimento.
Do lado receptor:
- Use o objeto descritor para criar um objeto
MessageQueue
. Certifique-se de usar o mesmo tipo de fila e tipo de dados, ou o modelo não será compilado. - Se você extraiu um sinalizador de evento, extraia o sinalizador do objeto
MessageQueue
correspondente no processo de recebimento. - Use o objeto
MessageQueue
para transferir dados.