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

إذا كنت تبحث عن دعم AIDL، فراجع أيضًا FMQ مع AIDL .

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

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

يتم دعم قوائم انتظار الرسائل السريعة فقط في لغة C++ وعلى الأجهزة التي تعمل بنظام التشغيل Android 8.0 والإصدارات الأحدث.

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

يدعم Android نوعين من قوائم الانتظار (المعروفين باسم النكهات ):

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

لا يُسمح لكلا النوعين من قوائم الانتظار بالتدفق (ستفشل القراءة من قائمة انتظار فارغة) ويمكن أن يكون لهما كاتب واحد فقط.

غير متزامن

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

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

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

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

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

متزامن

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

إعداد FMQ

تتطلب قائمة انتظار الرسائل وجود عدة كائنات 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 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) بإنشاء وتهيئة كائن يدعم وظيفة قائمة انتظار الرسائل.
  • 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 في كل عملية لاستخدامها مع FMQs الأخرى. وبدلاً من ذلك، يمكن تهيئة كائنات 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 هي:

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 المحددة.

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

عمليات النسخ صفر

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

استخدم واجهات برمجة التطبيقات العامة التالية لعمليات FMQ ذات النسخة الصفرية:

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 . تأكد من استخدام نفس نكهة قائمة الانتظار ونوع البيانات، وإلا سيفشل تجميع القالب.
  2. إذا قمت باستخراج علامة حدث، فاستخرج العلامة من كائن MessageQueue المطابق في عملية الاستلام.
  3. استخدم كائن MessageQueue لنقل البيانات.