Antrean Pesan Cepat (FMQ)

Jika Anda mencari dukungan AIDL, lihat juga FMQ dengan AIDL.

Infrastruktur remote procedure call (RPC) HIDL menggunakan mekanisme Binder, yang berarti panggilan melibatkan overhead, memerlukan operasi kernel, dan dapat memicu tindakan penjadwal. Namun, untuk kasus di mana data harus ditransfer antara proses dengan lebih sedikit overhead dan tanpa keterlibatan {i> kernel<i}, Antrean Pesan Cepat (FMQ) digunakan.

FMQ membuat antrean pesan dengan properti yang diinginkan. Channel Objek MQDescriptorSync atau MQDescriptorUnsync dapat dikirim melalui panggilan HIDL RPC dan digunakan oleh proses penerima untuk mengakses antrean pesan.

Antrean Pesan Cepat hanya didukung di C++ dan di perangkat menjalankan Android 8.0 dan yang lebih tinggi.

Jenis MessageQueue

Android mendukung dua jenis antrean (disebut sebagai ragam):

  • Antrean yang tidak disinkronkan diizinkan untuk melebihi batas, dan dapat memiliki banyak pembaca; setiap pembaca harus membaca data tepat waktu atau kehilangan data.
  • Antrean yang disinkronkan tidak boleh melebihi batas, dan hanya boleh memiliki satu pembaca.

Kedua jenis antrean tidak diizinkan untuk underflow (dibaca dari antrean kosong gagal) dan hanya dapat memiliki satu penulis.

Tidak disinkronkan

Sebuah antrean yang tidak tersinkronisasi hanya memiliki satu penulis, tetapi dapat memiliki pembaca. Ada satu posisi tulis untuk antrean; Namun, setiap pembaca menyimpan melacak posisi baca independennya.

Penulisan ke antrean selalu berhasil (tidak diperiksa apakah ada kelebihan) selama mereka tidak lebih besar dari kapasitas antrian yang terkonfigurasi (penulisan lebih besar dari kapasitas antrean akan langsung gagal). Karena setiap pembaca mungkin memiliki daripada menunggu setiap pembaca untuk membaca setiap bagian data, diizinkan untuk keluar dari antrean setiap kali operasi tulis baru memerlukan ruang.

Pembaca bertanggung jawab untuk mengambil data sebelum data itu jatuh pada akhir antrean. Operasi baca yang mencoba membaca lebih banyak data daripada yang tersedia gagal segera (jika tidak memblokir) atau menunggu cukup data tersedia (jika pemblokiran). Operasi baca yang selalu berupaya membaca lebih banyak data daripada kapasitas antrean gagal.

Jika pembaca gagal mengikuti perkembangan penulis, sehingga jumlah data yang ditulis dan belum dibaca oleh pembaca tersebut lebih besar dari kapasitas antrian, {i>next read<i} tidak mengembalikan data; sebagai gantinya, tindakan ini akan mereset untuk menyamakan posisi tulis terbaru lalu mengembalikan kegagalan. Jika data yang tersedia untuk dibaca diperiksa setelah {i> overflow<i} tetapi sebelum pembacaan berikutnya, menunjukkan lebih banyak data yang tersedia untuk dibaca daripada kapasitas antrean, menunjukkan terjadi tambahan. (Jika antrean melebihi pemeriksaan data yang tersedia dan mencoba membaca data tersebut, satu-satunya indikasi {i>overflow<i} adalah pembacaan gagal.)

Pembaca dari antrean yang tidak disinkronkan kemungkinan tidak ingin direset pointer baca dan tulis dari antrean. Jadi, saat membuat antrean dari pembaca deskriptor harus menggunakan argumen `false` untuk `resetPointers` .

Disinkronkan

Antrean yang disinkronkan memiliki satu penulis dan satu pembaca dengan satu penulisan satu posisi dan satu posisi baca. Tidak mungkin untuk menulis lebih banyak data dari antrean memiliki ruang untuk atau membaca lebih banyak data daripada antrean yang saat ini ada. Tergantung pada apakah fungsi operasi tulis atau baca yang memblokir atau tidak memblokir yang dipanggil, mencoba untuk melebihi ruang yang tersedia atau data yang gagal mengembalikan segera atau memblokir sampai operasi yang diinginkan dapat diselesaikan. Upaya untuk membaca atau menulis lebih banyak data daripada kapasitas antrean selalu gagal.

Menyiapkan FMQ

