قائمة انتظار الرسائل السريعة (FMQ)

تستخدِم بنية HIDL الأساسية لطلبات الإجراءات عن بُعد (RPC) آليات الربط، ما يعني أنّ طلبات الإجراءات تتطلّب وقتًا إضافيًا وتتطلّب عمليات في النواة ويمكن أن تؤدي إلى بدء إجراء جدولة. ومع ذلك، في الحالات التي يجب فيها نقل البيانات بين العمليات مع تقليل الوقت المستغرَق وبدون تدخل من نظام التشغيل، يتم استخدام نظام "قائمة الرسائل السريعة" (FMQ).

تُنشئ واجهة FMQ قوائم انتظار الرسائل باستخدام السمات المطلوبة. يمكنك إرسال ملف MQDescriptorSync أو MQDescriptorUnsync عبر طلب HIDL RPC، وتستخدم العملية المستلِمة العنصر للوصول إلى ملف MQDescriptorSync قائمة الرسائل.

أنواع قوائم الانتظار

يتيح Android نوعَين من قوائم الانتظار (المعروفَين باسم النُسخ):

  • يُسمح بتجاوز سعة قوائم الانتظار غير المتزامنة، ويمكن أن تتضمّن العديد من القارئين، ويجب أن يقرأ كل قارئ البيانات في الوقت المناسب وإلا سيفقدها.
  • لا يُسمح بتجاوز سعة قوائم الانتظار المتزامنة، ويمكن أن يكون لها قارِئ واحد فقط.

لا يُسمح لكلا نوعَي الانتظار بانخفاض عدد العناصر إلى ما دون الحد الأدنى (تؤدي القراءة من قائمة انتظار فارغة إلى حدوث خطأ)، ويمكن أن يتضمّن كلّ نوع كاتبًا واحدًا فقط.

قوائم الانتظار غير المتزامنة

تحتوي "القائمة الانتظار غير المتزامنة" على كاتب واحد فقط، ولكن يمكن أن تحتوي على أي عدد من القارئين. هناك موضع كتابة واحد للانتظار، ومع ذلك، يحتفظ كل قارئ بموضع القراءة المستقل الخاص به.

تنجح عمليات الكتابة إلى "القائمة الانتظار" دائمًا (لا يتم التحقّق من حدوث تدفّق زائد) ما دام حجمها ليس أكبر من سعة "القائمة الانتظار" التي تم ضبطها (تتعطّل عمليات الكتابة التي تزيد عن سعة "القائمة الانتظار" على الفور). وبما أنّ كل قارئ قد يكون لديه موضع قراءة مختلف، بدلاً من الانتظار إلى أن يقرأ كل قارئ كل قطعة من البيانات، يتمّ إزالة البيانات من "قائمة الانتظار" كلما احتجت عمليات الكتابة الجديدة إلى المساحة.

يتحمّل القرّاء مسؤولية استرداد البيانات قبل أن تسقط من نهاية القائمة. إذا حاولت قراءة بيانات أكثر من تلك المتوفّرة، إما أن تتعذّر القراءة على الفور (إذا كانت غير محظورة) أو تنتظر إلى أن تصبح بيانات كافية متاحة (إذا كانت محظورة). إنّ عملية القراءة التي تحاول قراءة بيانات أكثر من سعة الانتظار تتعذّر دائمًا على الفور.

إذا تعذّر على أحد القراء مواكبة الكاتب، بحيث تتجاوز كمية البيانات المكتوبة والتي لم يقرأها هذا القارئ بعد سعة الانتظار، لن تعرض عملية القراءة التالية البيانات، بل ستعيد ضبط موضع قراءة القارئ على موضع الكتابة بالإضافة إلى نصف السعة، ثم تعرِض خطأ. ويؤدي ذلك إلى ترك نصف مساحة التخزين المؤقتة متاحة للقراءة ويحجز مساحة للعمليات الجديدة لكتابة البيانات لتجنُّب تجاوز السعة القصوى للانتظار مرة أخرى على الفور. في حال التحقّق من البيانات المتاحة للقراءة بعد حدوث تدفّق بيانات زائد ولكن قبل القراءة التالية، يُظهر العنصر المزيد من البيانات المتاحة للقراءة مقارنةً بسعة الانتظار، ما يشير إلى حدوث تدفّق بيانات زائد. (إذا امتلأت قائمة الانتظار بين التحقّق من البيانات المتاحة ومحاولة قراءة تلك البيانات، فإنّ المؤشر الوحيد على الامتلاء هو تعذُّر القراءة).

قوائم الانتظار المتزامنة

