Hızlı Mesaj Kuyruğu (FMQ)

AIDL desteği arıyorsanız ayrıca bkz. AIDL ile FMQ .

HIDL'nin uzaktan prosedür çağrısı (RPC) altyapısı Binder mekanizmalarını kullanır; bu, çağrıların ek yük gerektirdiği, çekirdek işlemleri gerektirdiği ve zamanlayıcı eylemini tetikleyebileceği anlamına gelir. Ancak daha az ek yüke sahip ve çekirdek müdahalesi olmayan işlemler arasında veri aktarımının gerekli olduğu durumlarda Hızlı Mesaj Kuyruğu (FMQ) sistemi kullanılır.

FMQ istenilen özelliklere sahip mesaj kuyrukları oluşturur. Bir MQDescriptorSync veya MQDescriptorUnsync nesnesi, bir HIDL RPC çağrısı üzerinden gönderilebilir ve alıcı işlem tarafından mesaj kuyruğuna erişmek için kullanılabilir.

Hızlı Mesaj Kuyrukları yalnızca C++'da ve Android 8.0 ve üzerini çalıştıran cihazlarda desteklenir.

Mesaj Kuyruğu türleri

Android iki kuyruk türünü destekler ( tatlar olarak bilinir):

  • Senkronize edilmemiş kuyrukların taşmasına izin verilir ve çok sayıda okuyucusu olabilir; her okuyucunun verileri zamanında okuması veya kaybetmesi gerekir.
  • Senkronize kuyrukların taşmasına izin verilmez ve yalnızca bir okuyucu bulunabilir.

Her iki kuyruk türünün de taşmasına izin verilmez (boş bir kuyruktan okuma başarısız olur) ve yalnızca bir yazıcıya sahip olabilir.

senkronize edilmemiş

Senkronize olmayan bir kuyruğun yalnızca bir yazarı vardır ancak herhangi bir sayıda okuyucusu olabilir. Kuyruk için bir yazma konumu vardır; ancak her okuyucu kendi bağımsız okuma konumunu takip eder.

Yapılandırılan kuyruk kapasitesinden daha büyük olmadıkları sürece kuyruğa yazma işlemleri her zaman başarılı olur (taşma açısından kontrol edilmez) (kuyruk kapasitesinden daha büyük yazmalar hemen başarısız olur). Her okuyucunun farklı bir okuma konumu olabileceğinden, her okuyucunun her veri parçasını okumasını beklemek yerine, yeni yazma işlemlerinin alana ihtiyaç duyması durumunda verilerin kuyruktan düşmesine izin verilir.

Okuyucular verinin kuyruğun sonuna düşmeden önce alınmasından sorumludur. Mevcut olandan daha fazla veri okumaya çalışan bir okuma ya hemen başarısız olur (engellenmiyorsa) ya da yeterli verinin kullanılabilir olmasını bekler (engelliyorsa). Kuyruk kapasitesinden daha fazla veri okumaya çalışan bir okuma her zaman anında başarısız olur.

Bir okuyucu, yazara ayak uyduramazsa ve bu okuyucu tarafından yazılan ve henüz okunmayan veri miktarı kuyruk kapasitesinden daha büyükse, bir sonraki okuma veri döndürmez; bunun yerine okuyucunun okuma konumunu en son yazma konumuna eşit olacak şekilde sıfırlar ve ardından hata döndürür. Okunabilecek veriler taşma sonrasında ancak bir sonraki okumadan önce kontrol edilirse, sıra kapasitesinden daha fazla okunabilir veri gösterilir, bu da taşmanın meydana geldiğini gösterir. (Kuyruk, mevcut verilerin kontrol edilmesi ile bu verilerin okunmasının denenmesi arasında taşarsa, taşmanın tek göstergesi okumanın başarısız olmasıdır.)

Senkronize edilmemiş bir kuyruğun okuyucuları büyük olasılıkla kuyruğun okuma ve yazma işaretçilerini sıfırlamak istemez. Bu nedenle, tanımlayıcıdan kuyruk oluştururken okuyucular "resetPointers" parametresi için "false" bağımsız değişkenini kullanmalıdır.

senkronize

