Hızlı Mesaj Kuyruğu (FMQ)

HIDL'nin uzaktan prosedür çağrısı (RPC) altyapısı, Binder mekanizmalarını kullanır, yani çağrılar ek yük içerir, çekirdek işlemleri gerektirir ve zamanlayıcı eylemini tetikleyebilir. Ancak, verilerin işlemler arasında daha az ek yükü olan ve çekirdek katılımı olmadan aktarılmasının gerektiği durumlarda, Hızlı Mesaj Kuyruğu (FMQ) sistemi kullanılır.

FMQ, istenen özelliklerde mesaj kuyrukları oluşturur. Bir MQDescriptorSync veya MQDescriptorUnsync amacı HIDL RPC çağrısı üzerinden gönderilen ve mesaj kuyruğu ulaşmak için, alıcı işlemi ile kullanılabilir.

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

MessageQueue türleri

(Tatlar olarak da bilinir) Android destekleri iki kuyruk tipleri:

  • Eşitlenmemiş kuyruklar taşma izin verilir ve birçok okuyucu var olabilir; her okuyucunun verileri zamanında okuması veya kaybetmesi gerekir.
  • Senkronize kuyruklar taşma izin verilmez ve sadece bir okuyucu olabilir.

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 yazara sahip olabilir.

senkronize edilmemiş

Senkronize edilmemiş 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.

Kuyruğa yazma işlemleri, yapılandırılan kuyruk kapasitesinden daha büyük olmadığı sürece her zaman başarılı olur (taşma için kontrol edilmez) (sıra kapasitesinden daha büyük yazmalar hemen başarısız olur). Her okuyucunun her veri parçasını okumasını beklemek yerine, her okuyucu farklı bir okuma pozisyonuna sahip olabileceğinden, yeni yazmalar alana ihtiyaç duyduğunda verilerin kuyruktan düşmesine izin verilir.

Okumalar, sıranın sonuna düşmeden önce verileri almaktan sorumludur. Mevcut olandan daha fazla veri okumaya çalışan bir okuma ya hemen başarısız olur (engelleme yapmıyorsa) ya da yeterli verinin olmasını bekler (engelleme yapıyorsa). Kuyruk kapasitesinden daha fazla veri okumaya çalışan bir okuma her zaman hemen başarısız olur.

Bir okuyucu, yazara ayak uyduramazsa, bu okuyucu tarafından yazılan ve henüz okunmayan veri miktarı kuyruk kapasitesinden daha büyük olursa, bir sonraki okuma veri döndürmez; bunun yerine okuyucunun okuma konumunu en son yazma konumuna eşitleyecek şekilde sıfırlar ve ardından hata verir. Okumak için mevcut veriler taşma sonrasında ancak bir sonraki okumadan önce kontrol edilirse, taşmanın gerçekleştiğini gösteren kuyruk kapasitesinden daha fazla okunabilir veri gösterir. (Kuyruk, mevcut verileri kontrol etme ve bu verileri okumaya çalışma arasında taşarsa, taşmanın tek göstergesi okumanın başarısız olması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ğundan daha fazla veri yazmak veya kuyruğun şu anda sahip olduğundan daha fazla veri okumak imkansızdır. Engelleyen veya engellenmeyen yazma veya okuma işlevinin çağrılmasına bağlı olarak, kullanılabilir alanı veya verileri aşma girişimleri ya hemen başarısız 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 kurma

Bir ileti sırası birden gerektirir MessageQueue nesneleri: yazılacak bir, ve bir veya daha fazla okunacak. Yazma veya okuma için hangi nesnenin kullanılacağına dair açık bir yapılandırma yoktur; hem okuma hem de yazma için hiçbir nesnenin kullanılmadığından, en fazla bir yazar olduğundan ve senkronize kuyruklar için en fazla bir okuyucu olduğundan emin olmak kullanıcıya bağlıdır.

İlk MessageQueue 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) bir nesne olduğu destekler mesaj sıra işlevi oluşturur ve başlatır başlatıcısı.
  • MessageQueue<T, flavor>(numElements, configureEventFlagWord) bir nesne oluşturur ve başlatır başlatıcısı olduğu destekler blokaj mesaj kuyruğu işlevselliği.
  • flavor olabilir ya olabilir kSynchronizedReadWrite senkronize bir sıra veya için kUnsynchronizedWrite eşleşmemiş olan bir sıra için.
  • uint16_t (bu örnekte) herhangi biri olabilir HIDL tanımlı türü iç içe tampon (hayır içermeyen string veya vec türleri), kolları veya arayüzleri.
  • kNumElementsInQueue girişlerinin sayısını sırasının boyutunu belirtir; sıra için ayrılacak paylaşılan bellek arabelleğinin boyutunu belirler.

