سرویس‌ها و انتقال داده

این صفحه نحوه ثبت و کاوش سرویس‌ها و نحوه ارسال داده به سرویس با فراخوانی روش‌های تعریف‌شده در واسط‌های موجود در فایل‌های .hal را شرح می‌دهد.

ثبت سرویس‌ها

سرورهای میانای HIDL (اشیایی که میانای را پیاده‌سازی می‌کنند) می‌توانند به‌عنوان خدمات نام‌دار ثبت شوند. نام ثبت‌شده لازم نیست با نام بسته یا میانای کاربری مرتبط باشد. اگر نامی مشخص نشده باشد، از نام «پیش‌فرض» استفاده می‌شود؛ این نام باید برای HALهایی استفاده شود که نیازی به ثبت دو پیاده‌سازی از یک رابط ندارند. برای مثال، فراخوانی C++ برای ثبت سرویس که در هر واسط تعریف شده است به‌این‌صورت است:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

نسخه میانای HIDL در خود میانا گنجانده شده است. این شناسه به‌طور خودکار با ثبت سرویس مرتبط می‌شود و می‌توان آن را ازطریق تماس روش (android::hardware::IInterface::getInterfaceVersion()) در هر واسط HIDL بازیابی کرد. اشیاء سرور نیازی به ثبت ندارند و می‌توانند ازطریق پارامترهای روش HIDL به فرایند دیگری که فراخوانی‌های روش HIDL را در سرور انجام می‌دهد منتقل شوند.

کاوش سرویس‌ها

درخواست‌های کد مشتری برای میانای معینی براساس نام و نسخه انجام می‌شود و getService را در کلاس HAL موردنظر فرا می‌خواند:

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

هر نسخه از میانای HIDL به‌عنوان یک میانای جداگانه درنظر گرفته می‌شود. بنابراین، IFooService نسخه ۱.۱ و IFooService نسخه ۲.۲ هر دو می‌توانند به‌عنوان «foo_service» ثبت شوند و getService("foo_service") در هریک از میاناها سرویس ثبت‌شده برای آن میانا را دریافت می‌کند. به همین دلیل است که در اکثر موارد، هیچ پارامتر نامی برای ثبت یا شناسایی لازم نیست (یعنی نام «پیش‌فرض»).

«شیء میانای فروشنده» نیز در روش انتقال میانای برگردانده‌شده نقش دارد. برای میانای IFoo در بسته android.hardware.foo@1.0، میانای برگشتی از IFoo::getService همیشه از روش انتقال اعلام‌شده برای android.hardware.foo در مانیفست دستگاه استفاده می‌کند، درصورتی‌که ورودی وجود داشته باشد؛ و اگر روش انتقال دردسترس نباشد، nullptr برگردانده می‌شود.

در برخی موارد، ممکن است لازم باشد حتی بدون دریافت سرویس، بلافاصله ادامه دهید. این اتفاق می‌تواند (برای مثال) زمانی رخ دهد که کارخواهی بخواهد اعلان‌های سرویس را خودش یا در برنامه تشخیص خرابی (مثل atrace) مدیریت کند که باید همه hwservices را دریافت و آن‌ها را بازیابی کند. در این مورد، میاناهای برنامه‌سازی کاربردی اضافی مثل tryGetService در C++‎ یا getService("instance-name", false) در Java ارائه می‌شود. میانای برنامه‌سازی کاربردی قدیمی getService که در جاوا ارائه شده است نیز باید با اعلان‌های سرویس استفاده شود. استفاده از این API از وضعیت مسابقه‌ای که در آن سرور پس‌از درخواست مشتری با یکی از این APIهای بدون تلاش مجدد ثبت می‌شود جلوگیری نمی‌کند.

اعلان‌های فوت در حین خدمت

کاربرانی که می‌خواهند وقتی سرویسی ازکار می‌افتد مطلع شوند می‌توانند اعلان‌های ازکارافتادگی ارائه‌شده توسط چارچوب را دریافت کنند. برای دریافت اعلان‌ها، کارخواه باید:

  1. کلاس/میانای HIDL hidl_death_recipient را زیرکلاس کنید (در کد C++‎، نه در HIDL).
  2. روش serviceDied() آن را ملغی کنید.
  3. شیئی از زیرکلاس hidl_death_recipient را نمونه‌سازی کنید.
  4. روش linkToDeath() را در سرویس موردنظر برای نظارت فراخوانی کنید، و شیء میانای IDeathRecipient را ارسال کنید. توجه داشته باشید که این روش مالکیت گیرنده فوت‌شده یا پراکسی که روی آن فراخوانده می‌شود را دراختیار نمی‌گیرد.