Senkronize bir kuyrukta, tek bir yazma konumu ve tek bir okuma konumu olan bir yazar ve bir okuyucu bulunur. Kuyruğun sahip olduğu alandan daha fazla veri yazmak veya kuyrukta mevcut olandan daha fazla veri okumak mümkün değildir. Engellemeli veya engellemesiz yazma veya okuma fonksiyonunun çağrılmasına bağlı olarak, kullanılabilir alanı veya veriyi aşma girişimleri ya hemen hataya neden olur ya da istenen işlem tamamlanana kadar bloke eder. Kuyruk kapasitesinden daha fazla veri okuma veya yazma girişimleri her zaman hemen başarısız olur.

FMQ'yu ayarlama

Bir ileti kuyruğu birden fazla MessageQueue nesnesi gerektirir: biri yazılacak ve bir veya daha fazlası okunacak. Hangi nesnenin yazma veya okuma için kullanıldığına ilişkin açık bir yapılandırma yoktur; hem okuma hem de yazma için hiçbir nesnenin kullanılmamasını, en fazla bir yazarın olmasını ve senkronize kuyruklar için en fazla bir okuyucunun olmasını sağlamak kullanıcıya kalmıştır.

İlk Mesaj Sırası nesnesini oluşturma

Tek bir çağrıyla bir mesaj kuyruğu oluşturulur ve yapılandırılır:

#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) başlatıcısı, mesaj kuyruğu işlevselliğini destekleyen bir nesne oluşturur ve başlatır.
  • MessageQueue<T, flavor>(numElements, configureEventFlagWord) başlatıcısı, engelleme ile mesaj kuyruğu işlevselliğini destekleyen bir nesne oluşturur ve başlatır.
  • flavor senkronize edilmiş bir kuyruk için kSynchronizedReadWrite veya senkronize edilmemiş bir kuyruk için kUnsynchronizedWrite olabilir.
  • uint16_t (bu örnekte), iç içe arabellekler ( string veya vec türleri yok), tanıtıcılar veya arabirimler içermeyen herhangi bir HIDL tanımlı tür olabilir.
  • kNumElementsInQueue giriş sayısı cinsinden kuyruğun boyutunu gösterir; kuyruk için ayrılacak paylaşılan bellek arabelleğinin boyutunu belirler.

İkinciMessageQueue nesnesini oluşturma

Mesaj kuyruğunun ikinci tarafı, birinci taraftan elde edilen bir MQDescriptor nesnesi kullanılarak oluşturulur. MQDescriptor nesnesi, bir HIDL veya AIDL RPC çağrısı üzerinden mesaj kuyruğunun ikinci ucunu tutacak işleme gönderilir. MQDescriptor , aşağıdakiler de dahil olmak üzere kuyrukla ilgili bilgileri içerir:

  • Tamponu eşlemek ve işaretçiyi yazmak için bilgi.
  • Okuma işaretçisini eşlemeye yönelik bilgiler (kuyruk senkronize edilmişse).
  • Olay bayrağı sözcüğünü eşlemeye yönelik bilgiler (kuyruk engelleniyorsa).
  • HIDL tanımlı kuyruk öğeleri türünü ve kuyruk lezzetini (senkronize edilmiş veya senkronize edilmemiş) içeren nesne türü ( <T, flavor> ).

MQDescriptor nesnesi bir MessageQueue nesnesi oluşturmak için kullanılabilir:

MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)

resetPointers parametresi, bu MessageQueue nesnesini oluştururken okuma ve yazma konumlarının 0'a sıfırlanıp sıfırlanmayacağını gösterir. Senkronize edilmemiş bir kuyrukta, okuma konumu (senkronize edilmemiş kuyruklardaki her MessageQueue nesnesi için yereldir) oluşturma sırasında her zaman 0'a ayarlanır. Tipik olarak MQDescriptor , ilk mesaj kuyruğu nesnesinin oluşturulması sırasında başlatılır. Paylaşılan bellek üzerinde ekstra kontrol sağlamak için, MQDescriptor manuel olarak ayarlayabilir ( MQDescriptor system/libhidl/base/include/hidl/MQDescriptor.h dosyasında tanımlanmıştır) ve ardından bu bölümde açıklandığı gibi her bir MessageQueue nesnesini oluşturabilirsiniz.