Antrean pesan memerlukan beberapa objek MessageQueue: satu hingga ditulis, dan satu atau lebih untuk dibaca. Tidak ada eksplisit konfigurasi objek mana yang digunakan untuk menulis atau membaca; terserah untuk memastikan bahwa tidak ada objek yang digunakan untuk membaca dan menulis, yang hanya ada satu penulis, dan untuk antrean yang disinkronkan, setidaknya ada satu pembaca.

Membuat objek MessageQueue pertama

Antrean 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 melakukan inisialisasi objek yang mendukung fungsionalitas antrean pesan.
  • Penginisialisasi MessageQueue<T, flavor>(numElements, configureEventFlagWord) membuat dan melakukan inisialisasi objek yang mendukung fungsionalitas antrean pesan dengan pemblokiran.
  • flavor dapat berupa kSynchronizedReadWrite untuk antrean tersinkronisasi atau kUnsynchronizedWrite untuk antrean.
  • uint16_t (dalam contoh ini) dapat berupa Jenis yang didefinisikan HiDL yang tidak melibatkan buffer bertingkat (tanpa string atau vec {i>handle<i}, {i>handle<i}, atau antarmuka.
  • kNumElementsInQueue menunjukkan ukuran antrean dalam jumlah entri; menentukan ukuran {i>buffer<i} memori bersama yang dialokasikan untuk antrean.

Membuat objek MessageQueue kedua

Sisi kedua antrean pesan dibuat menggunakan Objek MQDescriptor yang diperoleh dari sisi pertama. Tujuan Objek MQDescriptor dikirim melalui panggilan HIDL atau AIDL RPC ke proses yang menampung ujung kedua antrean pesan. Tujuan MQDescriptor berisi informasi tentang antrean, termasuk:

  • Informasi untuk memetakan buffer dan menulis pointer.
  • Informasi untuk memetakan pointer baca (jika antrean disinkronkan).
  • Informasi untuk memetakan kata flag peristiwa (jika antrean memblokir).
  • Jenis objek (<T, flavor>), yang mencakup Jenis yang didefinisikan HIDL dari elemen antrean dan ragam antrean (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 pembacaan dan tulis posisi ke 0 saat membuat objek MessageQueue ini. Dalam antrean yang tidak tersinkronisasi, posisi baca (yang bersifat lokal Objek MessageQueue dalam antrean yang tidak disinkronkan) selalu ditetapkan ke 0 selama pembuatan. Biasanya, MQDescriptor diinisialisasi selama pembuatan objek antrean pesan pertama. Untuk kontrol tambahan terhadap Anda dapat menyiapkan MQDescriptor secara manual (MQDescriptor ditentukan di system/libhidl/base/include/hidl/MQDescriptor.h) lalu buat setiap objek MessageQueue seperti yang dijelaskan di bagian ini.

Antrean blok dan tanda peristiwa

Secara default, antrean tidak mendukung pemblokiran baca/tulis. Ada dua jenis memblokir panggilan baca/tulis:

  • Format pendek, dengan tiga parameter (pointer data, jumlah item, waktu tunggu). Mendukung pemblokiran pada setiap operasi baca/tulis pada satu antrean. Saat menggunakan formulir ini, antrean menangani flag peristiwa dan bitmask secara internal, dan objek antrean pesan pertama harus diinisialisasi dengan parameter kedua true. Contoh:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • Format panjang, dengan enam parameter (termasuk flag peristiwa dan bitmask). Mendukung penggunaan objek EventFlag bersama di antara beberapa antrean dan memungkinkan penentuan bit mask notifikasi yang akan digunakan. Dalam hal ini, {i>event flag<i} dan bitmask harus disediakan untuk setiap panggilan baca dan tulis.

Untuk format panjang, EventFlag dapat diberikan secara eksplisit di setiap panggilan readBlocking() dan writeBlocking(). Salah satu antrean dapat diinisialisasi dengan penanda peristiwa internal, yang kemudian harus diekstrak dari objek MessageQueue antrean tersebut menggunakan getEventFlagWord() dan digunakan untuk membuat EventFlag objek dalam setiap proses untuk digunakan dengan FMQ lain. Atau, Objek EventFlag dapat diinisialisasi dengan objek bersama yang sesuai memori.

Secara umum, setiap antrean sebaiknya hanya menggunakan satu format pendek, pemblokiran, atau pemblokiran video panjang. Jangan mencampurnya, tetapi hati-hati pemrograman diperlukan untuk mendapatkan hasil yang diinginkan.

Menandai memori sebagai hanya baca

Secara default, memori bersama memiliki izin baca dan tulis. Untuk yang tidak tersinkronisasi antrean (kUnsynchronizedWrite), penulis mungkin ingin menghapus izin tulis untuk semua pembaca sebelum membagikan objek MQDescriptorUnsync. Hal ini memastikan bahwa proses tidak dapat menulis ke antrean, yang disarankan untuk melindungi data dari {i>bug<i} atau perilaku buruk dalam diproses oleh pembaca. Jika penulis ingin pembaca dapat mereset antrean setiap kali mereka menggunakan MQDescriptorUnsync untuk membuat sisi baca antrean, lalu memori tidak dapat ditandai sebagai hanya-baca. Ini adalah perilaku default konstruktor `MessageQueue`. Jadi, jika sudah ada pengguna yang ada dari antrian ini, kode mereka perlu diubah untuk menyusun antrian dengan resetPointer=false.

  • Penulis: panggil ashmem_set_prot_region dengan deskriptor file MQDescriptor dan region yang disetel ke hanya baca (PROT_READ):
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • Pembaca: buat antrean pesan dengan resetPointer=false ( defaultnya adalah true):
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

Menggunakan MessageQueue

API publik 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. Di antrean yang tidak tersinkronisasi:

  • availableToWrite() selalu menampilkan kapasitas antrean.
  • Setiap pembaca memiliki posisi bacanya sendiri dan melakukan perhitungan availableToRead().
  • Dari sudut pandang pembaca yang lambat, antrean diperbolehkan untuk meluap; hal ini dapat menyebabkan availableToRead() menampilkan nilai yang lebih besar dari ukuran antrean. Operasi baca pertama setelah {i>overflow<i} gagal dan mengakibatkan posisi baca untuk pembaca tersebut yang diatur sama dengan {i>write pointer<i} saat ini, apakah overflow dilaporkan melalui availableToRead().

Metode read() dan write() akan menampilkan true jika semua data yang diminta dapat (dan telah) ditransfer ke/dari antrean. Metode ini tidak memblokir; mereka akan berhasil (dan kembali true), atau kegagalan pengembalian (false) secara langsung.

Metode readBlocking() dan writeBlocking() menunggu hingga operasi yang diminta dapat diselesaikan, atau sampai habis waktu (atau Nilai timeOutNanos 0 berarti tidak pernah waktu tunggu habis).

Operasi pemblokiran diterapkan menggunakan kata penanda peristiwa. Secara {i>default<i}, setiap antrean membuat dan menggunakan kata penandanya sendiri untuk mendukung bentuk readBlocking() dan writeBlocking(). Anda dapat menggunakan beberapa antrean untuk berbagi satu kata, sehingga proses dapat menunggu operasi tulis atau membaca ke antrean mana pun. Pointer ke kata penanda peristiwa dari antrean bisa yang diperoleh dengan memanggil getEventFlagWord(), dan pointer tersebut (atau ke lokasi memori bersama yang sesuai) dapat digunakan untuk membuat EventFlag objek untuk diteruskan ke dalam bentuk readBlocking(), dan writeBlocking()untuk akun antrean. readNotification dan writeNotification parameter memberi tahu bit mana dalam penanda kejadian yang harus digunakan untuk memberi sinyal pembacaan dan yang menulis pada antrean tersebut. readNotification dan writeNotification adalah bitmask 32-bit.

readBlocking() menunggu pada writeNotification bit; jika parameternya 0, panggilan akan selalu gagal. Jika Nilai readNotification adalah 0, panggilan tidak gagal, tetapi pembacaan yang berhasil tidak akan menetapkan bit notifikasi apa pun. Dalam antrean yang tersinkronisasi, ini berarti bahwa panggilan writeBlocking() yang sesuai tidak pernah aktif kecuali bit diatur di tempat lain. Dalam antrean yang tidak tersinkronisasi, writeBlocking() tidak menunggu (masih harus digunakan untuk menyetel menulis bit notifikasi), dan memang sesuai untuk pembacaan bit notifikasi. Demikian pula, writeblocking() akan gagal jika readNotification adalah 0, dan operasi tulis yang berhasil menetapkan writeNotification bit.

Untuk menunggu beberapa antrean sekaligus, gunakan objek EventFlag wait() untuk menunggu bitmask notifikasi. Tujuan Metode wait() menampilkan kata status dengan bit yang menyebabkan bangun. Informasi ini kemudian digunakan untuk memverifikasi antrean yang memiliki cukup ruang atau data untuk operasi tulis/baca yang diinginkan dan lakukan write()/read() yang tidak memblokir. Untuk mendapatkan operasi pasca-operasi gunakan panggilan lain keEventFlag Metode wake(). Untuk definisi EventFlag abstraksi, mengacu pada system/libfmq/include/fmq/EventFlag.h.

Tidak ada operasi penyalinan

Tujuan readBlocking/read/write/writeBlocking() API membawa pointer ke buffer input/output sebagai argumen dan menggunakan memcpy() memanggil secara internal untuk menyalin data antara Buffering dering FMQ. Untuk meningkatkan kinerja, Android 8.0 dan yang lebih tinggi menyertakan serangkaian API yang menyediakan akses pointer langsung ke buffer ring, sehingga menghilangkan perlu menggunakan panggilan memcpy.

Gunakan API publik berikut untuk operasi FMQ zero-copy:

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 memberikan pointer dasar ke dalam cincin FMQ {i>buffer <i}(penyangga). Setelah data ditulis, commit menggunakan commitWrite(). Metode beginRead/commitRead berfungsi dengan cara yang sama.
  • Metode beginRead/Write menjadikan jumlah pesan yang akan dibaca/ditulis dan mengembalikan boolean yang menunjukkan apakah baca/tulis dimungkinkan. Jika operasi baca atau tulis memungkinkan, memTx struct diisi dengan pointer dasar yang dapat digunakan untuk pointer langsung akses ke memori bersama buffer ring.
  • Struktur MemRegion berisi detail tentang blok memori, termasuk pointer dasar (alamat dasar blok memori) dan panjang dalam istilah T (panjang blok memori dalam kaitannya dengan rentang jenis antrean pesan).
  • Struktur MemTransaction berisi dua MemRegion struct, first dan second sebagai perintah baca atau tulis buffer cincin mungkin memerlukan pembungkusan ke awal antrean. Ini akan berarti bahwa dua penunjuk dasar diperlukan untuk membaca/menulis data ke FMQ buffer ring.

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 penulisan ke FMQ menggunakan API zero copy:

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 helper berikut juga merupakan bagian dari MemTransaction:

  • T* getSlot(size_t idx);
    Mengembalikan pointer ke slot idx dalam MemRegions yang merupakan bagian dari MemTransaction ini . Jika objek MemTransaction mewakili memori untuk membaca/menulis N item bertipe T, lalu rentang yang valid dari idx adalah antara 0 dan N-1.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    Tulis nMessages item jenis T ke dalam region memori dijelaskan oleh objek, mulai dari indeks startIdx. Metode ini menggunakan memcpy() dan tidak dimaksudkan untuk digunakan pada salinan nol operasi. Jika objek MemTransaction mewakili memori untuk membaca/menulis N item bertipe T, maka rentang yang valid dari idx adalah antara 0 dan N-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Metode helper untuk membaca nMessages item tipe T dari region memori yang dijelaskan oleh objek mulai dari startIdx. Ini menggunakan memcpy() dan tidak dimaksudkan untuk digunakan untuk salinan nol operasi.

Kirim antrean melalui HIDL

Di sisi pembuatan:

  1. Buat objek antrean pesan seperti yang dijelaskan di atas.
  2. Pastikan bahwa objek valid dengan isValid().
  3. Jika Anda menunggu beberapa antrean dengan meneruskan EventFlag menjadi format panjang readBlocking()/writeBlocking(), Anda dapat mengekstrak pointer flag peristiwa (menggunakan getEventFlagWord()) dari MessageQueue yang diinisialisasi untuk membuat flag, dan gunakan flag tersebut untuk membuat objek EventFlag yang diperlukan.
  4. Gunakan metode MessageQueue getDesc() untuk mendapatkan objek deskriptor.
  5. Dalam file .hal, berikan parameter jenis untuk metode tersebut fmq_sync atau fmq_unsync dengan T adalah yang sesuai dengan definisi HIDL. Gunakan metode ini untuk mengirim objek yang ditampilkan oleh getDesc() ke proses penerimaan.

Di sisi penerima:

  1. Gunakan objek deskriptor untuk membuat objek MessageQueue. Menjadi pastikan untuk menggunakan ragam antrean dan tipe data yang sama, atau {i>template<i} gagal mengompilasi.
  2. Jika Anda mengekstrak tanda peristiwa, ekstrak tanda dari penanda yang sesuai MessageQueue dalam proses penerimaan.
  3. Gunakan objek MessageQueue untuk mentransfer data.