İkinci MessageQueue nesnesini oluşturma

Mesaj sıranın ikinci yan bir kullanılarak oluşturulur MQDescriptor birinci taraftan elde edilen bir nesne. MQDescriptor nesne mesajı sıranın ikinci ucunu tutacak sürecine HIDL RPC çağrısı üzerinden gönderilir. MQDescriptor kuyrukta dahil hakkında bilgi içerir:

  • Arabellek eşlemek ve işaretçi yazmak için bilgiler.
  • Okuma işaretçisini eşlemek için bilgi (sıra senkronize edilmişse).
  • Olay bayrağı kelimesini eşleme bilgisi (sıra engelleniyorsa).
  • Nesne tipi ( <T, flavor> içerir), HIDL tanımlı tür sıra elemanlarının ve sıra lezzet (senkronize veya senkronize olmayan).

MQDescriptor amacı, oluşturmak için kullanılabilir MessageQueue nesne:

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

resetPointers parametre, bu oluşturulurken 0 okuma ve yazma pozisyonları sıfırlamak için olup olmadığını gösterir MessageQueue nesnesi. Eşleşmemiş olan bir sıraya şekilde, (her biri yerel bir okuma konumu MessageQueue eşitlenmemiş sıralarında nesne) her zaman oluşturma sırasında 0 olarak ayarlanır. Tipik olarak, MQDescriptor ilk mesaj sıra nesnesi oluşturulması sırasında başlatılır. Paylaşılan bellek üzerinde ekstra denetim için, ayarlayabilirsiniz MQDescriptor (el MQDescriptor tanımlanan system/libhidl/base/include/hidl/MQDescriptor.h sonra her oluşturun) MessageQueue bu bölümde açıklandığı şekilde nesne.

Kuyrukları ve olay bayraklarını engelleme

Varsayılan olarak, kuyruklar okuma/yazma engellemeyi desteklemez. Okuma/yazma çağrılarını engellemenin iki türü vardır:

  • Üç parametreleri (veri pointer, öğelerin sayısı, zaman aşımı) ile kısa form. Tek bir kuyrukta bireysel okuma/yazma işlemlerinde engellemeyi destekler. Bu formu kullanırken, kuyruk içten olay bayrak ve bir bit maskesi idare edecek ve ilk mesaj kuyruğu nesnesi ikinci bir parametre ile ilklendirilmelidir true . Örneğin:
    // 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() ve availableToRead() tek bir işlemde aktarılabilir ne kadar veri belirlemek için kullanılabilir. Senkronize edilmemiş bir kuyrukta:

  • availableToWrite() her sıranın kapasitesini verir.
  • Her okuyucu kendi okuma konuma sahiptir ve için kendi hesaplamasını yapar availableToRead() .
  • Yavaş bir okuyucu açısından, kuyruğun taşmasına izin verilir; Bu neden olabilir availableToRead() sıranın boyutundan daha büyük bir değeri geri döndürülmesi. Bir taşma sonra ilk okuma taşma yoluyla bildirilmiştir olsun ya da olmasın, mevcut yazma işaretçisine eşit başarısız ve okuyucu olmak seti için okuma pozisyonda sonuçlanır availableToRead() .