Kuyrukları ve etkinlik bayraklarını engelleme

Varsayılan olarak kuyruklar okuma/yazma işlemlerinin engellenmesini desteklemez. Okuma/yazma çağrılarını engellemenin iki türü vardır:

  • Kısa form , üç parametreli (veri işaretçisi, öğe sayısı, zaman aşımı). Tek bir kuyrukta bireysel okuma/yazma işlemlerinin engellenmesini destekler. Bu formu kullanırken kuyruk, olay bayrağını ve bit maskelerini dahili olarak işleyecektir ve ilk mesaj kuyruğu nesnesinin ikinci bir true parametresiyle başlatılması gerekir. Örneğin:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • Altı parametreli uzun biçim (olay bayrağını ve bit maskelerini içerir). Birden çok kuyruk arasında paylaşılan bir EventFlag nesnesinin kullanılmasını destekler ve kullanılacak bildirim bit maskelerinin belirlenmesine olanak tanır. Bu durumda, her okuma ve yazma çağrısına olay bayrağı ve bit maskelerinin sağlanması gerekir.

Uzun biçim için, EventFlag her readBlocking() ve writeBlocking() çağrısında açıkça sağlanabilir. Kuyruklardan biri dahili bir olay bayrağıyla başlatılabilir; bunun daha sonra getEventFlagWord() kullanılarak kuyruğun MessageQueue nesnelerinden çıkarılması ve diğer FMQ'larla kullanılmak üzere her işlemde EventFlag nesneleri oluşturmak için kullanılması gerekir. Alternatif olarak EventFlag nesneleri herhangi bir uygun paylaşımlı bellekle başlatılabilir.

Genel olarak her sıra, engellemesiz, kısa biçimli engelleme veya uzun biçimli engellemeden yalnızca birini kullanmalıdır. Bunları karıştırmak bir hata değildir ancak istenilen sonucu elde etmek için dikkatli bir programlama gerekir.

Belleği salt okunur olarak işaretleme

Varsayılan olarak, paylaşılan belleğin okuma ve yazma izinleri vardır. Senkronize edilmemiş kuyruklar için ( kUnsynchronizedWrite ), yazar, MQDescriptorUnsync nesnelerini dağıtmadan önce tüm okuyucuların yazma izinlerini kaldırmak isteyebilir. Bu, okuyucu süreçlerindeki hatalara veya kötü davranışlara karşı koruma sağlamak için önerilen diğer süreçlerin kuyruğa yazamamasını sağlar. Yazar, okuyucuların kuyruğun okuma tarafını oluşturmak için MQDescriptorUnsync kullandıklarında kuyruğu sıfırlayabilmelerini istiyorsa, bellek salt okunur olarak işaretlenemez. Bu, 'MessageQueue' yapıcısının varsayılan davranışıdır. Dolayısıyla, bu kuyruğun zaten mevcut kullanıcıları varsa, kuyruğu resetPointer=false ile oluşturmak için kodlarının değiştirilmesi gerekir.

  • Yazar: ashmem_set_prot_region MQDescriptor dosya tanımlayıcısı ve salt okunur ( PROT_READ ) olarak ayarlanmış bölgeyle çağırın:
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • Okuyucu: resetPointer=false ile mesaj kuyruğu oluşturun (varsayılan true ):
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

Mesaj Sırasını Kullanma

MessageQueue nesnesinin genel API'si şöyledir:

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() ve availableToRead() tek bir işlemde ne kadar verinin aktarılabileceğini belirlemek için kullanılabilir. Senkronize edilmemiş bir kuyrukta:

  • availableToWrite() her zaman kuyruğun kapasitesini döndürür.
  • Her okuyucunun kendi okuma konumu vardır ve availableToRead() için kendi hesaplamasını yapar.
  • Yavaş bir okuyucunun bakış açısından kuyruğun taşmasına izin verilir; bu, availableToRead() işlevinin kuyruk boyutundan daha büyük bir değer döndürmesine neden olabilir. Taşma sonrasındaki ilk okuma başarısız olur ve taşma availableToRead() aracılığıyla rapor edilmiş olsun ya da olmasın, söz konusu okuyucunun okuma konumunun geçerli yazma işaretçisine eşit olarak ayarlanmasına neden olur.

