Se estiver procurando suporte à AIDL, consulte também FMQ com AIDL.
A infraestrutura de chamada de procedimento remoto (RPC) do HIDL usa mecanismos Binder, ou seja, chamadas envolvem overhead, exigem operações de kernel e podem acionar ação do programador. No entanto, quando os dados precisam ser transferidos entre processos com menos overhead e nenhum envolvimento do kernel, o Fast Message Queue (FMQ, na sigla em inglês) é usado.
O FMQ cria filas de mensagens com as propriedades desejadas. Um
Os objetos MQDescriptorSync
ou MQDescriptorUnsync
podem ser
enviado por uma chamada RPC HIDL e usado pelo processo de recebimento para acessar o
fila de mensagens.
As filas rápidas de mensagens são compatíveis apenas com C++ e dispositivos com Android 8.0 ou mais recente.
Tipos de MessageQueue
O Android é compatível com dois tipos de fila, conhecidos como variações:
- Filas não sincronizadas podem ultrapassar o limite e podem ter vários leitores; cada leitor precisa ler os dados a tempo ou perdê-los.
- Filas sincronizadas não podem entrar em excesso e só podem ter um leitor.
Os dois tipos de fila não podem sofrer underflow (leitura de uma fila vazia) falhar) e só pode ter 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 a posição de leitura independente dele.
As gravações na fila sempre têm êxito (não são verificadas quanto à sobrecarga), desde que não sejam maiores do que a capacidade de fila configurada (gravações maiores que a a capacidade da fila falha imediatamente). Como cada leitor pode ter uma leitura diferente em vez de esperar que cada leitor leia todos os dados, dados tem permissão para 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 sejam descartados da fila. Leitura que tenta ler mais dados do que os disponíveis falhar imediatamente (se não houver bloqueio) ou esperar que dados suficientes estejam disponíveis (se bloqueio). uma leitura que tenta ler mais dados do que a capacidade da fila sempre vai falhar imediatamente.
Se um leitor não acompanhar o gravador, de modo que a quantidade de dados gravados e ainda não lidos por esse leitor for maior do que a capacidade da fila, o a próxima leitura não retorne dados; isso redefine o processo de leitura para se igualar à posição de gravação mais recente, então retorna uma falha. Se o dados disponíveis para leitura são verificados após o estouro, mas antes da próxima leitura, mostra mais dados disponíveis para leitura do que a capacidade da fila, indicando ocorreu um estouro. (Se a fila sobrecarregar entre a verificação dos dados disponíveis e tentar ler esses dados, a única indicação de estouro é que o leitura falhar.)
Os leitores de uma fila não sincronizada provavelmente não querem redefinir os ponteiros de leitura e gravação da fila. Portanto, ao criar a fila a partir do os leitores do descritor precisam usar um argumento "false" para "resetPointers" .
Sincronizado
Uma fila sincronizada tem um gravador e um leitor com uma única gravação e em uma única posição de leitura. É impossível gravar mais dados do que a fila tem espaço para ou ler mais dados do que a fila retém atualmente. Dependendo da função de gravação ou leitura, que bloqueia ou não, chamado, tenta exceder o espaço disponível ou os dados retornam falhas imediatamente ou bloquear até que a operação desejada possa ser concluída. Tentativas de ler ou gravar mais dados do que a capacidade da fila sempre falhará imediatamente.
Configurar um FMQ
Uma fila de mensagens requer vários objetos MessageQueue
: um para
ser gravados e um ou mais para serem lidos. Não há linguagem explícita
configuração de qual objeto é usado para gravação ou leitura; depende
usuário para garantir que nenhum objeto seja usado para leitura e gravação, que não
é no máximo um gravador e, para filas sincronizadas, há no máximo um
leitor de tela.
Criar 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 compatível com a funcionalidade da fila de mensagens. - O inicializador
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
cria e inicializa um objeto. que oferece suporte à funcionalidade de fila de mensagens com bloqueio. flavor
pode serkSynchronizedReadWrite
para um fila sincronizada oukUnsynchronizedWrite
para um evento fila.uint16_t
(neste exemplo) pode ser qualquer tipo definido por HIDL que não envolve buffers aninhados (semstring
ouvec
identificadores, identificadores ou interfaces.kNumElementsInQueue
indica o tamanho da fila em número de entradas ele determina o tamanho do buffer de memória compartilhada alocado para a fila.
Criar o segundo objeto MessageQueue
O segundo lado da fila de mensagens é criado usando uma
Objeto MQDescriptor
recebido do primeiro lado. A
O objeto MQDescriptor
é enviado por uma chamada RPC HIDL ou AIDL para o processo
que contém a segunda extremidade da fila de mensagens. A
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 da sinalização de evento (se a fila estiver bloqueando).
- Tipo de objeto (
<T, flavor>
), que inclui o tipo definido por HIDL de elementos de fila e a variação de fila (sincronizados ou não sincronizados).
O objeto MQDescriptor
pode ser usado para criar um
Objeto MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
O parâmetro resetPointers
indica se a leitura será redefinida
e gravar posições como 0 ao criar esse objeto MessageQueue
.
Em uma fila não sincronizada, a posição de leitura (que é local a cada
objeto MessageQueue
em filas não sincronizadas) é sempre definido como 0
durante a criação. Normalmente, o MQDescriptor
é inicializado
criação do primeiro objeto da fila de mensagens. Para ter mais controle sobre os recursos
de memória, é possível configurar MQDescriptor
manualmente
(MQDescriptor
é definido em
system/libhidl/base/include/hidl/MQDescriptor.h
).
Depois, crie cada objeto MessageQueue
conforme descrito nesta seção.
Bloquear filas e sinalizações de eventos
Por padrão, as filas não oferecem suporte a leituras/gravações de bloqueio. Há dois tipos de chamadas de leitura/gravação de bloqueio:
- Formato curto, com três parâmetros (ponteiro de dados, número de itens,
tempo limite). Suporta o bloqueio em operações individuais de leitura/gravação em uma única
fila. Ao usar esse formulário, a fila lida com a flag de evento e os bitmasks.
internamente, e o primeiro objeto da fila de mensagens precisa
ser inicializada 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 */);
- Formato longo, com seis parâmetros (inclui sinalização de evento e bitmasks).
Aceita o uso de um objeto
EventFlag
compartilhado entre várias filas e permite especificar as bitmask de notificação a serem usadas. Nesse caso, o a flag de evento e bitmasks devem ser fornecidos para cada chamada de leitura e gravação.
Para o formato longo, o EventFlag
pode ser fornecido explicitamente em
cada chamada de readBlocking()
e writeBlocking()
. Um de
as filas podem ser inicializadas com um sinalizador de evento interno, que deve ser
extraídos dos objetos MessageQueue
dessa fila usando
getEventFlagWord()
e usada para criar EventFlag
em cada processo para uso com outros FMQs. Por outro lado,
Os objetos EventFlag
podem ser inicializados com qualquer
memória.
Em geral, cada fila deve usar apenas um de vídeo ou de formato longo. Não é um erro combiná-los, mas tenha cuidado programação é necessária para obter o resultado desejado.
Marcar a recordação como somente leitura
Por padrão, a memória compartilhada tem permissões de leitura e gravação. Para dessincronizados
filas (kUnsynchronizedWrite
), talvez o gravador queira remover as permissões de gravação de todas
dos leitores antes de distribuir os objetos MQDescriptorUnsync
. Isso garante que as outras
os processos não podem gravar na fila, o que é recomendado para se proteger contra bugs ou mau comportamento em
os processos do leitor.
Se o gravador deseja que os leitores possam redefinir a fila sempre que usarem a
MQDescriptorUnsync
para criar o lado de leitura da fila, a memória não poderá ser marcada
como somente leitura. Esse é o comportamento padrão do construtor "MessageQueue". Portanto, se já existe
usuários existentes dessa fila, o código deles 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: cria fila de mensagens com
resetPointer=false
(o o padrão étrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Usar 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 um
fila dessincronizada:
availableToWrite()
sempre retorna a capacidade da fila.- Cada leitor tem sua própria posição de leitura e faz o próprio cálculo para
availableToRead()
: - Do ponto de vista de um leitor lento, a fila pode ultrapassar o limite.
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 em a posição de leitura desse leitor sendo definida igual ao ponteiro de gravação atual, se o estouro foi informado 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 o
da fila. Esses métodos não bloqueiam. eles terão sucesso (e retornarão
true
) ou retornar 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
o valor timeOutNanos
de 0 significa que 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 dar suporte à forma abreviada de
readBlocking()
e writeBlocking()
. É possível que
múltiplas filas para compartilhar uma única palavra, de modo que um processo possa aguardar gravações ou
as leituras em qualquer uma das filas. Um ponteiro para uma palavra de sinalização de evento de uma fila pode ser
obtido chamando getEventFlagWord()
, e esse ponteiro (ou qualquer
para um local adequado de memória compartilhada) pode ser usada para criar um
objeto EventFlag
a ser transmitido para a forma longa de
readBlocking()
e writeBlocking()
para uma
fila. readNotification
e writeNotification
informam quais bits na flag de evento devem ser usados para sinalizar leituras e
gravações nessa fila. readNotification
e
writeNotification
são bitmasks de 32 bits.
readBlocking()
aguarda os bits writeNotification
;
Se esse parâmetro for 0, a chamada sempre falhará. Se o
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 significa que a chamada writeBlocking()
correspondente
nunca desperta, a menos que o bit esteja configurado em outro lugar. Em uma fila dessincronizada,
O método writeBlocking()
não espera. Ele ainda deve ser usado para definir o
bit de notificação de gravação), e é apropriado que as leituras não definam nenhum
bits de notificação. Da mesma forma, writeblocking()
falhará se
readNotification
é 0, e uma gravação bem-sucedida define o valor
writeNotification
bits.
Para aguardar em várias filas de uma vez, use o método EventFlag
Método wait()
para aguardar um bitmask de notificações. A
O método wait()
retorna uma palavra de status com os bits que causaram a
configurado para acordar. Essas informações são usadas para verificar se a fila correspondente foi
espaço ou dados suficientes para a operação de gravação/leitura desejada e execute
write()
/read()
sem bloqueio. Para receber uma operação de postagem
use outra chamada para o método EventFlag
wake()
. Para uma definição de EventFlag
de abstração, consulte
system/libfmq/include/fmq/EventFlag.h
.
Nenhuma operação de cópia
A
write
/read
/readBlocking
/writeBlocking()
As APIs levam um ponteiro para um buffer de entrada/saída como argumento e usam
memcpy()
chama internamente para copiar dados entre o mesmo e o
Buffer de anel do FMQ. Para melhorar o desempenho, o Android 8.0 e versões mais recentes incluem um conjunto de
APIs que fornecem acesso direto de ponteiro ao buffer de anel, eliminando a
precisa 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 base no anel do FMQ tempo extra. Depois que os dados forem gravados, confirme-os usandocommitWrite()
. Os métodosbeginRead
/commitRead
funcionam da mesma forma. - Os métodos
beginRead
/Write
recebem como entrada o número de mensagens a serem lidas/gravadas e retornam um booleano indicando se o leitura/gravação é possível. Se a leitura ou gravação for possível, omemTx
struct é preenchido com ponteiros base que podem ser usados para ponteiro direto acesso à memória compartilhada do buffer de anel. - O struct
MemRegion
contém detalhes sobre um bloco de memória. incluindo o ponteiro base (endereço de base do bloco de memória) e o comprimento em termos deT
(comprimento do bloco de memória em termos do espaço da fila de mensagens). - O struct
MemTransaction
contém doisMemRegion
. structs,first
esecond
como uma leitura ou gravação o buffer de anel pode exigir um encapsulamento ao início da fila. Isso significa que são necessários dois ponteiros base para ler/gravar dados no FMQ buffer de anel
Para descobrir o endereço base e o comprimento de um struct 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 conseguir referências ao primeiro e ao segundo MemRegion
s em uma
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 métodos auxiliares abaixo também fazem parte de MemTransaction
:
T* getSlot(size_t idx);
Retorna um ponteiro para o slotidx
naMemRegions
que fazem parte destaMemTransaction
objeto. Se o objetoMemTransaction
estiver representando a memória regiões para ler/gravar N itens do tipo T, então o intervalo válido deidx
está entre 0 e N-1.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
Grave itensnMessages
do tipo T nas regiões de memória. descrito pelo objeto, começando pelo índicestartIdx
. Esse método usamemcpy()
e não foi criado para cópia zero operação Se o objetoMemTransaction
representar a memória para ler/gravar N itens do tipo T, então o intervalo válido deidx
será 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 do regiões de memória descritas pelo objeto começando comstartIdx
. Isso usamemcpy()
e não foi feito para ser usado para cópia zero operação
Enviar a fila por HIDL
No lado da criação:
- Crie o objeto da 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
na forma longa dereadBlocking()
/writeBlocking()
, é possível extrair o ponteiro de sinalização de evento (usandogetEventFlagWord()
) de um objetoMessageQueue
inicializado para criar a sinalização; e use essa flag para criar o objetoEventFlag
necessário. - Use o método
getDesc()
MessageQueue
para receber uma objeto descritor. - No arquivo
.hal
, forneça ao método um parâmetro do tipofmq_sync
oufmq_unsync
, em queT
é um definido pelo HIDL mais adequado. Use este campo para enviar o objeto retornado porgetDesc()
ao processo de recebimento.
No lado do destinatário:
- Use o objeto descritor para criar um objeto
MessageQueue
. Tenha use a mesma variação de fila e o mesmo tipo de dados, ou o modelo falhará compilar. - Se você extraiu uma sinalização de evento, extraia-a do
MessageQueue
no processo de recebimento. - Use o objeto
MessageQueue
para transferir dados.