تحتوي "القائمة الانتظار المتزامنة" على كاتب واحد وقارئ واحد مع موضع كتابة واحد وموضع قراءة واحد. من المستحيل كتابة بيانات أكثر مما يمكن أن تستوعبه الطابور أو قراءة بيانات أكثر مما يحتوي عليه حاليًا. استنادًا إلى ما إذا تم استدعاء دالة القراءة أو الكتابة المحظورة أو غير المحظورة، تؤدي محاولات تجاوز المساحة أو البيانات المتاحة إلى تعذُّر إكمال العملية أو حظرها على الفور إلى أن تتمكّن من إكمال العملية المطلوبة. إنّ محاولات قراءة أو كتابة بيانات أكثر من سعة الانتظار تؤدي دائمًا إلى تعذُّر إكمالها على الفور.

إعداد طلب معلومات فورية

تتطلّب قائمة الرسائل الواردة عناصر MessageQueue متعددة: عنصر واحد لكتابة الرسائل فيه وعنصر واحد أو أكثر لقراءتها. لا تتوفّر عملية برمجة صريحة لتحديد العنصر الذي يتم استخدامه للكتابة أو القراءة، ويتحمل المستخدم مسؤولية التأكّد من عدم استخدام أي عنصر للقراءة والكتابة معًا، ومن توفّر كاتب واحد على الأكثر، ومن توفّر قارئ واحد على الأكثر في الطوابير المتزامنة.

أنشئ أول عنصر من عناصر MessageQueue.

يتم إنشاء قائمة انتظار للرسائل وضبطها من خلال مكالمة واحدة:

#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 nonblocking 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) عنصرًا يتيح وظيفة "قائمة انتظار الرسائل" ويُنشئها.
  • ينشئ مُنشئ MessageQueue<T, flavor>(numElements, configureEventFlagWord) عنصرًا ويُنشئه ويُنشئ عنصرًا يتوافق مع وظيفة "قائمة انتظار الرسائل" مع الحظر.
  • يمكن أن يكون flavor إما kSynchronizedReadWrite لقائمة انتظار متزامنة أو kUnsynchronizedWrite لقائمة انتظار غير متزامنة.
  • يمكن أن يكون uint16_t (في هذا المثال) أي نوع حدَّده HIDL لا يتضمّن مخازن مؤقتة متداخلة (بدون أنواع string أو vec ) أو معالِمات أو واجهات.
  • يشير kNumElementsInQueue إلى حجم "القائمة الانتظار" بعدد الإدخالات، ويحدِّد حجم ذاكرة التخزين المؤقت المشتركة التي يتم تخصيصها للقائمة الانتظار.

أنشئ عنصر MessageQueue الثاني.

يتم إنشاء الجانب الثاني من قائمة انتظار الرسائل باستخدام كائن MQDescriptor تم الحصول عليه من الجانب الأول. يتم إرسال عنصر MQDescriptor عبر طلب HIDL أو AIDL RPC إلى العملية التي تمتلك الطرف الثاني من قائمة انتظار الرسائل. يحتوي الرمز MQDescriptor على معلومات عن "قائمة الانتظار"، بما في ذلك:

  • معلومات لربط المخزن المؤقت ومؤشر الكتابة
  • معلومات لربط مؤشر القراءة (إذا تمت مزامنة "القائمة الانتظار")
  • معلومات لربط كلمة علامة الحدث (إذا كانت قائمة الانتظار تحظر).
  • نوع العنصر (<T, flavor>)، الذي يتضمّن النوع المحدّد من خلال HIDL لعناصر السلسلة ونوع السلسلة (متزامنة أو غير متزامنة).

يمكنك استخدام عنصر MQDescriptor لإنشاء عنصر MessageQueue:

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

تشير المَعلمة resetPointers إلى ما إذا كان يجب إعادة ضبط مواضع القراءة والكتابة على 0 أثناء إنشاء عنصر MessageQueue هذا. في قائمة الانتظار غير المتزامنة، يتم دائمًا ضبط موضع القراءة (الذي يكون محليًا لكل كائن MessageQueue في قوائم الانتظار غير المتزامنة) على 0 أثناء الإنشاء. عادةً ما يتمّ إعداد MQDescriptor أثناء إنشاء كائن "قائمة انتظار الرسائل" الأولى. للتحكّم بشكل أكبر في الذاكرة المشترَكة، يمكنك إعداد MQDescriptor يدويًا (يتم تعريف MQDescriptor في system/libhidl/base/include/hidl/MQDescriptor.h)، ثم إنشاء كل عنصر MessageQueue كما هو موضّح في هذا القسم.