İstenen tüm veriler kuyruğa aktarılabiliyorsa (ve aktarılmışsa) read() ve write() yöntemleri true döndürür. Bu yöntemler engellemez; ya başarılı olurlar (ve true değerini döndürürler) ya da hemen başarısızlık ( false ) değerini döndürürler.

readBlocking() ve writeBlocking() yöntemleri, istenen işlem tamamlanana veya zaman aşımına uğrayana kadar bekler ( timeOutNanos değerinin 0 olması, hiçbir zaman zaman aşımı olmadığı anlamına gelir).

Engelleme işlemleri bir olay bayrağı sözcüğü kullanılarak uygulanır. Varsayılan olarak her kuyruk, readBlocking() ve writeBlocking() kısa biçimini desteklemek için kendi bayrak sözcüğünü oluşturur ve kullanır. Birden fazla kuyruğun tek bir kelimeyi paylaşması mümkündür, böylece bir işlem kuyruklardan herhangi birine yazma veya okuma işlemlerini bekleyebilir. Bir kuyruğun olay bayrağı sözcüğüne yönelik bir işaretçi, getEventFlagWord() çağrılarak elde edilebilir ve bu işaretçi (veya uygun bir paylaşılan bellek konumuna yönelik herhangi bir işaretçi), readBlocking() in uzun biçimine geçmek üzere bir EventFlag nesnesi oluşturmak için kullanılabilir ve Farklı bir kuyruk için writeBlocking() . readNotification ve writeNotification parametreleri, olay bayrağındaki hangi bitlerin o kuyruktaki okuma ve yazma sinyallerini vermek için kullanılması gerektiğini söyler. readNotification ve writeNotification 32 bitlik bit maskeleridir.

readBlocking() writeNotification bitlerini bekler; bu parametre 0 ise çağrı her zaman başarısız olur. readNotification değeri 0 ise çağrı başarısız olmaz ancak başarılı bir okuma herhangi bir bildirim bitini ayarlamaz. Senkronize bir kuyrukta bu, karşılık gelen writeBlocking() çağrısının, bit başka bir yere ayarlanmadığı sürece asla uyanmayacağı anlamına gelir. Senkronize olmayan bir kuyrukta writeBlocking() beklemez (yine de yazma bildirim bitini ayarlamak için kullanılmalıdır) ve okumaların herhangi bir bildirim biti ayarlamaması uygundur. Benzer şekilde, readNotification 0 ise writeblocking() başarısız olur ve başarılı bir yazma, belirtilen writeNotification bitlerini ayarlar.

Aynı anda birden fazla kuyrukta beklemek için, bildirimlerin bit maskesini beklemek amacıyla EventFlag nesnesinin wait() yöntemini kullanın. wait() yöntemi, uyandırma ayarına neden olan bitleri içeren bir durum sözcüğünü döndürür. Bu bilgi daha sonra ilgili kuyruğun istenen yazma/okuma işlemi için yeterli alana veya veriye sahip olduğunu doğrulamak ve engellemeyen bir write() / read() gerçekleştirmek için kullanılır. İşlem sonrası bildirimi almak için EventFlag wake() yöntemine yapılan başka bir çağrıyı kullanın. EventFlag soyutlamasının tanımı için system/libfmq/include/fmq/EventFlag.h bakın.

Sıfır kopyalama işlemi

read / write / readBlocking / writeBlocking() API'leri argüman olarak bir giriş/çıkış arabelleğine yönelik bir işaretçi alır ve verileri aynı ile FMQ halka arabelleği arasında kopyalamak için dahili olarak memcpy() çağrılarını kullanır. Performansı artırmak için Android 8.0 ve üzeri, halka arabelleğine doğrudan işaretçi erişimi sağlayan ve memcpy çağrılarını kullanma ihtiyacını ortadan kaldıran bir dizi API içerir.