نمونه شبه‌کد (C++‎ و Java مشابه هستند):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

یک گیرنده فوت می‌تواند در چندین سرویس مختلف ثبت شود.

انتقال داده

داده‌ها را می‌توان با فراخوانی روش‌های تعریف‌شده در میاناهای موجود در فایل‌های .hal به سرویس ارسال کرد. دو نوع روش وجود دارد:

  • روش‌های مسدودکننده تا زمانی که سرور نتیجه‌ای تولید کند منتظر می‌مانند.
  • روش‌های یک‌طرفه داده‌ها را فقط در یک جهت ارسال می‌کنند و مسدود نمی‌کنند. اگر مقدار داده درحال انتقال در تماس‌های RPC از محدودیت‌های پیاده‌سازی فراتر رود، تماس‌ها ممکن است مسدود شوند یا نشانگر خطا برگردانند (عملکرد هنوز مشخص نشده است).

روشی که مقداری برنمی‌گرداند اما به‌عنوان oneway اعلام نشده است همچنان مسدود است.

همه روش‌های تعریف‌شده در میانای HIDL در یک جهت فراخوانی می‌شوند، یا از HAL یا به HAL. واسط مشخص نمی‌کند که در کدام جهت فراخوانی می‌شود. معماری‌هایی که نیاز به تماس‌هایی دارند که از HAL منشأ می‌گیرند، باید دو (یا بیشتر) رابط در بسته HAL ارائه دهند و رابط مناسب را از هر فرایند ارائه دهند. کلمات کارخواه و کارساز با درنظر گرفتن جهت فراخوانی واسط استفاده می‌شوند (یعنی HAL می‌تواند کارساز یک واسط و کارخواه واسط دیگر باشد).

بازخوانی‌ها

کلمه بازخوانی به دو مفهوم متفاوت اشاره دارد که با بازخوانی هم‌زمان و بازخوانی ناهم‌زمان از هم متمایز می‌شوند.

از بازخوان‌های هم‌زمان در برخی‌از روش‌های HIDL که داده برمی‌گردانند استفاده می‌شود. روش HIDL که بیش‌از یک مقدار برمی‌گرداند (یا یک مقدار از نوع غیربدوی برمی‌گرداند) نتایج خود را ازطریق تابع برگشتی برمی‌گرداند. اگر فقط یک مقدار برگردانده شود و آن مقدار از نوع ابتدایی باشد، از پاسخ‌بر استفاده نمی‌شود و مقدار از روش برگردانده می‌شود. سرور روش‌های HIDL را پیاده‌سازی می‌کند و کارخواه برگشتی‌ها را پیاده‌سازی می‌کند.

فراخوانی‌های ناهمگام به سرور واسط HIDL اجازه می‌دهد تا تماس‌ها را آغاز کند. این کار با ارسال نمونه‌ای از میانای دوم ازطریق میانای اول انجام می‌شود. کاربر واسط اول باید به‌عنوان کاربر واسط دوم عمل کند. کارساز واسط اول می‌تواند روش‌هایی را در شیء واسط دوم فراخوانی کند. برای نمونه، پیاده‌سازی HAL می‌تواند اطلاعات را به‌صورت ناهمگام به فرایندی که از آن استفاده می‌کند برگرداند. این کار با فراخوانی روش‌ها در شیء میانایی انجام می‌شود که توسط آن فرایند ایجاد و ارائه شده است. روش‌های موجود در میانه‌های استفاده‌شده برای بازخوانی ناهم‌زمان ممکن است مسدودکننده باشند (و ممکن است مقادیری را به تماس‌گیرنده برگردانند) یا oneway. برای مثال، «بازخوان‌های ناهم‌زمان» را در HIDL C++‎ ببینید.

برای ساده کردن مالکیت حافظه، فراخوانی‌های روش و بازخوان‌ها فقط in پارامتر می‌گیرند و از پارامترهای out یا inout پشتیبانی نمی‌کنند.

حدود هر تراکنش

محدودیت‌های هر تراکنش بر مقدار داده‌های ارسالی در روش‌ها و برگشت‌های HIDL اعمال نمی‌شود. بااین‌حال، فراخوانی‌های بیش‌از ۴ کیلوبایت در هر تراکنش افراطی درنظر گرفته می‌شود. اگر این مورد دیده شد، توصیه می‌شود میانای HIDL داده‌شده مجدداً معماری شود. محدودیت دیگر منابع دردسترس زیرساخت HIDL برای مدیریت چندین تراکنش هم‌زمان است. به‌دلیل وجود چند رشته یا فرایند که تماس‌ها را به یک فرایند یا چند تماس oneway ارسال می‌کنند که توسط فرایند دریافت‌کننده به‌سرعت مدیریت نمی‌شوند، چند تراکنش می‌توانند به‌طور هم‌زمان درحال انجام باشند. حداکثر فضای کل دردسترس برای همه تراکنش‌های هم‌زمان به‌طور پیش‌فرض ۱ مگابایت است.

