Infrastruktur panggilan prosedur jarak jauh (RPC) HIDL menggunakan mekanisme Binder, artinya panggilan melibatkan overhead, memerlukan operasi kernel, dan dapat memicu tindakan penjadwal. Namun, untuk kasus di mana data harus ditransfer antar proses dengan overhead yang lebih sedikit dan tanpa keterlibatan kernel, sistem Fast Message Queue (FMQ) digunakan.
FMQ membuat antrian pesan dengan properti yang diinginkan. MQDescriptorSync
atau MQDescriptorUnsync
dapat dikirim melalui panggilan HIDL RPC dan digunakan oleh proses penerima untuk mengakses antrian pesan.
Antrean Pesan Cepat hanya didukung di C++ dan pada perangkat yang menjalankan Android 8.0 dan lebih tinggi.
Jenis Antrian Pesan
Android mendukung dua jenis antrian (dikenal sebagai flavor ):
- Antrian yang tidak disinkronkan dibiarkan meluap, dan dapat memiliki banyak pembaca; setiap pembaca harus membaca data tepat waktu atau kehilangannya.
- Antrian yang disinkronkan tidak boleh meluap, dan hanya dapat memiliki satu pembaca.
Kedua jenis antrian tidak boleh underflow (membaca dari antrian kosong akan gagal) dan hanya dapat memiliki satu penulis.
Tidak disinkronkan
Antrian yang tidak disinkronkan hanya memiliki satu penulis, tetapi dapat memiliki sejumlah pembaca. Ada satu posisi tulis untuk antrian; namun, setiap pembaca melacak posisi membaca independennya sendiri.
Penulisan ke antrean selalu berhasil (tidak diperiksa untuk luapan) selama tidak lebih besar dari kapasitas antrean yang dikonfigurasi (penulisan lebih besar dari kapasitas antrean langsung gagal). Karena setiap pembaca mungkin memiliki posisi baca yang berbeda, daripada menunggu setiap pembaca membaca setiap bagian data, data dibiarkan keluar dari antrian setiap kali penulisan baru membutuhkan ruang.
Baca bertanggung jawab untuk mengambil data sebelum jatuh dari akhir antrian. Pembacaan yang mencoba membaca lebih banyak data daripada yang tersedia akan langsung gagal (jika tidak memblokir) atau menunggu data yang cukup tersedia (jika memblokir). Pembacaan yang mencoba membaca lebih banyak data daripada kapasitas antrian selalu langsung gagal.
Jika pembaca gagal mengikuti penulis, sehingga jumlah data yang ditulis dan belum dibaca oleh pembaca itu lebih besar dari kapasitas antrian, pembacaan berikutnya tidak mengembalikan data; alih-alih, ini mengatur ulang posisi baca pembaca agar sama dengan posisi tulis terakhir lalu mengembalikan kegagalan. Jika data yang tersedia untuk dibaca diperiksa setelah luapan tetapi sebelum pembacaan berikutnya, ini menunjukkan lebih banyak data yang tersedia untuk dibaca daripada kapasitas antrian, yang menunjukkan telah terjadi luapan. (Jika antrian meluap antara memeriksa data yang tersedia dan mencoba membaca data itu, satu-satunya indikasi meluap adalah bahwa pembacaan gagal.)
Disinkronkan
Antrian yang disinkronkan memiliki satu penulis dan satu pembaca dengan satu posisi tulis dan satu posisi baca. Tidak mungkin untuk menulis lebih banyak data daripada antrian yang memiliki ruang untuk atau membaca lebih banyak data daripada antrian saat ini. Tergantung pada apakah memblokir atau nonblocking menulis atau membaca fungsi dipanggil, upaya untuk melebihi ruang atau data yang tersedia baik mengembalikan kegagalan segera atau memblokir sampai operasi yang diinginkan dapat diselesaikan. Upaya untuk membaca atau menulis lebih banyak data daripada kapasitas antrian akan selalu segera gagal.
Menyiapkan FMQ
Antrian pesan memerlukan beberapa objek MessageQueue
: satu untuk ditulis, dan satu atau lebih untuk dibaca. Tidak ada konfigurasi eksplisit objek mana yang digunakan untuk menulis atau membaca; terserah pengguna untuk memastikan bahwa tidak ada objek yang digunakan untuk membaca dan menulis, bahwa ada paling banyak satu penulis, dan, untuk antrian yang disinkronkan, ada paling banyak satu pembaca.
Membuat objek MessageQueue pertama
Antrian pesan dibuat dan dikonfigurasi dengan satu panggilan:
#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)
membuat dan menginisialisasi objek yang mendukung fungsionalitas antrian pesan. -
MessageQueue<T, flavor>(numElements, configureEventFlagWord)
membuat dan menginisialisasi objek yang mendukung fungsionalitas antrian pesan dengan pemblokiran. -
flavor
dapat berupakSynchronizedReadWrite
untuk antrian yang disinkronkan ataukUnsynchronizedWrite
untuk antrian yang tidak disinkronkan. -
uint16_t
(dalam contoh ini) dapat berupa tipe terdefinisi HIDL yang tidak melibatkan buffer bersarang (tanpa tipestring
atauvec
), pegangan, atau antarmuka. -
kNumElementsInQueue
menunjukkan ukuran antrian dalam jumlah entri; itu menentukan ukuran buffer memori bersama yang akan dialokasikan untuk antrian.
Membuat objek MessageQueue kedua
Sisi kedua dari antrian pesan dibuat menggunakan objek MQDescriptor
yang diperoleh dari sisi pertama. Objek MQDescriptor
dikirim melalui panggilan HIDL RPC ke proses yang akan menahan ujung kedua antrian pesan. MQDescriptor
berisi informasi tentang antrian, termasuk:
- Informasi untuk memetakan buffer dan menulis pointer.
- Informasi untuk memetakan penunjuk baca (jika antrian disinkronkan).
- Informasi untuk memetakan kata bendera peristiwa (jika antrian menghalangi).
- Jenis objek (
<T, flavor>
), yang mencakup jenis elemen antrian yang ditentukan HIDL dan ragam antrian (disinkronkan atau tidak disinkronkan).
Objek MQDescriptor
dapat digunakan untuk membuat objek MessageQueue
:
MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)
Parameter resetPointers
menunjukkan apakah akan mereset posisi baca dan tulis ke 0 saat membuat objek MessageQueue
ini. Dalam antrian yang tidak disinkronkan, posisi baca (yang bersifat lokal untuk setiap objek MessageQueue
dalam antrian yang tidak disinkronkan) selalu disetel ke 0 selama pembuatan. Biasanya, MQDescriptor
diinisialisasi selama pembuatan objek antrian pesan pertama. Untuk kontrol ekstra atas memori bersama, Anda dapat mengatur MQDescriptor
secara manual ( MQDescriptor
didefinisikan di system/libhidl/base/include/hidl/MQDescriptor.h
) lalu buat setiap objek MessageQueue
seperti yang dijelaskan di bagian ini.
Memblokir antrian dan bendera acara
Secara default, antrian tidak mendukung pemblokiran baca/tulis. Ada dua jenis pemblokiran panggilan baca/tulis:
- Bentuk singkat , dengan tiga parameter (penunjuk data, jumlah item, batas waktu). Mendukung pemblokiran pada operasi baca/tulis individu pada satu antrian. Saat menggunakan formulir ini, antrian akan menangani bendera peristiwa dan bitmask secara internal, dan objek antrian pesan pertama harus diinisialisasi dengan parameter kedua
true
. Misalnya:// 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()
dan availableToRead()
dapat digunakan untuk menentukan berapa banyak data yang dapat ditransfer dalam satu operasi. Dalam antrian yang tidak disinkronkan:
-
availableToWrite()
selalu mengembalikan kapasitas antrian. - Setiap pembaca memiliki posisi bacanya sendiri dan melakukan perhitungannya sendiri untuk
availableToRead()
. - Dari sudut pandang pembaca lambat, antrian dibiarkan meluap; ini dapat mengakibatkan
availableToRead()
mengembalikan nilai yang lebih besar dari ukuran antrian. Pembacaan pertama setelah overflow akan gagal dan mengakibatkan posisi baca untuk pembaca tersebut disetel sama dengan penunjuk tulis saat ini, terlepas dari apakah overflow dilaporkan melaluiavailableToRead()
.
Metode read()
dan write()
mengembalikan nilai true
jika semua data yang diminta dapat (dan telah) ditransfer ke/dari antrian. Metode ini tidak memblokir; mereka berhasil (dan mengembalikan true
), atau mengembalikan kegagalan ( false
) segera.
Metode readBlocking()
dan writeBlocking()
menunggu hingga operasi yang diminta dapat diselesaikan, atau hingga timeout (nilai timeOutNanos
0 berarti tidak pernah timeout).
Operasi pemblokiran diimplementasikan menggunakan kata bendera peristiwa. Secara default, setiap antrian membuat dan menggunakan kata benderanya sendiri untuk mendukung bentuk pendek readBlocking()
dan writeBlocking()
. Beberapa antrean dapat berbagi satu kata, sehingga suatu proses dapat menunggu untuk menulis atau membaca ke salah satu antrean. Pointer ke kata event flag antrian dapat diperoleh dengan memanggil getEventFlagWord()
, dan pointer itu (atau pointer apa pun ke lokasi memori bersama yang sesuai) dapat digunakan untuk membuat objek EventFlag
untuk diteruskan ke bentuk panjang readBlocking()
dan writeBlocking()
untuk antrian yang berbeda. Parameter readNotification
dan writeNotification
memberi tahu bit mana dalam flag event yang harus digunakan untuk memberi sinyal baca dan tulis pada antrian itu. readNotification
dan writeNotification
adalah bitmask 32-bit.
readBlocking()
menunggu bit writeNotification
; jika parameter itu adalah 0, panggilan selalu gagal. Jika nilai readNotification
adalah 0, panggilan tidak akan gagal, tetapi pembacaan yang berhasil tidak akan menetapkan bit notifikasi apa pun. Dalam antrian yang disinkronkan, ini berarti bahwa panggilan writeBlocking()
yang sesuai tidak akan pernah aktif kecuali bit disetel di tempat lain. Dalam antrean yang tidak disinkronkan, writeBlocking()
tidak akan menunggu (seharusnya masih digunakan untuk menyetel bit notifikasi tulis), dan sesuai untuk pembacaan agar tidak menyetel bit notifikasi apa pun. Demikian pula, writeblocking()
akan gagal jika readNotification
adalah 0, dan penulisan yang berhasil menyetel bit writeNotification
yang ditentukan.
Untuk menunggu beberapa antrian sekaligus, gunakan metode wait()
objek EventFlag
untuk menunggu bitmask notifikasi. Metode wait()
mengembalikan kata status dengan bit yang menyebabkan set bangun. Informasi ini kemudian digunakan untuk memverifikasi antrean yang sesuai memiliki cukup ruang atau data untuk operasi tulis/baca yang diinginkan dan lakukan nonblocking write()
/ read()
. Untuk mendapatkan notifikasi pasca operasi, gunakan panggilan lain ke metode wake()
EventFlag
. Untuk definisi abstraksi EventFlag
, lihat system/libfmq/include/fmq/EventFlag.h
.
Operasi penyalinan nol
API read
/ write
/ readBlocking
/ writeBlocking()
mengambil pointer ke buffer input/output sebagai argumen dan menggunakan panggilan memcpy()
secara internal untuk menyalin data antara buffer cincin yang sama dan FMQ. Untuk meningkatkan kinerja, Android 8.0 dan yang lebih tinggi menyertakan serangkaian API yang menyediakan akses penunjuk langsung ke buffer dering, sehingga tidak perlu menggunakan panggilan memcpy
.
Gunakan API publik berikut untuk operasi FMQ tanpa salinan:
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);
- Metode
beginWrite
menyediakan pointer dasar ke buffer ring FMQ. Setelah data ditulis, komit menggunakancommitWrite()
. MetodebeginRead
/commitRead
bertindak dengan cara yang sama. - Metode
beginRead
/Write
mengambil sebagai input jumlah pesan yang akan dibaca/ditulis dan mengembalikan boolean yang menunjukkan jika read/write memungkinkan. Jika membaca atau menulis dimungkinkan, structmemTx
diisi dengan pointer dasar yang dapat digunakan untuk akses pointer langsung ke memori bersama buffer ring. -
MemRegion
berisi rincian tentang blok memori, termasuk penunjuk dasar (alamat dasar blok memori) dan panjang dalam halT
(panjang blok memori dalam hal tipe antrian pesan yang ditentukan oleh HIDL). -
MemTransaction
berisi dua structMemRegion
,first
dansecond
sebagai membaca atau menulis ke buffer cincin mungkin memerlukan membungkus ke awal antrian. Ini berarti bahwa dua pointer dasar diperlukan untuk membaca/menulis data ke dalam buffer cincin FMQ.
Untuk mendapatkan alamat dan panjang dasar dari 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
Untuk mendapatkan referensi ke MemRegion
s pertama dan kedua dalam objek MemTransaction
:
const MemRegion& getFirstRegion(); // get a reference to the first MemRegion const MemRegion& getSecondRegion(); // get a reference to the second MemRegion
Contoh menulis ke FMQ menggunakan nol copy API:
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 }
Metode pembantu berikut juga merupakan bagian dari MemTransaction
:
-
T* getSlot(size_t idx);
Mengembalikan pointer ke slotidx
dalamMemRegions
yang merupakan bagian dari objekMemTransaction
ini. Jika objekMemTransaction
mewakili wilayah memori untuk membaca/menulis N item bertipe T, maka rentangidx
yang valid adalah antara 0 dan N-1. -
bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
Tulis itemnMessages
tipe T ke dalam wilayah memori yang dijelaskan oleh objek, mulai dari indeksstartIdx
. Metode ini menggunakanmemcpy()
dan tidak dimaksudkan untuk digunakan untuk operasi penyalinan nol. Jika objekMemTransaction
mewakili memori untuk membaca/menulis N item bertipe T, maka rentangidx
yang valid adalah antara 0 dan N-1. -
bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
Metode pembantu untuk membaca itemnMessages
tipe T dari wilayah memori yang dijelaskan oleh objek mulai daristartIdx
. Metode ini menggunakanmemcpy()
dan tidak dimaksudkan untuk digunakan untuk operasi penyalinan nol.
Mengirim antrian melalui HIDL
Di sisi pembuatan:
- Buat objek antrian pesan seperti yang dijelaskan di atas.
- Verifikasi objek tersebut valid dengan
isValid()
. - Jika Anda akan menunggu di beberapa antrian dengan meneruskan
EventFlag
ke dalam bentuk panjangreadBlocking()
/writeBlocking()
, Anda dapat mengekstrak penunjuk flag peristiwa (menggunakangetEventFlagWord()
) dari objekMessageQueue
yang diinisialisasi untuk membuat flag, dan gunakan flag itu untuk membuat objekEventFlag
yang diperlukan. - Gunakan metode
MessageQueue
getDesc()
untuk mendapatkan objek deskriptor. - Dalam file
.hal
, berikan metode parameter tipefmq_sync
atau fmq_unsync
di mana T
adalah tipe terdefinisi HIDL yang sesuai. Gunakan ini untuk mengirim objek yang dikembalikan olehgetDesc()
ke proses penerima.
Di sisi penerima:
- Gunakan objek deskriptor untuk membuat objek
MessageQueue
. Pastikan untuk menggunakan ragam antrian dan tipe data yang sama, atau template akan gagal dikompilasi. - Jika Anda mengekstrak bendera peristiwa, ekstrak bendera dari objek
MessageQueue
yang sesuai dalam proses penerimaan. - Gunakan objek
MessageQueue
untuk mentransfer data.