اگر به دنبال پشتیبانی AIDL هستید، FMQ با AIDL را نیز ببینید.
زیرساخت فراخوانی روش راه دور (RPC) HIDL از مکانیزمهای Binder استفاده میکند، به این معنی که تماسها شامل سربار هستند، به عملیات هسته نیاز دارند و ممکن است اقدام زمانبندی را آغاز کنند. با این حال، برای مواردی که داده ها باید بین فرآیندهایی با سربار کمتر و بدون دخالت هسته منتقل شوند، از سیستم صف پیام سریع (FMQ) استفاده می شود.
FMQ صف های پیام را با ویژگی های مورد نظر ایجاد می کند. یک شی MQDescriptorSync
یا MQDescriptorUnsync
می تواند از طریق یک تماس HIDL RPC ارسال شود و توسط فرآیند دریافت برای دسترسی به صف پیام استفاده شود.
صفهای پیام سریع فقط در C++ و دستگاههای دارای Android نسخه ۸.۰ و بالاتر پشتیبانی میشوند.
انواع MessageQueue
اندروید از دو نوع صف (معروف به طعم ها ) پشتیبانی می کند:
- صف های غیرهمگام مجاز به سرریز شدن هستند و می توانند خواننده های زیادی داشته باشند. هر خواننده باید داده ها را به موقع بخواند یا آن را از دست بدهد.
- صف های همگام سازی شده مجاز به سرریز شدن نیستند و فقط می توانند یک خواننده داشته باشند.
هر دو نوع صف مجاز به پایین آمدن نیستند (خواندن از صف خالی با شکست مواجه می شود) و فقط می توانند یک نویسنده داشته باشند.
هماهنگ نشده است
یک صف غیرهمگام فقط یک نویسنده دارد، اما می تواند هر تعداد خواننده داشته باشد. یک موقعیت نوشتن برای صف وجود دارد. با این حال، هر خواننده موقعیت خواندن مستقل خود را پیگیری می کند.
نوشتن در صف همیشه با موفقیت انجام می شود (برای سرریز بررسی نمی شود) تا زمانی که بزرگتر از ظرفیت صف پیکربندی شده نباشد (بزرگتر از ظرفیت صف می نویسد بلافاصله با شکست مواجه می شود). از آنجایی که هر خواننده ممکن است موقعیت خواندن متفاوتی داشته باشد، به جای اینکه منتظر بماند تا هر خواننده هر قطعه داده را بخواند، هر زمان که نوشته های جدید به فضا نیاز داشته باشند، داده ها از صف خارج می شوند.
خوانندگان مسئول بازیابی داده ها قبل از اینکه از انتهای صف بیفتند، هستند. خواندنی که تلاش میکند دادههای بیشتری از موجود را بخواند یا فوراً با شکست مواجه میشود (اگر مسدود نباشد) یا منتظر میماند تا دادههای کافی در دسترس باشد (در صورت مسدود شدن). خواندنی که تلاش میکند دادههای بیشتری از ظرفیت صف را بخواند، همیشه بلافاصله با شکست مواجه میشود.
اگر خواننده نتواند با رایتر همگام شود، به طوری که مقدار داده های نوشته شده و هنوز خوانده نشده توسط آن خواننده بیشتر از ظرفیت صف باشد، خواندن بعدی داده را بر نمی گرداند. در عوض، موقعیت خواندن خواننده را بازنشانی می کند تا با آخرین موقعیت نوشتن برابری کند و سپس شکست را برمی گرداند. اگر دادههای موجود برای خواندن پس از سرریز بررسی شود، اما قبل از خواندن بعدی، دادههای موجود برای خواندن بیشتر از ظرفیت صف را نشان میدهد، که نشان میدهد سرریز رخ داده است. (اگر صف بین بررسی داده های موجود و تلاش برای خواندن آن داده ها سرریز شود، تنها نشانه سرریز این است که خواندن ناموفق است.)
خوانندگان یک صف غیرهمگام احتمالاً نمی خواهند نشانگرهای خواندن و نوشتن صف را بازنشانی کنند. بنابراین، هنگام ایجاد صف از توصیفگر، خوانندگان باید از آرگومان «نادرست» برای پارامتر «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
نشان می دهد که آیا هنگام ایجاد این شیء MessageQueue
، موقعیت های خواندن و نوشتن به 0 بازنشانی شود یا خیر. در یک صف غیرهمگام، موقعیت خواندن (که محلی برای هر شیء 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
ساخته شود.
- Writer:
ashmem_set_prot_region
را با یک توصیفگر فایلMQDescriptor
و منطقه روی فقط خواندنی (PROT_READ
) تماس بگیرید:int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
- Reader: ایجاد صف پیام با
resetPointer=false
(پیشفرضtrue
است):mFmq = new (std::nothrow) MessageQueue(mqDesc, false);
از MessageQueue استفاده کنید
API عمومی شی 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()
منتظر نمی ماند (هنوز باید برای تنظیم بیت اعلان نوشتن استفاده شود)، و مناسب است برای خواندن هیچ بیت اعلان تنظیم نشود. به طور مشابه، در صورتی که readNotification
0 باشد، writeblocking()
با شکست مواجه می شود و یک نوشتن موفق بیت های writeNotification
مشخص شده را تنظیم می کند.
برای منتظر ماندن روی چندین صف به طور همزمان، از روش wait()
یک شیء EventFlag
استفاده کنید تا روی یک بیت ماسک از اعلان ها منتظر بمانید. متد wait()
یک کلمه وضعیت را با بیت هایی که باعث تنظیم بیدار شدن شده اند برمی گرداند. سپس از این اطلاعات برای تأیید اینکه صف مربوطه دارای فضای کافی یا داده برای عملیات نوشتن/خواندن مورد نظر است و انجام یک write()
/ read()
غیر مسدود کننده استفاده می شود. برای دریافت اعلان عملیات، از فراخوانی دیگری با متد wake()
EventFlag
استفاده کنید. برای تعریف انتزاع EventFlag
، به system/libfmq/include/fmq/EventFlag.h
مراجعه کنید.
عملیات کپی صفر
APIهای read
/ write
/ readBlocking
/ writeBlocking()
یک اشاره گر را به بافر ورودی/خروجی به عنوان آرگومان می گیرند و از فراخوانی های memcpy()
به صورت داخلی برای کپی داده ها بین همان و بافر حلقه FMQ استفاده می کنند. برای بهبود عملکرد، Android 8.0 و بالاتر شامل مجموعهای از APIها است که دسترسی اشاره گر مستقیم به بافر حلقه را فراهم میکند و نیاز به استفاده از تماسهای memcpy
را از بین میبرد.
از APIهای عمومی زیر برای عملیات 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()
commit کنید. متدهای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
به عنوان مثال با استفاده از API های صفر کپی در 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 ارسال کنید
در سمت ایجاد:
- شیء صف پیام را همانطور که در بالا توضیح داده شد ایجاد کنید.
- بررسی کنید که شی با
isValid()
معتبر است. - اگر با ارسال یک
EventFlag
به فرم طولانیreadBlocking()
/writeBlocking()
در صف های متعدد منتظر هستید، می توانید نشانگر پرچم رویداد (با استفاده ازgetEventFlagWord()
) را از یک شیMessageQueue
که برای ایجاد پرچم مقداردهی اولیه شده است استخراج کنید، و از آن پرچم برای ایجاد شئEventFlag
لازم استفاده کنید. - از متد
MessageQueue
getDesc()
برای دریافت یک شی توصیفگر استفاده کنید. - در فایل
.hal
به متد پارامتری از نوعfmq_sync
بدهیدیا fmq_unsync
که در آن T
یک نوع HIDL تعریف شده مناسب است. از این برای ارسال شیء برگشتی توسطgetDesc()
به فرآیند دریافت استفاده کنید.
در سمت دریافت کننده:
- از شی توصیفگر برای ایجاد یک شیء
MessageQueue
استفاده کنید. مطمئن شوید که از همان طعم صف و نوع داده استفاده کنید، در غیر این صورت قالب کامپایل نمی شود. - اگر پرچم رویداد را استخراج کردید، پرچم را از شی
MessageQueue
مربوطه در فرآیند دریافت استخراج کنید. - از شی
MessageQueue
برای انتقال داده استفاده کنید.