قوائم الانتظار وحقول الأحداث المحظورة

لا تتيح قوائم الانتظار تلقائيًا حظر عمليات القراءة والكتابة. هناك نوعان من عمليات حظر طلبات القراءة والكتابة:

  • النموذج القصير، الذي يتضمّن ثلاث مَعلمات (مُشير البيانات وعدد العناصر ومهلة الانتظار)، يتيح حظر عمليات القراءة والكتابة الفردية في ملف انتظار واحد. عند استخدام هذا النموذج، تعالج "القائمة" علامة الحدث وشبكات البت داخليًا، ويجب تهيئة كائن قائمة الرسائل الأولى باستخدام مَعلمة ثانية هي true. مثلاً:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • الشكل الطويل، الذي يتضمّن ستة مَعلمات (تتضمّن علامة الحدث وشبكات الوحدات)، يسمح باستخدام عنصر EventFlag مشترَك بين قوائم انتظار متعددة ويسمح بتحديد شبكات الوحدات للإشعارات التي سيتم استخدامها. في هذه الحالة، يجب تقديم علامة الحدث وشبكات البتات لكل طلب قراءة وكتابة.

بالنسبة إلى النموذج الطويل، يمكنك تقديم EventFlag صراحةً في كلّ مكالمة readBlocking() وwriteBlocking(). يمكنك بدء إحدى القوائم باستخدام علامة حدث داخلية، والتي يجب بعد ذلك استخراجها من كائنات MessageQueue الخاصة بهذه القائمة باستخدام getEventFlagWord() واستخدامها لإنشاء كائنات EventFlag في كل عملية لاستخدامها مع قوائم FMQ الأخرى. بدلاً من ذلك، يمكنك بدء عناصر EventFlag باستخدام أي ذاكرة مشترَكة مناسبة.

بشكل عام، يجب أن تستخدم كل قائمة انتظار طريقة واحدة فقط من بين الحظر غير المشروط أو حظر المحتوى القصير أو حظر المحتوى الطويل. ولا يشكّل خلطهما خطأ، ولكن يجب استخدام برمجة مدروسة للحصول على النتيجة المطلوبة.

وضع علامة "للقراءة فقط" على الذاكرة

تحصل الذاكرة المشتركة تلقائيًا على أذونات القراءة والكتابة. بالنسبة إلى طوابير الرسائل غير المتزامنة (kUnsynchronizedWrite)، قد يريد الكاتب إزالة أذونات الكتابة لجميع القراء قبل توزيع عناصر MQDescriptorUnsync. يضمن ذلك عدم تمكّن العمليات الأخرى من الكتابة في "قائمة الانتظار"، وهو إجراء يُنصح به للحماية من الأخطاء أو السلوك السيئ في عمليات القراءة. إذا أراد الكاتب أن يتمكّن القرّاء من إعادة ضبط "القائمة الانتظار" كلما استخدموا MQDescriptorUnsync لإنشاء جانب القراءة من "القائمة الانتظار"، لا يمكن وضع علامة على الذاكرة بصفتها للقراءة فقط. هذا هو السلوك التلقائي لصانع MessageQueue. لذلك، إذا كان هناك مستخدمون حاليون لهذه الطابور، يجب تغيير الرمز البرمجي الخاص بهم لإنشاء الطابور باستخدام resetPointer=false.

  • الكاتب: استدعاء ashmem_set_prot_region باستخدام وصف ملف MQDescriptor وضبط المنطقة على القراءة فقط (PROT_READ):
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • القارئ: إنشاء قائمة انتظار الرسائل باستخدام resetPointer=false (true هو القيمة التلقائية):
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

استخدام MessageQueue

واجهة برمجة التطبيقات العامة للكائن MessageQueue هي:

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() وavailableToRead() لتحديد مقدار البيانات التي يمكن نقلها في عملية واحدة. في ملف شخصي غير متزامن:

  • تعرض دالة availableToWrite() دائمًا سعة الانتظار.
  • يمتلك كل قارئ موضع قراءة خاصًا به ويجري عملية حسابية خاصة به لتحديد قيمة availableToRead().
  • من وجهة نظر قارئ بطيء، يُسمح بتجاوز السعة القصوى للصف، ويمكن أن يؤدي ذلك إلى عرض availableToRead() لقيمة أكبر من حجم الصف. تؤدي القراءة الأولى بعد حدوث تدفّق إلى تعذُّر ضبط موضع القراءة لهذا القارئ على أنّه يساوي مؤشر الكتابة الحالي، سواء تم الإبلاغ عن تدفّق البيانات من خلال availableToRead() أم لا.