read() ve write() yöntemleri dönmek true istenen tüm veriler kuyruktan / olarak aktarılabilir (ve oldu) eğer. Bu yöntemler engellemez; onlar da başarılı (ve dönüş true ) veya dönüş hatası ( false derhal).

readBlocking() ve writeBlocking() yöntemleri istenen işlem tamamlanabilir kadar beklemek ya dek zaman aşımı (bir timeOutNanos 0 araçlarla asla zaman aşımı değeri).

Engelleme işlemleri, bir olay bayrak sözcüğü kullanılarak uygulanır. Varsayılan olarak, her kuyruk oluşturur ve kısa biçimi desteklemek için kendi bayrağı kelimesini kullanır readBlocking() ve writeBlocking() . Birden çok sıranın tek bir kelimeyi paylaşması mümkündür, böylece bir işlem herhangi bir sıraya yazma veya okuma işlemlerini bekleyebilir. Bir sıranın olay işaret kelimesi için bir işaretçi çağırarak elde edilebilir getEventFlagWord() , ve bu işaretçi (ya da uygun bir ortak bellek konumuna herhangi bir işaretçi) bir oluşturmak için kullanılabilir EventFlag uzun forma geçmesine nesne readBlocking() ve writeBlocking() farklı bir sıra için. readNotification ve writeNotification parametreleri sinyal okur ve o sırasına yazıyor için kullanılması gereken olay bayrak hangi bit söyle. readNotification ve writeNotification 32 bit bit maskesi vardır.

readBlocking() üzerinde bekler writeNotification bit; bu parametre 0 ise, çağrı her zaman başarısız olur. Eğer readNotification değeri 0'dır, çağrı başarısız olmaz ama başarılı bir okuma herhangi bildirim bitlerini set olmayacaktır. Senkronize bir kuyrukta, bu karşılık gelen anlamına geleceğini writeBlocking() biraz başka yerde ayarlanmadığı sürece çağrı uyanmak asla. Senkronize olmamış kuyrukta, writeBlocking() (hala yazma bildirim bitini ayarlamak için kullanılmalıdır) beklemeyecek ve herhangi bildirim biti ayarlanmamış okur için uygundur. Benzer şekilde, writeblocking() eğer başarısız olur readNotification 0'dır ve başarılı bir yazma belirtilen ayarlar writeNotification bitlerini.

Bir kullanmak, bir kerede birden fazla kuyruklar beklemek EventFlag nesnenin wait() bildirimlerin bit maskesi beklemek yöntemi. wait() metodu seti yukarı uyandırma neden bit ile bir durum sözcüğü döndürür. Bu bilgiler, daha sonra karşılık gelen sıra arzu edilen yazma / okuma işlemi için yeterli boşluk veya veri içeren kontrol ve tıkanmasız gerçekleştirmek için kullanılan write() / read() . Bir post işlemi bildirim almak için, başka bir çağrı kullanmak EventFlag 'ın wake() yöntemiyle. Bir tanımı için EventFlag soyutlama, bakınız system/libfmq/include/fmq/EventFlag.h .

Sıfır kopyalama işlemleri