در میانای خوش‌طراحی، نباید از این محدودیت‌های منبع فراتر رفت؛ اگر این اتفاق بیفتد، تماسی که از این محدودیت‌ها فراتر رفته است می‌تواند تا زمانی که منابع دردسترس قرار بگیرند مسدود شود یا خطای انتقال را نشان دهد. هر بار که از محدودیت‌های هر تراکنش فراتر می‌روید یا منابع پیاده‌سازی HIDL را براساس تراکنش‌های درحال انجام انبوه سرریز می‌کنید، برای تسهیل اشکال‌زدایی ثبت می‌شود.

پیاده‌سازی‌های روش

HIDL فایل‌های سرایند را تولید می‌کند که انواع، روش‌ها، و فراخوانی‌های لازم را در زبان هدف (C++‎ یا Java) اعلام می‌کنند. نمونه اولیه روش‌ها و برگشتی‌های تعریف‌شده با HIDL برای کد مشتری و سرور یکسان است. سیستم HIDL پیاده‌سازی‌های پروکسی از روش‌های طرف تماس‌گیرنده ارائه می‌دهد که داده‌ها را برای انتقال IPC سازمان‌دهی می‌کند، و کد کدنویسی در طرف تماس‌گیرنده که داده‌ها را به پیاده‌سازی‌های توسعه‌دهنده روش‌ها منتقل می‌کند.

فراخواننده تابع (روش HIDL یا فراخوانی متقابل) مالکیت ساختارهای داده‌ای را که به تابع منتقل شده‌اند دارد و پس‌از فراخوانی نیز مالکیت را حفظ می‌کند؛ در همه موارد، فراخوانده نیازی به آزاد کردن یا رها کردن فضای ذخیره‌سازی ندارد.

  • در C++‎، داده‌ها ممکن است فقط خواندنی باشند (تلاش برای نوشتن در آن می‌تواند باعث خطای تقسیم‌بندی شود) و برای مدت تماس معتبر هستند. کارخواه می‌تواند داده‌ها را کپی عمیق کند تا آن را فراتر از تماس منتشر کند.
  • در Java، کد یک نسخه محلی از داده‌ها (یک شیء Java معمولی) را دریافت می‌کند، که ممکن است آن را نگه دارد و اصلاح کند یا اجازه دهد زباله‌روبی شود.

انتقال داده غیرRPC

HIDL دو روش برای انتقال داده بدون استفاده از فراخوانی RPC دارد: حافظه مشترک و «صف پیام سریع» (FMQ)، که هر دو فقط در ++C پشتیبانی می‌شوند.

  • خاطرات مشترک. از نوع HIDL داخلی memory برای انتقال شیء نشان‌دهنده حافظه مشترکی که تخصیص داده شده است استفاده می‌شود. می‌تواند در فرایند دریافت برای نگاشت حافظه مشترک استفاده شود.
  • صف پیام سریع (FMQ). ‫HIDL نوع صف پیام الگوپذیری را ارائه می‌دهد که انتقال پیام بدون انتظار را پیاده‌سازی می‌کند. در حالت عبور یا اتصال (ارتباط بین دستگاه‌ها این ویژگی‌ها را ندارد) از هسته یا زمان‌بند استفاده نمی‌کند. معمولاً HAL انتهای صف خود را راه‌اندازی می‌کند، و شیئی ایجاد می‌کند که می‌تواند ازطریق RPC ازطریق پارامتری از نوع HIDL داخلی MQDescriptorSync یا MQDescriptorUnsync منتقل شود. این شیء می‌تواند توسط فرایند دریافت‌کننده برای راه‌اندازی انتهای دیگر صف استفاده شود.
    • صف‌های همگام‌سازی نباید سرریز شوند و فقط می‌توانند یک خواننده داشته باشند.
    • صف‌های همگام‌سازی‌نشده می‌توانند سرریز شوند و خوانندگان زیادی داشته باشند، که هرکدام باید داده‌ها را به‌موقع بخوانند یا آن‌ها را ازدست بدهند.
    هیچ‌کدام از این دو نوع اجازه سرریز شدن ندارند (خواندن از صف خالی با خطا مواجه می‌شود)، و هر نوع فقط می‌تواند یک نویسنده داشته باشد.

برای جزئیات بیشتر درباره FMQ، به صف پیام سریع (FMQ) مراجعه کنید.