تُعرِض الطريقتان read() وwrite()true إذا كان بالإمكان نقل جميع البيانات المطلوبة (وتم نقلها) من وإلى القائمة. لا تؤدي هذه الطرق إلى الحظر، بل إما أن تنجح (وتعرض true) أو تؤدي إلى تعذُّر الإجراء (false) على الفور.

تنتظر الطريقتان readBlocking() وwriteBlocking() إلى أن يتم إكمال العملية المطلوبة، أو إلى أن تنتهي مهلة التنفيذ (تعني قيمة timeOutNanos 0 عدم انتهاء المهلة مطلقًا).

يتم تنفيذ عمليات الحظر باستخدام كلمة علامة حدث. بشكلٍ تلقائي، تُنشئ كل قائمة انتظار كلمة علامة خاصة بها وتستخدمها للسماح بالشكل المختصر من readBlocking() وwriteBlocking(). يمكن أن تشترك عدة طوابير في كلمة واحدة، بحيث يمكن لعملية الانتظار على عمليات الكتابة أو القراءة في أي من الطوابير. من خلال استدعاء getEventFlagWord()، يمكنك الحصول على مؤشر إلى كلمة علامة الحدث في "القائمة"، ويمكنك استخدام هذا المؤشر (أو أي مؤشر إلى موقع ذاكرة مشترَكة مناسب) لإنشاء EventFlag لعرضه في الشكل الطويل من readBlocking() وwriteBlocking() لقائمة مختلفة. تحدد المَعلمتَان readNotification وwriteNotification البتّات التي يجب استخدامها في علامة الحدث للإشارة إلى عمليات القراءة والكتابة في هذه الطابور. readNotification و writeNotification هما قناع بت 32 بت.

ينتظر readBlocking() writeNotification بتًا، إذا كانت هذه المَعلمة هي 0، يتعذّر إكمال الطلب دائمًا. إذا كانت قيمة readNotification هي 0، لن يتعطّل الطلب، ولكن لن يؤدي قراءة ملف التمهيد بنجاح إلى ضبط أيّ بتات إشعار. في قائمة الانتظار المتزامنة، يعني ذلك أنّ طلب writeBlocking() المقابل لن يتم تفعيله أبدًا ما لم يتم ضبط القيمة في مكان آخر. في قائمة الانتظار غير المتزامنة، لا ينتظر writeBlocking() (يجب أن يظلّ قيد الاستخدام لضبط وبت إشعار الكتابة)، ومن المناسب ألا تضبط عمليات القراءة أي وبت إشعار. وبالمثل، يتعذّر تنفيذ writeblocking() إذا كانت قيمة readNotification هي 0، ويؤدي الإجراء الناجح إلى ضبط وحدات البت المحدّدة writeNotification.

للانتظار في قوائم انتظار متعددة في الوقت نفسه، استخدِم EventFlagwait() في كائن EventFlag للانتظار في قناع بتات للإشعارات. تُرجع الطريقة wait() كلمة حالة تتضمّن الوحدات التي أدّت إلى ضبط wake up. وتُستخدَم هذه المعلومات بعد ذلك للتحقّق من توفّر مساحة أو بيانات كافية في "القائمة الانتظار" المقابلة لإجراء عملية الكتابة والقراءة المطلوبة وتنفيذ write() وread() غير المحظورة. للحصول على إعلام بعملية نشر، استخدِم طلبًا آخر لطريقة wake() في عنصر EventFlag. للاطّلاع على تعريف EventFlag التجريد، يُرجى الاطّلاع على system/libfmq/include/fmq/EventFlag.h.

عمليات النسخ بدون أي تكلفة

تأخذ الدوالّ read وwrite وreadBlocking وwriteBlocking() معلمة تشير إلى مخزن مؤقت للدخل والخرج، وتستخدم طلبات memcpy() داخليًا لنسخ البيانات بين المخزن المؤقت نفسه و مخزن FMQ الدائري. لتحسين الأداء، يتضمّن الإصدار 8.0 من نظام التشغيل Android والإصدارات الأحدث مجموعة من واجهتَي برمجة التطبيقات (API) اللتين توفّران إمكانية وصول المؤشر مباشرةً إلى المخزن الدائري، ما يزيل الحاجة إلى استخدام طلبات memcpy.

