说明
:转载自官方英文+中文版https://source.android.com/devices/architecture/hidl/fmq
Fast Message Queue (FMQ) 快速消息队列
HIDL’s remote procedure call (RPC) infrastructure uses Binder mechanisms, meaning calls involve overhead, require kernel operations, and may trigger scheduler action. However, for cases where data must be transferred between processes with less overhead and no kernel involvement, the Fast Message Queue (FMQ) system is used.
FMQ creates message queues with the desired properties. AnMQDescriptorSync
orMQDescriptorUnsync
object can be sent over a HIDL RPC call and used by the receiving process to access the message queue.
! Fast Message Queues are supported only in C++.
HIDL 的远程过程调用 (RPC) 基础架构使用 Binder 机制,这意味着调用涉及开销、需要内核操作,并且可以触发调度程序操作。不过,对于必须在开销较小且无内核参与的进程之间传输数据的情况,则使用快速消息队列 (FMQ) 系统。
FMQ 会创建具有所需属性的消息队列。MQDescriptorSync
或 MQDescriptorUnsync
对象可通过 HIDL RPC 调用发送,并可供接收进程用于访问消息队列。
! 仅 C++ 支持快速消息队列。 |
MessageQueue types 类型
Android supports two queue types (known as flavors):
- Unsynchronized queues are allowed to overflow, and can have many readers; each reader must read data in time or lose it.
- Synchronized queues are not allowed to overflow, and can have only one reader.
Both queue types are not allowed to underflow (read from an empty queue will fail) and can only have one writer.
Android 支持两种队列类型(称为“风格”):
- 未同步队列:可以溢出,并且可以有多个读取器;每个读取器都必须及时读取数据,否则数据将会丢失。
- 已同步队列:不能溢出,并且只能有一个读取器。
这两种队列都不能下溢(从空队列进行读取将会失败),并且只能有一个写入器。
Unsynchronized 未同步
An unsynchronized queue has only one writer, but can have any number of readers. There is one write position for the queue; however, each reader keeps track of its own independent read position.
Writes to the queue always succeed (are not checked for overflow) as long as they are no larger than the configured queue capacity (writes larger than the queue capacity fail immediately). As each reader may have a different read position, rather than waiting for every reader to read every piece of data, data is allowed to fall off the queue whenever new writes need the space.
Reads are responsible for retrieving data before it falls off the end of the queue. A read that attempts to read more data than is available either fails immediately (if nonblocking) or waits for enough data to be available (if blocking). A read that attempts to read more data than the queue capacity always fails immediately.
If a reader fails to keep up with the writer, so that the amount of data written and not yet read by that reader is larger than the queue capacity, the next read does not return data; instead, it resets the reader’s read position to equal the latest write position then returns failure. If the data available to read is checked after overflow but before the next read, it shows more data available to read than the queue capacity, indicating overflow has occurred. (If the queue overflows between checking available data and attempting to read that data, the only indication of overflow is that the read fails.)
未同步队列只有一个写入器,但可以有任意多个读取器。此类队列有一个写入位置;不过,每个读取器都会跟踪各自的独立读取位置。
对此类队列执行写入操作一定会成功(不会检查是否出现溢出情况),但前提是写入的内容不超出配置的队列容量(如果写入的内容超出队列容量,则操作会立即失败)。由于各个读取器的读取位置可能不同,因此每当新的写入操作需要空间时,系统都允许数据离开队列,而无需等待每个读取器读取每条数据。
读取操作负责在数据离开队列末尾之前对其进行检索。如果读取操作尝试读取的数据超出可用数据量,则该操作要么立即失败(如果非阻塞),要么等到有足够多的可用数据时(如果阻塞)。如果读取操作尝试读取的数据超出队列容量,则读取一定会立即失败。
如果某个读取器的读取速度无法跟上写入器的写入速度,则写入的数据量和该读取器尚未读取的数据量加在一起会超出队列容量,这会导致下一次读取不会返回数据;相反,该读取操作会将读取器的读取位置重置为等于最新的写入位置,然后返回失败。如果在发生溢出后但在下一次读取之前,系统查看可供读取的数据,则会显示可供读取的数据超出了队列容量,这表示发生了溢出。(如果队列溢出发生在系统查看可用数据和尝试读取这些数据之间,则溢出的唯一表征就是读取操作失败。)
Synchronized 已同步
A synchronized queue has one writer and one reader with a single write position and a single read position. It is impossible to write more data than the queue has space for or read more data than the queue currently holds. Depending on whether the blocking or nonblocking write or read function is called, attempts to exceed available space or data either return failure immediately or block until the desired operation can be completed. Attempts to read or write more data than the queue capacity will always fail immediately.
已同步队列有一个写入器和一个读取器,其中写入器有一个写入位置,读取器有一个读取位置。写入的数据量不可能超出队列可提供的空间;读取的数据量不可能超出队列当前存在的数据量。如果尝试写入的数据量超出可用空间或尝试读取的数据量超出现有数据量,则会立即返回失败,或会阻塞到可以完成所需操作为止,具体取决于调用的是阻塞还是非阻塞写入或读取函数。如果尝试读取或尝试写入的数据量超出队列容量,则读取或写入操作一定会立即失败。
Setting up an FMQ 设置 FMQ
A message queue requires multiple
MessageQueue
objects: one to be written to, and one or more to be read from. There is no explicit configuration of which object is used for writing or reading; it is up to the user to ensure that no object is used for both reading and writing, that there is at most one writer, and, for synchronized queues, that there is at most one reader.
一个消息队列需要多个 MessageQueue
对象:一个对象用作数据写入目标位置,以及一个或多个对象用作数据读取来源。没有关于哪些对象用于写入数据或读取数据的显式配置;用户需负责确保没有对象既用于读取数据又用于写入数据,也就是说最多只有一个写入器,并且对于已同步队列,最多只有一个读取器。
Creating the first MessageQueue object 创建第一个 MessageQueue 对象
A message queue is created and configured with a single call:
#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 */);
- The
MessageQueue<T, flavor>(numElements)
initializer creates and initializes an object that supports the message queue functionality.- The
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
initializer creates and initializes an object that supports the message queue functionality with blocking.flavor
can be eitherkSynchronizedReadWrite
for a synchronized queue orkUnsynchronizedWrite
for an unsynchronized queue.uint16_t
(in this example) can be any HIDL-defined type that does not involve nested buffers (nostring
orvec
types), handles, or interfaces.kNumElementsInQueue
indicates the size of queue in number of entries; it determines the size of shared memory buffer that will be allocated for the queue.
通过单个调用创建并配置消息队列:
#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 */);
MessageQueue<T, flavor>(numElements)
初始化程序负责创建并初始化支持消息队列功能的对象。MessageQueue<T, flavor>(numElements, configureEventFlagWord)
初始化程序负责创建并初始化支持消息队列功能和阻塞的对象。flavor
可以是kSynchronizedReadWrite
(对于已同步队列)或kUnsynchronizedWrite
(对于未同步队列)。uint16_t
(在本示例中)可以是任意不涉及嵌套式缓冲区(无string
或vec
类型)、句柄或接口的 HIDL 定义的类型。kNumElementsInQueue
表示队列的大小(以条目数表示);它用于确定将为队列分配的共享内存缓冲区的大小。
Creating the second MessageQueue object 创建第二个 MessageQueue 对象
The second side of the message queue is created using an
MQDescriptor
object obtained from the first side. TheMQDescriptor
object is sent over a HIDL RPC call to the process that will hold the second end of the message queue. TheMQDescriptor
contains information about the queue, including:
- Information to map the buffer and write pointer.
- Information to map the read pointer (if the queue is synchronized).
- Information to map the event flag word (if the queue is blocking).
- Object type (
<T, flavor>
), which includes the HIDL-defined type of queue elements and the queue flavor (synchronized or unsynchronized).
TheMQDescriptor
object can be used to construct aMessageQueue object
:MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, >flavor>& Desc, bool resetPointers)
The
resetPointers
parameter indicates whether to reset the read and write positions to 0 while creating thisMessageQueue
object. In an unsynchronized queue, the read position (which is local to eachMessageQueue
object in unsynchronized queues) is always set to 0 during creation. Typically, theMQDescriptor
is initialized during creation of the first message queue object. For extra control over the shared memory, you can set up theMQDescriptor
manually (MQDescriptor
is defined in system/libhidl/base/include/hidl/MQDescriptor.h) then create everyMessageQueue
object as described in this section.
使用从消息队列的第一侧获取的 MQDescriptor
对象创建消息队列的第二侧。通过 HIDL RPC 调用将 MQDescriptor
对象发送到将容纳消息队列末端的进程。MQDescriptor
包含该队列的相关信息,其中包括:
- 用于映射缓冲区和写入指针的信息。
- 用于映射读取指针的信息(如果队列已同步)。
- 用于映射事件标记字词的信息(如果队列是阻塞队列)。
- 对象类型 (
<T, flavor>
),其中包含 HIDL 定义的队列元素类型 和队列风格(已同步或未同步)。
MQDescriptor
对象可用于构建 MessageQueue
对象:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
resetPointers
参数表示是否在创建此 MessageQueue
对象时将读取和写入位置重置为 0。在未同步队列中,读取位置(在未同步队列中,是每个 MessageQueue
对象的本地位置)在此对象创建过程中始终设为 0。通常,MQDescriptor
是在创建第一个消息队列对象过程中初始化的。要对共享内存进行额外的控制,您可以手动设置 MQDescriptor
(MQDescriptor
是在 system/libhidl/base/include/hidl/MQDescriptor.h 中定义的),然后按照本部分所述内容创建每个 MessageQueue
对象。
Blocking queues and event flags 阻塞队列和事件标记
By default, queues do not support blocking reads/writes. There are two kinds of blocking read/write calls:
Short form, with three parameters (data pointer, number of items, timeout). Supports blocking on individual read/write operations on a single queue. When using this form, the queue will handle the event flag and bitmasks internally, and the first message queue object must be initialized with a second parameter of true. For example:
// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, >k UnsynchronizedWrite> (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, theEventFlag
can be supplied explicitly in eachreadBlocking()
andwriteBlocking()
call. One of the queues may be initialized with an internal event flag, which must then be extracted from that queue’sMessageQueue
objects usinggetEventFlagWord()
and used to createEventFlag
objects in each process for use with other FMQs. Alternatively, theEventFlag
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.
默认情况下,队列不支持阻塞读取/写入。有两种类型的阻塞读取/写入调用:
- 短格式:有三个参数(数据指针、项数、超时)。支持阻塞针对单个队列的各个读取/写入操作。在使用这种格式时,队列将在内部处理事件标记和位掩码,并且第一个消息队列对象 必须初始化为第二个参数为 true。例如:
// For an unsynchronized FMQ that supports blocking mFmqUnsynchronizedBlocking = new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite> (kNumElementsInQueue, true /* enable blocking operations */);
- 长格式:有六个参数(包括事件标记和位掩码)。支持在多个队列之间使用共享
EventFlag
对象,并允许指定要使用的通知位掩码。在这种情况下,必须为每个读取和写入调用提供事件标记和位掩码。
对于长格式,可在每个 readBlocking()
和 writeBlocking()
调用中显式提供 EventFlag
。可以将其中一个队列初始化为包含一个内部事件标记,如果是这样,则必须使用 getEventFlagWord()
从相应队列的 MessageQueue
对象中提取该标记,以用于在每个进程中创建与其他 FMQ 一起使用的 EventFlag
对象。或者,可以将 EventFlag
对象初始化为具有任何合适的共享内存。
一般来说,每个队列都应只使用以下三项之一:非阻塞、短格式阻塞,或长格式阻塞。混合使用也不算是错误;但要获得理想结果,则需要谨慎地进行编程。
Using the MessageQueue 使用 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()
andavailableToRead()
can be used to determine how much data can be transferred in a single operation. In an unsynchronized queue:
availableToWrite()
always returns the capacity of the queue.- Each reader has its own read position and does its own calculation for
availableToRead()
.- From the point of view of a slow reader, the queue is allowed to overflow; this may result in
availableToRead()
returning a value larger than the size of the queue. The first read after an overflow will fail and result in the read position for that reader being set equal to the current write pointer, whether or not the overflow was reported throughavailableToRead()
.
Theread()
andwrite()
methods returntrue
if all requested data could be (and was) transferred to/from the queue. These methods do not block; they either succeed (and returntrue
), or return failure (false
) immediately.
ThereadBlocking()
andwriteBlocking()
methods wait until the requested operation can be completed, or until they timeout (atimeOutNanos
value of 0 means never timeout).
Blocking operations are implemented using an event flag word. By default, each queue creates and uses its own flag word to support the short form ofreadBlocking()
andwriteBlocking()
. It is possible for multiple queues to share a single word, so that a process can wait on writes or reads to any of the queues. A pointer to a queue’s event flag word can be obtained by callinggetEventFlagWord()
, and that pointer (or any pointer to a suitable shared memory location) can be used to create anEventFlag
object to pass into the long form ofreadBlocking()
andwriteBlocking()
for a different queue. ThereadNotification
andwriteNotification
parameters tell which bits in the event flag should be used to signal reads and writes on that queue.readNotification
andwriteNotification
are 32-bit bitmasks.
readBlocking()
waits on thewriteNotification
bits; if that parameter is 0, the call always fails. If thereadNotification
value is 0, the call will not fail, but a successful read will not set any notification bits. In a synchronized queue, this would mean that the correspondingwriteBlocking()
call will never wake up unless the bit is set elsewhere. In an unsynchronized queue,writeBlocking()
will not wait (it should still be used to set the write notification bit), and it is appropriate for reads to not set any notification bits. Similarly,writeblocking()
will fail ifreadNotification
is 0, and a successful write sets the specifiedwriteNotification
bits.
To wait on multiple queues at once, use anEventFlag
object’swait()
method to wait on a bitmask of notifications. Thewait()
method returns a status word with the bits that caused the wake up set. This information is then used to verify the corresponding queue has enough space or data for the desired write/read operation and perform a nonblockingwrite()
/read()
. To get a post operation notification, use another call to theEventFlag
'swake()
method. For a definition of theEventFlag
abstraction, refer to system/libfmq/include/fmq/EventFlag.h.
availableToWrite()
和 availableToRead()
可用于确定在一次操作中可传输的数据量。在未同步队列中:
availableToWrite()
始终返回队列容量。- 每个读取器都有自己的读取位置,并会针对 availableToRead() 进行自己的计算。
- 如果是读取速度缓慢的读取器,队列可以溢出,这可能会导致
availableToRead()
返回的值大于队列的大小。发生溢出后进行的第一次读取操作将会失败,并且会导致相应读取器的读取位置被设为等于当前写入指针,无论是否通过availableToRead()
报告了溢出都是如此。
如果所有请求的数据都可以(并已)传输到队列/从队列传出,则 read()
和 write()
方法会返回 true
。这些方法不会阻塞;它们要么成功(并返回 true
),要么立即返回失败 (false
)。
readBlocking()
和 writeBlocking()
方法会等到可以完成请求的操作,或等到超时(timeOutNanos
值为 0 表示永不超时)。
阻塞操作使用事件标记字词来实现。默认情况下,每个队列都会创建并使用自己的标记字词来支持短格式的 readBlocking()
和 writeBlocking()
。多个队列可以共用一个字词,这样一来,进程就可以等待对任何队列执行写入或读取操作。可以通过调用 getEventFlagWord()
获得指向队列事件标记字词的指针,此类指针(或任何指向合适的共享内存位置的指针)可用于创建 EventFlag
对象,以传递到其他队列的长格式 readBlocking()
和 writeBlocking()
。readNotification
和 writeNotification
参数用于指示事件标记中的哪些位应该用于针对相应队列发出读取和写入信号。readNotification
和 writeNotification
是 32 位的位掩码。
readBlocking()
会等待 writeNotification
位;如果该参数为 0,则调用一定会失败。如果 readNotification
值为 0,则调用不会失败,但成功的读取操作将不会设置任何通知位。在已同步队列中,这意味着相应的 writeBlocking()
调用一定不会唤醒,除非已在其他位置对相应的位进行设置。在未同步队列中,writeBlocking()
将不会等待(它应仍用于设置写入通知位),而且对于读取操作来说,不适合设置任何通知位。同样,如果 readNotification
为 0,writeblocking()
将会失败,并且成功的写入操作会设置指定的 writeNotification
位。
要一次等待多个队列,请使用 EventFlag
对象的 wait()
方法来等待通知的位掩码。wait()
方法会返回一个状态字词以及导致系统设置唤醒的位。然后,该信息可用于验证相应的队列是否有足够的控件或数据来完成所需的写入/读取操作,并执行非阻塞 write()
/read()
。要获取操作后通知,请再次调用 EventFlag
的 wake()
方法。有关 EventFlag
抽象的定义,请参阅 system/libfmq/include/fmq/EventFlag.h。
Zero copy operations 零复制操作
The
read
/write
/readBlocking
/writeBlocking()
APIs take a pointer to an input/output buffer as an argument and usememcpy()
calls internally to copy data between the same and the FMQ ring buffer. To improve performance, Android 8.0 and higher include a set of APIs that provide direct pointer access into the ring buffer, eliminating the need to usememcpy
calls.
Use the following public APIs for zero copy FMQ 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);
- The
beginWrite
method provides base pointers into the FMQ ring buffer. After the data is written, commit it usingcommitWrite()
. ThebeginRead
/commitRead
methods act the same way.- The
beginRead
/Write
methods take as input the number of messages to be read/written and return a boolean indicating if the read/write is possible. If the read or write is possible thememTx
struct is populated with base pointers that can be used for direct pointer access into the ring buffer shared memory.- The
MemRegion
struct contains details about a block of memory, including the base pointer (base address of the memory block) and the length in terms of T (length of the memory block in terms of the HIDL-defined type of the message queue).- The
MemTransaction
struct contains twoMemRegion
structs,first
andsecond
as a read or write into the ring buffer may require a wrap around to the beginning of the queue. This would mean that two base pointers are needed to read/write data into the FMQ ring buffer.
To get the base address and length from aMemRegion
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
To get references to the first and second
MemRegions
within aMemTransaction
object:const MemRegion& getFirstRegion(); // get a reference to the first >MemRegion const MemRegion& getSecondRegion(); // get a reference to the >second MemRegion
Example write to the FMQ using zero copy APIs:
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 }
The following helper methods are also part of
MemTransaction
:
T* getSlot(size_t idx)
;
Returns a pointer to slotidx
within theMemRegions
that are part of thisMemTransaction
object. If theMemTransaction
object is representing the memory regions to read/write N items of type T, then the valid range ofidx
is between 0 and N-1.bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
WritenMessages
items of type T into the memory regions described by the object, starting from indexstartIdx
. This method usesmemcpy()
and is not to meant to be used for a zero copy operation. If theMemTransaction
object represents memory to read/write N items of type T, then the valid range ofidx
is between 0 and N-1.bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Helper method to readnMessages
items of type T from the memory regions described by the object starting fromstartIdx
. This method usesmemcpy()
and is not meant to be used for a zero copy operation.
read
/write
/readBlocking
/writeBlocking()
API 会将指向输入/输出缓冲区的指针作为参数,并在内部使用 memcpy()
调用,以便在相应缓冲区和 FMQ 环形缓冲区之间复制数据。为了提高性能,Android 8.0 及更高版本包含一组 API,这些 API 可提供对环形缓冲区的直接指针访问,这样便无需使用 memcpy
调用。
使用以下公共 API 执行零复制 FMQ 操作:
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);
beginWrite
方法负责提供用于访问 FMQ 环形缓冲区的基址指针。在数据写入之后,使用commitWrite()
提交数据。beginRead
/commitRead
方法的运作方式与之相同。beginRead
/Write
方法会将要读取/写入的消息条数视为输入,并会返回一个布尔值来指示是否可以执行读取/写入操作。如果可以执行读取或写入操作,则memTx
结构体中会填入基址指针,这些指针可用于对环形缓冲区共享内存进行直接指针访问。MemRegion
结构体包含有关内存块的详细信息,其中包括基础指针(内存块的基址)和以T
表示的长度(以 HIDL 定义的消息队列类型表示的内存块长度)。MemTransaction
结构体包含两个MemRegion
结构体(first
和second
),因为对环形缓冲区执行读取或写入操作时可能需要绕回到队列开头。这意味着,要对 FMQ 环形缓冲区执行数据读取/写入操作,需要两个基址指针。
从 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
获取对 MemTransaction
对象内的第一个和第二个 MemRegion
的引用:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion
const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
使用零复制 API 写入 FMQ 的示例:
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
}
以下辅助方法也是 MemTransaction 的一部分:
T* getSlot(size_t idx);
返回一个指针,该指针指向属于此MemTransaction
对象一部分的MemRegions
内的槽位idx
。如果MemTransaction
对象表示要读取/写入 N 个类型为 T 的项目的内存区域,则idx
的有效范围在 0 到 N-1 之间。bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
将nMessages
个类型为 T 的项目写入到该对象描述的内存区域,从索引startIdx
开始。此方法使用memcpy()
,但并非旨在用于零复制操作。如果MemTransaction
对象表示要读取/写入 N 个类型为 T 的项目的内存区域,则 idx 的有效范围在 0 到 N-1 之间。bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
一种辅助方法,用于从该对象描述的内存区域读取nMessages
个类型为 T 的项目,从索引startIdx
开始。此方法使用memcpy()
,但并非旨在用于零复制操作。
Sending the queue over HIDL 通过 HIDL 发送队列
On the creating side:
- Create message queue object as described above.
- Verify the object is valid with
isValid()
.- If you will be waiting on multiple queues by passing an
EventFlag
into the long form ofreadBlocking()
/writeBlocking()
, you can extract the event flag pointer (usinggetEventFlagWord()
) from aMessageQueue
object that was initialized to create the flag, and use that flag to create the necessaryEventFlag
object.- Use the
MessageQueue getDesc()
method to get a descriptor object.- In the
.hal
file, give the method a parameter of typefmq_sync
orfmq_unsync
whereT
is a suitable HIDL-defined type. Use this to send the object returned bygetDesc()
to the receiving process.
On the receiving side:
- Use the descriptor object to create a
MessageQueue
object. Be sure to use the same queue flavor and data type, or the template will fail to compile.- If you extracted an event flag, extract the flag from the corresponding
MessageQueue
object in the receiving process.- Use the
MessageQueue
object to transfer data.
在创建侧执行的操作:
- 创建消息队列对象,如上所述。
- 使用
isValid()
验证对象是否有效。 - 如果您要通过将
EventFlag
传递到长格式的readBlocking()
/writeBlocking()
来等待多个队列,则可以从经过初始化的MessageQueue
对象提取事件标记指针(使用getEventFlagWord()
)以创建标记,然后使用该标记创建必需的EventFlag
对象。 - 使用
MessageQueue
getDesc()
方法获取描述符对象。 - 在
.hal
文件中,为某个方法提供一个类型为fmq_sync
或fmq_unsync
的参数,其中T
是 HIDL 定义的一种合适类型。使用此方法将getDesc()
返回的对象发送到接收进程。
在接收侧执行的操作:
- 使用描述符对象创建
MessageQueue
对象。务必使用相同的队列风格和数据类型,否则将无法编译模板。 - 如果您已提取事件标记,则在接收进程中从相应的
MessageQueue
对象提取该标记。 - 使用
MessageQueue
对象传输数据。