read / write / readBlocking / writeBlocking() API bir bağımsız değişken ve kullanımı gibi bir giriş / çıkış tampon için bir işaretçi alır memcpy() aynı ve FMQ halka tamponu arasında veri kopyalamak dahili çağırır. Performansını artırmak için Android 8.0 ve daha kullanma gereksinimini ortadan kaldırır halka tampon doğrudan işaretçi erişim sağlayan bir API, bir dizi içerir memcpy çağrı.

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 tampona baz işaretçiler içerir. Veri yazıldıktan sonra kullanarak işlemek commitWrite() . beginRead / commitRead yöntem aynı şekilde hareket ederler.
  • beginRead / Write yöntemler girdi olarak iletilerin sayısı okuma / yazılı ve okuma / yazma mümkün olup olmadığını belirten bir boolean dönmek üzere alır. Okuma veya yazma mümkünse memTx yapı halka tampon paylaşılan belleğe doğrudan işaretçi erişimi için kullanılabilecek baz işaretçiler doldurulur.
  • MemRegion yapı tabanı işaretçi (bellek bloğunun taban adresi) ve açısından süresi de dahil bir bellek bloğu ayrıntılarını içeren T (mesaj kuyruğu HIDL tanımlı türü açısından bellek bloğu uzunluğu).
  • MemTransaction yapı iki içeren MemRegion yapılar, first ve second sıranın başlangıcına bir şal gerektirebilir halka tamponu içine okuma veya yazma olarak. Bu, FMQ halka arabelleğine veri okumak/yazmak için iki temel işaretçiye ihtiyaç duyulduğu anlamına gelir.

Bir gelen baz adresini ve uzunluğunu almak için MemRegion yapı:

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

Birinci ve ikinci başvurular almak için MemRegion bir dahilinde s MemTransaction nesne:

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'leri kullanarak FMQ'ya örnek yazma:

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öntemleri de bir parçası olan MemTransaction :

  • T* getSlot(size_t idx);
    Yuvası için bir işaretçi İade idx içinde MemRegions bunun bir parçasıdır MemTransaction nesne. Eğer MemTransaction nesne türü T / yazma N öğeleri okuma hafıza bölgeleri temsil eden, o zaman geçerli aralığı idx 0 ve N-1 arasındadır.
  • bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1);
    Yaz nMessages endeksi başlayarak nesne, tarif bellek bölgelere tipi T ürün startIdx . Bu yöntem, kullanım memcpy() ve sıfır kopyalama işlemi için kullanılması anlamına için değildir. Eğer MemTransaction nesnesi okuma belleği / yazma N tipi T ürün temsil ettiğinde, geçerli aralığı idx 0 ve N-1 arasındadır.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1);
    Yardımcının yöntem okuma nMessages başlayarak nesnesi tarafından açıklanan bellek bölgelerden tipi T ürün startIdx . Bu yöntem, kullanım memcpy() ve sıfır kopyalama işlemi için kullanılacak amaçlı değildir.

Kuyruğu HIDL üzerinden gönderme

Yaratma tarafında:

  1. Yukarıda açıklandığı gibi mesaj kuyruğu nesnesi oluşturun.
  2. Nesne ile geçerlidir doğrulama isValid() .
  3. Bir ileterek birden fazla sıralarda bekleyen edilecektir EventFlag uzun forma readBlocking() / writeBlocking() , sen (kullanarak olay bayrak işaretçiyi ayıklamak getEventFlagWord() a) 'dan MessageQueue bayrağı oluşturmak üzere başlatıldı nesne ve gerekli oluşturmak için bu bayrağı kullanmak EventFlag nesnesi.
  4. Kullan MessageQueue getDesc() bir açıklayıcısı nesneyi almak için yöntem.
  5. Gelen .hal dosya yöntemini tipi bir parametre elde fmq_sync ya fmq_unsync burada T , uygun bir HIDL tanımlı türüdür. Tarafından döndürülen nesne göndermek için kullanın getDesc() alıcı sürecine.

Alıcı tarafta:

  1. Bir oluşturmak için açıklayıcı nesnesini kullanın MessageQueue nesnesi. Aynı kuyruk türünü ve veri türünü kullandığınızdan emin olun, aksi takdirde şablon derlenemez.
  2. Bir olay bayrağı ekstre, karşılık gelen bayrağı özü MessageQueue alıcı işlemde bir nesne.
  3. Kullanım MessageQueue veri aktarımı nesne.