استخدِم واجهات برمجة التطبيقات العامة التالية لإجراء عمليات "الطلبات المجمّعة بدون نسخ":

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 مؤشرات أساسية إلى ملف تخزين دوار للفوترة (FMQ). بعد كتابة البيانات، احفظها باستخدام commitWrite(). تعمل الطريقتان beginRead وcommitRead بالطريقة نفسها.
  • تأخذ الطريقتان beginRead وWrite كمدخل عدد الرسائل التي سيتم قراءتها وكتابتها، وتُعرِض قيمة منطقية تشير إلى ما إذا كان بالإمكان قراءة الرسائل أو كتابتها. إذا كان من الممكن القراءة أو الكتابة، تتم تعبئة memTx البنية بمؤشرات أساسية يمكن استخدامها للوصول المباشر باستخدام المؤشر إلى الذاكرة المشتركة للوسيط الدائري.
  • يحتوي عنصر MemRegion على تفاصيل عن كتلة ذاكرة، بما في ذلك المؤشر الأساسي (العنوان الأساسي لكتلة الذاكرة) والطول بوحدة T (طول كتلة الذاكرة بوحدة نوع قائمة الرسائل الذي تحدّده HIDL).
  • تحتوي بنية MemTransaction على بنيتي MemRegion first وsecond، لأنّ القراءة أو الكتابة في المخزّن الدائري قد تتطلّب لفّ البيانات إلى بداية السلسلة. وهذا يعني أنّه يجب استخدام مؤشّرَين أساسيَّين لقراءة البيانات وكتابتها في ملف التخزين المؤقت الدائري لـ FMQ.

للحصول على العنوان الأساسي والطول من بنية 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

للحصول على إحالات إلى بنية MemRegion الأولى والثانية ضمن عنصر MemTransaction:

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

مثال على الكتابة إلى FMQ باستخدام واجهات برمجة التطبيقات التي لا تتطلب نسخ البيانات:

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
}

تشكّل أيضًا طرق المساعدة التالية جزءًا من MemTransaction:

  • تعرِض T* getSlot(size_t idx); مؤشرًا إلى الفتحة idx ضمن MemRegions التي تشكّل جزءًا من MemTransaction هذا العنصر. إذا كان العنصر MemTransaction يمثّل مناطق الذاكرة لقراءة وكتابة N عنصرًا من النوع T، يكون النطاق الصالح ل idx بين 0 وN-1.
  • يكتب bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1); nMessages عنصرًا من النوع T في مناطق الذاكرة التي يصفّها العنصر، بدءًا من الفهرس startIdx. تستخدِم هذه الطريقةmemcpy() ولا يُقصد استخدامها في عملية عدم نسخ أي بيانات. إذا كان عنصر MemTransaction يمثّل ذاكرة لقراءة وكتابة N عنصر من النوع T، يكون النطاق الصالح لـ idx بين 0 وN-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1); هي طريقة مساعدة لقراءة nMessages عنصر من النوع T من مناطق الذاكرة التي يصفّها الكائن بدءًا من startIdx. تستخدِم هذه المحاولةmemcpy() ولا يُقصد استخدامها في عملية بدون نسخة.

إرسال قائمة المحتوى التالي عبر HIDL

من جانب الإنشاء:

  1. أنشئ عنصرًا في "قائمة انتظار الرسائل" كما هو موضّح أعلاه.
  2. تأكَّد من أنّ العنصر صالح باستخدام isValid().
  3. إذا كنت في انتظار قوائم انتظار متعددة من خلال تمرير EventFlag إلى الشكل الطويل من readBlocking() أو writeBlocking()، يمكنك استخراج مُشير علامة الحدث (باستخدام getEventFlagWord()) من ملف برمجي MessageQueue تم إعداده لإنشاء العلامة، و استخدام هذه العلامة لإنشاء ملف EventFlag البرمجي المطلوب.
  4. استخدِم الطريقة MessageQueue getDesc() للحصول على ملف شخصي للموصّف.
  5. في ملف HAL، امنح الطريقة مَعلمة من النوع fmq_sync أو fmq_unsync حيث يكون T هو نوع مناسب محدّد من HIDL. استخدِم هذا النموذج لإرسال المنتج الذي تم إرجاعه من قِبل العميل "getDesc()" إلى عملية الاستلام.

على الجانب المستلِم:

  1. استخدِم عنصر الوصف لإنشاء عنصر MessageQueue. استخدِم نوع البيانات ونكهة "القائمة الانتظار" نفسها، وإلا لن يتم compiling (تجميع) النموذج.
  2. إذا استخرجت علامة حدث، استخرِج العلامة من عنصر MessageQueue المقابل في عملية الاستقبال.
  3. استخدِم عنصر MessageQueue لنقل البيانات.