Jika Anda mencari dukungan AIDL, lihat juga FMQ dengan AIDL .
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. Objek MQDescriptorSync
atau MQDescriptorUnsync
dapat dikirim melalui panggilan HIDL RPC dan digunakan oleh proses penerimaan untuk mengakses antrian pesan.
Antrean Pesan Cepat hanya didukung di C++ dan pada perangkat yang menjalankan Android 8.0 dan lebih tinggi.
Jenis Antrean Pesan
Android mendukung dua jenis antrean (dikenal sebagai varian ):
- Antrean yang tidak tersinkronisasi 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 diperbolehkan mengalami underflow (pembacaan dari antrian kosong akan gagal) dan hanya dapat memiliki satu penulis.
Tidak disinkronkan
Antrean yang tidak disinkronkan hanya memiliki satu penulis, namun dapat memiliki sejumlah pembaca. Ada satu posisi tulis untuk antrian; namun, setiap pembaca melacak posisi membaca independennya.
Penulisan ke antrean selalu berhasil (tidak dicentang untuk overflow) selama tidak lebih besar dari kapasitas antrean yang dikonfigurasi (penulisan yang 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 antrean setiap kali penulisan baru memerlukan ruang.
Pembaca bertanggung jawab untuk mengambil data sebelum data tersebut keluar dari antrian. Pembacaan yang mencoba membaca lebih banyak data daripada yang tersedia akan langsung gagal (jika non-pemblokiran) atau menunggu cukup data tersedia (jika memblokir). Pembacaan yang mencoba membaca lebih banyak data daripada kapasitas antrian selalu gagal.
Jika pembaca gagal mengikuti penulis, sehingga jumlah data yang ditulis dan belum dibaca oleh pembaca tersebut lebih besar dari kapasitas antrian, pembacaan berikutnya tidak mengembalikan data; sebaliknya, ia menyetel ulang posisi baca pembaca agar sama dengan posisi tulis terakhir, lalu mengembalikan kegagalan. Jika data tersedia untuk dibaca dicentang setelah overflow tetapi sebelum pembacaan berikutnya, ini menunjukkan lebih banyak data tersedia untuk dibaca daripada kapasitas antrian, yang menunjukkan telah terjadi overflow. (Jika antrean meluap antara pemeriksaan data yang tersedia dan upaya membaca data tersebut, satu-satunya indikasi luapan adalah pembacaan gagal.)
Pembaca antrean yang tidak disinkronkan kemungkinan besar tidak ingin menyetel ulang petunjuk baca dan tulis antrean. Jadi, saat membuat antrean dari deskriptor, pembaca harus menggunakan argumen `false` untuk parameter `resetPointers`.
Disinkronkan
Antrian tersinkronisasi memiliki satu penulis dan satu pembaca dengan satu posisi tulis dan satu posisi baca. Tidak mungkin untuk menulis lebih banyak data daripada ruang antrian atau membaca lebih banyak data daripada yang dimiliki antrian saat ini. Bergantung pada apakah fungsi tulis atau baca pemblokiran atau non-pemblokiran dipanggil, upaya untuk melebihi ruang atau data yang tersedia akan segera menghasilkan kegagalan atau memblokir hingga operasi yang diinginkan dapat diselesaikan. Upaya untuk membaca atau menulis lebih banyak data daripada kapasitas antrian akan selalu gagal.
Menyiapkan FMQ
Antrean pesan memerlukan beberapa objek MessageQueue
: satu untuk menulis, dan satu atau lebih untuk membaca. Tidak ada konfigurasi eksplisit mengenai objek mana yang digunakan untuk menulis atau membaca; terserah pada pengguna untuk memastikan bahwa tidak ada objek yang digunakan untuk membaca dan menulis, paling banyak ada satu penulis, dan, untuk antrian tersinkronisasi, paling banyak ada 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 */);
- Penginisialisasi
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 antrean tersinkronisasi ataukUnsynchronizedWrite
untuk antrean yang tidak tersinkronisasi. -
uint16_t
(dalam contoh ini) dapat berupa tipe apa pun yang ditentukan HIDL yang tidak melibatkan buffer bersarang (tidak ada tipestring
atauvec
), pegangan, atau antarmuka. -
kNumElementsInQueue
menunjukkan ukuran antrian dalam jumlah entri; ini 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 RPC HIDL atau AIDL 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 flag event (jika antrian menghalangi).
- Tipe objek (
<T, flavor>
), yang mencakup tipe elemen antrean yang ditentukan HIDL dan ragam antrean (tersinkronisasi atau tidak tersinkronisasi).
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 diatur 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 dalam system/libhidl/base/include/hidl/MQDescriptor.h
) lalu membuat 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 pendek , dengan tiga parameter (penunjuk data, jumlah item, batas waktu). Mendukung pemblokiran operasi baca/tulis individual dalam satu antrian. Saat menggunakan formulir ini, antrian akan menangani event flag 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 */);
- Bentuk panjang , dengan enam parameter (termasuk event flag dan bitmask). Mendukung penggunaan objek
EventFlag
bersama di antara beberapa antrian dan memungkinkan penentuan masker bit notifikasi yang akan digunakan. Dalam hal ini, event flag dan bitmask harus diberikan ke setiap panggilan baca dan tulis.
Untuk bentuk panjang, EventFlag
dapat diberikan secara eksplisit di setiap panggilan readBlocking()
dan writeBlocking()
. Salah satu antrean dapat diinisialisasi dengan tanda peristiwa internal, yang kemudian harus diekstraksi dari objek MessageQueue
antrean tersebut menggunakan getEventFlagWord()
dan digunakan untuk membuat objek EventFlag
di setiap proses untuk digunakan dengan FMQ lainnya. Alternatifnya, objek EventFlag
dapat diinisialisasi dengan memori bersama apa pun yang sesuai.
Secara umum, setiap antrean hanya boleh menggunakan salah satu pemblokiran non-pemblokiran, pemblokiran bentuk pendek, atau pemblokiran bentuk panjang. Mencampurnya bukanlah suatu kesalahan, tetapi diperlukan pemrograman yang cermat untuk mendapatkan hasil yang diinginkan.
Menandai memori sebagai hanya baca
Secara default, memori bersama memiliki izin membaca dan menulis. Untuk antrian yang tidak disinkronkan ( kUnsynchronizedWrite
), penulis mungkin ingin menghapus izin menulis untuk semua pembaca sebelum membagikan objek MQDescriptorUnsync
. Hal ini memastikan proses lain tidak dapat menulis ke antrian, yang direkomendasikan untuk melindungi terhadap bug atau perilaku buruk dalam proses pembaca. Jika penulis ingin pembaca dapat menyetel ulang antrean setiap kali mereka menggunakan MQDescriptorUnsync
untuk membuat sisi baca antrean, maka memori tidak dapat ditandai sebagai hanya-baca. Ini adalah perilaku default konstruktor `MessageQueue`. Jadi, jika sudah ada pengguna antrian ini, kode mereka perlu diubah untuk membuat antrian dengan resetPointer=false
.
- Penulis: panggil
ashmem_set_prot_region
dengan deskriptor fileMQDescriptor
dan wilayah disetel ke read-only (PROT_READ
):int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Pembaca: buat antrian pesan dengan
resetPointer=false
(defaultnya adalahtrue
):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
Menggunakan MessageQueue
API publik dari objek MessageQueue
adalah:
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 antrean yang tidak disinkronkan:
-
availableToWrite()
selalu mengembalikan kapasitas antrian. - Setiap pembaca memiliki posisi bacanya sendiri dan melakukan perhitungannya sendiri untuk
availableToRead()
. - Dari sudut pandang pembaca yang lambat, antrian dibiarkan meluap; Hal ini dapat mengakibatkan
availableToRead()
mengembalikan nilai yang lebih besar dari ukuran antrean. Pembacaan pertama setelah luapan akan gagal dan mengakibatkan posisi baca untuk pembaca tersebut disetel sama dengan penunjuk tulis saat ini, baik luapan tersebut dilaporkan melaluiavailableToRead()
atau tidak.
Metode read()
dan write()
mengembalikan true
jika semua data yang diminta dapat (dan telah) ditransfer ke/dari antrian. Metode-metode ini tidak menghalangi; mereka berhasil (dan mengembalikan true
), atau segera mengembalikan kegagalan ( false
).
Metode readBlocking()
dan writeBlocking()
menunggu hingga operasi yang diminta dapat diselesaikan, atau hingga waktu habis (nilai timeOutNanos
0 berarti tidak pernah habis waktu).
Operasi pemblokiran diimplementasikan menggunakan kata bendera acara. Secara default, setiap antrean membuat dan menggunakan kata benderanya sendiri untuk mendukung bentuk singkat readBlocking()
dan writeBlocking()
. Ada kemungkinan bagi beberapa antrian untuk berbagi satu kata, sehingga suatu proses dapat menunggu saat menulis atau membaca ke antrian mana pun. Sebuah penunjuk ke kata tanda peristiwa antrean dapat diperoleh dengan memanggil getEventFlagWord()
, dan penunjuk tersebut (atau penunjuk apa pun ke lokasi memori bersama yang sesuai) dapat digunakan untuk membuat objek EventFlag
untuk diteruskan ke dalam bentuk panjang readBlocking()
dan writeBlocking()
untuk antrian yang berbeda. Parameter readNotification
dan writeNotification
memberitahukan bit mana dalam flag event yang harus digunakan untuk memberi sinyal baca dan tulis pada antrian tersebut. readNotification
dan writeNotification
adalah bitmask 32-bit.
readBlocking()
menunggu bit writeNotification
; jika parameternya 0, panggilan selalu gagal. Jika nilai readNotification
adalah 0, panggilan tidak akan gagal, tetapi pembacaan yang berhasil tidak akan menyetel bit notifikasi apa pun. Dalam antrian tersinkronisasi, ini berarti bahwa panggilan writeBlocking()
yang bersangkutan tidak akan pernah aktif kecuali bitnya disetel di tempat lain. Dalam antrean yang tidak disinkronkan, writeBlocking()
tidak akan menunggu (masih harus digunakan untuk menyetel bit notifikasi tulis), dan sebaiknya pembacaan tidak menyetel bit notifikasi apa pun. Demikian pula, writeblocking()
akan gagal jika readNotification
adalah 0, dan penulisan yang berhasil akan menyetel bit writeNotification
yang ditentukan.
Untuk menunggu di beberapa antrean 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 antrian terkait memiliki cukup ruang atau data untuk operasi tulis/baca yang diinginkan dan melakukan write()
/ read()
nonblocking. 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 penunjuk ke buffer input/output sebagai argumen dan menggunakan panggilan memcpy()
secara internal untuk menyalin data antara buffer cincin FMQ dan yang sama. Untuk meningkatkan performa, Android 8.0 dan yang lebih tinggi menyertakan sekumpulan API yang menyediakan akses penunjuk langsung ke buffer ring, sehingga menghilangkan kebutuhan untuk menggunakan panggilan memcpy
.
Gunakan API publik berikut untuk operasi FMQ tanpa penyalinan:
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 penunjuk dasar ke dalam buffer cincin FMQ. Setelah data ditulis, komit menggunakancommitWrite()
. MetodebeginRead
/commitRead
bertindak dengan cara yang sama. - Metode
beginRead
/Write
mengambil input jumlah pesan yang akan dibaca/ditulis dan mengembalikan boolean yang menunjukkan apakah baca/tulis dapat dilakukan. Jika pembacaan atau penulisan dimungkinkan, structmemTx
diisi dengan pointer dasar yang dapat digunakan untuk akses penunjuk langsung ke dalam memori bersama buffer ring. - Struct
MemRegion
berisi detail tentang blok memori, termasuk penunjuk dasar (alamat dasar blok memori) dan panjangnya dalamT
(panjang blok memori dalam jenis antrian pesan yang ditentukan HIDL). - Struct
MemTransaction
berisi dua structMemRegion
,first
dansecond
karena pembacaan atau penulisan ke dalam buffer ring mungkin memerlukan penyelesaian ke awal antrian. Ini berarti bahwa dua penunjuk dasar diperlukan untuk membaca/menulis data ke dalam buffer cincin FMQ.
Untuk mendapatkan alamat dasar dan panjang 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
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 zero 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
bertipe T ke dalam wilayah memori yang dijelaskan oleh objek, mulai dari indeksstartIdx
. Metode ini menggunakanmemcpy()
dan tidak dimaksudkan untuk digunakan pada 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
bertipe T dari wilayah memori yang dijelaskan oleh objek mulai daristartIdx
. Metode ini menggunakanmemcpy()
dan tidak dimaksudkan untuk digunakan pada operasi penyalinan nol.
Mengirim antrian melalui HIDL
Di sisi pembuatan:
- Buat objek antrian pesan seperti dijelaskan di atas.
- Verifikasi objek valid dengan
isValid()
. - Jika Anda akan menunggu di beberapa antrean dengan meneruskan
EventFlag
ke dalam bentuk panjangreadBlocking()
/writeBlocking()
, Anda dapat mengekstrak penunjuk tanda peristiwa (menggunakangetEventFlagWord()
) dari objekMessageQueue
yang diinisialisasi untuk membuat tanda, dan gunakan tanda 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 yang ditentukan HIDL yang sesuai. Gunakan ini untuk mengirim objek yang dikembalikan olehgetDesc()
ke proses penerimaan.
Di pihak penerima:
- Gunakan objek deskriptor untuk membuat objek
MessageQueue
. Pastikan untuk menggunakan ragam antrean dan tipe data yang sama, atau templat akan gagal dikompilasi. - Jika Anda mengekstrak bendera peristiwa, ekstrak bendera tersebut dari objek
MessageQueue
yang sesuai dalam proses penerimaan. - Gunakan objek
MessageQueue
untuk mentransfer data.