Sıfır kopya FMQ işlemleri için aşağıdaki genel API'leri kullanın:

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 yöntemi, FMQ halka arabelleğine temel işaretçiler sağlar. Veriler yazıldıktan sonra commitWrite() kullanarak kaydedin. beginRead / commitRead yöntemleri aynı şekilde hareket eder.
  • beginRead / Write yöntemleri, okunacak/yazılacak mesaj sayısını girdi olarak alır ve okuma/yazmanın mümkün olup olmadığını belirten bir boole değeri döndürür. Okuma veya yazma mümkünse memTx yapısı, halka arabellek paylaşımlı belleğine doğrudan işaretçi erişimi için kullanılabilen temel işaretçilerle doldurulur.
  • MemRegion yapısı, temel işaretçi (bellek bloğunun temel adresi) ve T cinsinden uzunluk (mesaj kuyruğunun HIDL tanımlı türü açısından bellek bloğunun uzunluğu) dahil olmak üzere bir bellek bloğu hakkında ayrıntılar içerir.
  • MemTransaction yapısı iki MemRegion yapısı içerir; first ve second olarak halka arabelleğine okuma veya yazma işlemi kuyruğun başlangıcına kadar sarma gerektirebilir. Bu, FMQ halka arabelleğine veri okumak/yazmak için iki temel işaretçinin gerekli olduğu anlamına gelir.

Bir MemRegion yapısından temel adresi ve uzunluğu almak için:

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

Bir MemTransaction nesnesi içindeki birinci ve ikinci MemRegion referanslar almak için:

const MemRegion& getFirstRegion(); // get a reference to the first MemRegion
const MemRegion& getSecondRegion(); // get a reference to the second MemRegion

Sıfır kopya API'lerini kullanarak FMQ'ya yazma örneği:

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
}

Aşağıdaki yardımcı yöntemler de MemTransaction bir parçasıdır:

  • T* getSlot(size_t idx);
    Bu MemTransaction nesnesinin parçası olan MemRegions içindeki yuva idx bir işaretçi döndürür. MemTransaction nesnesi T tipi N öğeyi okumak/yazmak için bellek bölgelerini temsil ediyorsa, geçerli idx aralığı 0 ile N-1 arasındadır.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    T türündeki nMessages öğelerini startIdx dizininden başlayarak nesne tarafından tanımlanan bellek bölgelerine yazın. Bu yöntem memcpy() işlevini kullanır ve sıfır kopyalama işlemi için kullanılması amaçlanmamıştır. MemTransaction nesnesi T tipi N öğeyi okumak/yazmak için belleği temsil ediyorsa, geçerli idx aralığı 0 ile N-1 arasındadır.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    startIdx başlayan nesne tarafından tanımlanan bellek bölgelerinden T türündeki nMessages öğelerini okumaya yönelik yardımcı yöntem. Bu yöntem memcpy() işlevini kullanır ve sıfır kopyalama işlemi için kullanılması amaçlanmamıştır.

Sıranın HIDL üzerinden gönderilmesi

Oluşturma tarafında:

  1. Yukarıda açıklandığı gibi mesaj kuyruğu nesnesini oluşturun.
  2. Nesnenin isValid() ile geçerli olduğunu doğrulayın.
  3. Bir EventFlag uzun readBlocking() / writeBlocking() biçimine geçirerek birden fazla kuyrukta bekleyecekseniz, olay bayrağı işaretçisini ( getEventFlagWord() kullanarak) bayrağı oluşturmak için başlatılan bir MessageQueue nesnesinden çıkarabilirsiniz. ve gerekli EventFlag nesnesini oluşturmak için bu bayrağı kullanın.
  4. Bir tanımlayıcı nesne almak için MessageQueue getDesc() yöntemini kullanın.
  5. .hal dosyasında yönteme fmq_sync türünde bir parametre verin veya fmq_unsync burada T uygun bir HIDL tanımlı türdür. getDesc() tarafından döndürülen nesneyi alma işlemine göndermek için bunu kullanın.

Alıcı tarafta:

  1. Bir MessageQueue nesnesi oluşturmak için tanımlayıcı nesneyi kullanın. Aynı kuyruk türünü ve veri türünü kullandığınızdan emin olun, aksi takdirde şablon derlenemez.
  2. Bir olay bayrağını ayıkladıysanız, alma işleminde ilgili MessageQueue nesnesinden bayrağı çıkarın.
  3. Verileri aktarmak MessageQueue nesnesini kullanın.