AIDL برای HAL ها

اندروید 11 قابلیت استفاده از AIDL برای HAL ها را در اندروید معرفی می کند. این امکان پیاده سازی بخش هایی از اندروید را بدون HIDL فراهم می کند. HAL های انتقالی را به استفاده انحصاری از AIDL در صورت امکان تغییر دهید (زمانی که HAL های بالادست از HIDL استفاده می کنند، HIDL باید استفاده شود).

HAL هایی که از AIDL برای برقراری ارتباط بین اجزای چارچوب، مانند اجزای موجود در system.img و قطعات سخت افزاری، مانند آنهایی که در vendor.img استفاده می کنند، باید از Stable AIDL استفاده کنند. با این حال، برای برقراری ارتباط در یک پارتیشن، به عنوان مثال، از یک HAL به دیگری، هیچ محدودیتی در مکانیسم IPC برای استفاده وجود ندارد.

انگیزه

AIDL طولانی‌تر از HIDL بوده است و در بسیاری از مکان‌های دیگر مانند بین اجزای چارچوب Android یا در برنامه‌ها استفاده می‌شود. اکنون که AIDL از پشتیبانی پایداری برخوردار است، امکان پیاده‌سازی کل پشته با یک زمان اجرا IPC وجود دارد. AIDL همچنین سیستم نسخه سازی بهتری نسبت به HIDL دارد.

  • استفاده از یک زبان IPC به معنای داشتن تنها یک چیز برای یادگیری است، اشکال زدایی، بهینه سازی و ایمن سازی.
  • AIDL از نسخه سازی در محل برای صاحبان یک رابط پشتیبانی می کند:
    • مالکان می‌توانند روش‌هایی را به انتهای رابط‌ها یا فیلدهایی را به بسته‌بندی‌ها اضافه کنند. این بدان معناست که کد نسخه در طول سال‌ها آسان‌تر است، و همچنین هزینه سال به سال کمتر است (انواع را می‌توان در محل اصلاح کرد و نیازی به کتابخانه‌های اضافی برای هر نسخه رابط نیست).
    • رابط های برنامه افزودنی را می توان در زمان اجرا به جای سیستم نوع متصل کرد، بنابراین نیازی به تغییر برنامه های افزودنی پایین دستی به نسخه های جدیدتر اینترفیس ها نیست.
  • یک رابط AIDL موجود می‌تواند مستقیماً زمانی استفاده شود که صاحب آن بخواهد آن را تثبیت کند. قبل از این، یک نسخه کامل از رابط باید در HIDL ایجاد شود.

ساخت در برابر زمان اجرا AIDL

AIDL دارای سه باطن مختلف است: جاوا، NDK، CPP. برای استفاده از Stable AIDL، همیشه باید از نسخه سیستمی libbinder در system/lib*/libbinder.so استفاده کنید و در /dev/binder صحبت کنید. برای کد روی تصویر فروشنده، این بدان معنی است که libbinder (از VNDK) نمی تواند استفاده شود: این کتابخانه دارای یک C++ API ناپایدار و داخلی های ناپایدار است. در عوض، کد فروشنده بومی باید از پشتیبان NDK AIDL، پیوند در برابر libbinder_ndk (که توسط system libbinder.so پشتیبانی می‌شود) و در برابر کتابخانه‌های NDK ایجاد شده توسط ورودی‌های aidl_interface استفاده کند. برای نام‌های دقیق ماژول، قوانین نام‌گذاری ماژول را ببینید.

یک رابط AIDL HAL بنویسید

برای استفاده از رابط AIDL بین سیستم و فروشنده، این رابط به دو تغییر نیاز دارد:

  • هر نوع تعریف باید با @VintfStability حاشیه نویسی شود.
  • اعلان aidl_interface باید شامل stability: "vintf", .

فقط صاحب یک رابط می تواند این تغییرات را انجام دهد.

وقتی این تغییرات را انجام می دهید، رابط باید در مانیفست VINTF باشد تا کار کند. با استفاده از آزمون VTS vts_treble_vintf_vendor_test این (و الزامات مربوطه، مانند تأیید اینکه رابط های منتشر شده ثابت هستند) را آزمایش کنید. می‌توانید با فراخوانی AIBinder_forceDowngradeToLocalStability در باطن NDK، android::Stability::forceDowngradeToLocalStability در بک‌اند C++، یا android.os.Binder#forceDowngradeToSystemStability قبل از ارسال شیء پشتیبان آن در سیستم، از یک واسط @VintfStability بدون این الزامات استفاده کنید. به فرآیند دیگری تنزل دادن یک سرویس به ثبات فروشنده در جاوا پشتیبانی نمی‌شود، زیرا همه برنامه‌ها در زمینه سیستم اجرا می‌شوند.

علاوه بر این، برای حداکثر قابلیت حمل کد و برای جلوگیری از مشکلات احتمالی مانند کتابخانه های اضافی غیر ضروری، پشتیبان CPP را غیرفعال کنید.

توجه داشته باشید که استفاده از backends در مثال کد زیر صحیح است، زیرا سه Backend وجود دارد (جاوا، NDK و CPP). کد زیر به شما می گوید که چگونه می توان باطن CPP را به طور خاص انتخاب کرد تا آن را غیرفعال کند.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

رابط های AIDL HAL را پیدا کنید

رابط های AOSP Stable AIDL برای HAL ها در همان دایرکتوری های پایه رابط های HIDL در پوشه های aidl قرار دارند.

  • سخت افزار/رابط: برای رابط هایی که معمولاً توسط سخت افزار ارائه می شود
  • چارچوب/سخت افزار/اینترفیس: برای رابط های سطح بالا ارائه شده به سخت افزار
  • سیستم/سخت افزار/اینترفیس: برای رابط های سطح پایین ارائه شده به سخت افزار

شما باید رابط های افزونه را در زیر شاخه های hardware/interfaces دیگر در vendor یا hardware قرار دهید.

رابط های برنامه افزودنی

اندروید با هر نسخه دارای مجموعه ای از رابط های رسمی AOSP است. وقتی شرکای اندرویدی می‌خواهند قابلیت‌هایی را به این رابط‌ها اضافه کنند، نباید مستقیماً این رابط‌ها را تغییر دهند زیرا این بدان معناست که زمان اجرا اندروید آن‌ها با زمان اجرا اندروید AOSP ناسازگار است. برای دستگاه های GMS، اجتناب از تغییر این رابط ها نیز چیزی است که تضمین می کند تصویر GSI می تواند به کار خود ادامه دهد.

برنامه های افزودنی می توانند به دو روش مختلف ثبت شوند:

با این حال یک برنامه افزودنی ثبت شده است، زمانی که اجزای خاص فروشنده (به این معنی که بخشی از AOSP بالادستی نیست) از رابط استفاده می کنند، امکان تداخل ادغام وجود ندارد. با این حال، هنگامی که اصلاحات پایین دستی برای اجزای AOSP بالادست انجام می شود، تداخل ادغام می تواند منجر شود و استراتژی های زیر توصیه می شود:

  • افزودنی های رابط را می توان در نسخه بعدی به AOSP ارتقا داد
  • افزودنی‌های رابط که به انعطاف‌پذیری بیشتر اجازه می‌دهند، بدون تداخل ادغام، می‌توانند در نسخه بعدی به بالادستی شوند

بسته های الحاقی: ParcelableHolder

ParcelableHolder یک Parcelable است که می تواند حاوی Parcelable دیگری باشد. مورد اصلی استفاده از ParcelableHolder این است که یک Parcelable قابل توسعه باشد. به عنوان مثال، تصویری که پیاده‌کننده‌های دستگاه انتظار دارند بتوانند یک Parcelable تعریف‌شده توسط AOSP، AospDefinedParcelable را گسترش دهند تا ویژگی‌های ارزش افزوده آن‌ها را در بر گیرد.

قبلاً بدون ParcelableHolder ، پیاده‌کننده‌های دستگاه نمی‌توانستند یک رابط AIDL پایدار تعریف‌شده توسط AOSP را تغییر دهند، زیرا اضافه کردن فیلدهای بیشتر با خطا همراه خواهد بود:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

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

با استفاده از ParcelableHolder ، صاحب یک parcelable می تواند یک نقطه توسعه را در Parcelable تعریف کند.

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

سپس پیاده‌کننده‌های دستگاه می‌توانند Parcelable خود را برای پسوند خود تعریف کنند.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

در نهایت، Parcelable جدید را می توان با قسمت ParcelableHolder به Parcelable اصلی متصل کرد.


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

نام های نمونه سرور AIDL HAL

طبق قرارداد، خدمات AIDL HAL یک نام نمونه با فرمت $package.$type/$instance دارند. به عنوان مثال، یک نمونه از لرزاننده HAL به عنوان android.hardware.vibrator.IVibrator/default ثبت شده است.

یک سرور AIDL HAL بنویسید

سرورهای AIDL @VintfStability باید در مانیفست VINTF اعلان شوند، به عنوان مثال:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

در غیر این صورت باید خدمات AIDL را به طور معمول ثبت کنند. هنگام اجرای تست های VTS، انتظار می رود که تمام AIDL HAL های اعلام شده در دسترس باشند.

یک مشتری AIDL بنویسید

مشتریان AIDL باید خود را در ماتریس سازگاری، به عنوان مثال، به شرح زیر اعلام کنند:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

HAL موجود را از HIDL به AIDL تبدیل کنید

از ابزار hidl2aidl برای تبدیل رابط HIDL به AIDL استفاده کنید.

ویژگی های hidl2aidl :

  • فایل های .aidl را بر اساس فایل های .hal برای بسته داده شده ایجاد کنید
  • قوانین ساخت بسته AIDL تازه ایجاد شده را با فعال بودن تمام backendها ایجاد کنید
  • ایجاد متدهای ترجمه در پشتیبان جاوا، CPP و NDK برای ترجمه از انواع HIDL به انواع AIDL
  • قوانین ساخت را برای کتابخانه های ترجمه با وابستگی های مورد نیاز ایجاد کنید
  • برای اطمینان از اینکه شمارشگرهای HIDL و AIDL دارای مقادیر یکسانی در backendهای CPP و NDK هستند، اظهارات ثابت ایجاد کنید.

برای تبدیل بسته ای از فایل های hal به فایل های .aidl مراحل زیر را دنبال کنید:

  1. ابزار واقع در system/tools/hidl/hidl2aidl را بسازید.

    ساخت این ابزار از آخرین منبع کامل ترین تجربه را ارائه می دهد. می توانید از آخرین نسخه برای تبدیل رابط ها در شاخه های قدیمی تر از نسخه های قبلی استفاده کنید.

    m hidl2aidl
  2. ابزار را با دایرکتوری خروجی و به دنبال آن بسته ای که قرار است تبدیل شود اجرا کنید.

    به صورت اختیاری، از آرگومان -l برای افزودن محتویات یک فایل مجوز جدید به بالای همه فایل های تولید شده استفاده کنید. حتما از مجوز و تاریخ صحیح استفاده کنید.

    hidl2aidl -o <output directory> -l <file with license> <package>

    به عنوان مثال:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
  3. فایل های تولید شده را بخوانید و مشکلات مربوط به تبدیل را برطرف کنید.

    • conversion.log حاوی هر گونه مشکل کنترل نشده است که ابتدا باید برطرف شود.
    • فایل‌های .aidl ایجاد شده ممکن است هشدارها و پیشنهاداتی داشته باشند که ممکن است نیاز به اقدام داشته باشند. این نظرات با // شروع می شود.
    • از فرصت برای پاکسازی و بهبود بسته استفاده کنید.
    • حاشیه‌نویسی @JavaDerive را برای ویژگی‌هایی که ممکن است مورد نیاز باشد، مانند toString یا equals بررسی کنید.
  4. فقط اهداف مورد نیاز خود را بسازید.

    • پشتیبان هایی که استفاده نمی شوند را غیرفعال کنید. باطن NDK را بر باطن CPP ترجیح دهید، به انتخاب زمان اجرا مراجعه کنید.
    • کتابخانه‌های ترجمه یا هر کد تولید شده‌ای که استفاده نمی‌شود را حذف کنید.
  5. تفاوت های عمده AIDL/HIDL را ببینید.

    • استفاده از Status داخلی AIDL و استثناها معمولاً رابط را بهبود می بخشد و نیاز به نوع وضعیت خاص رابط دیگر را برطرف می کند.
    • آرگومان‌های واسط AIDL در متدها به‌طور پیش‌فرض مانند HIDL @nullable نیستند.

SEPolicy برای AIDL HAL

یک نوع سرویس AIDL که برای کد فروشنده قابل مشاهده است باید دارای ویژگی hal_service_type باشد. در غیر این صورت، پیکربندی sepolicy مانند هر سرویس AIDL دیگری است (اگرچه ویژگی های خاصی برای HAL ها وجود دارد). در اینجا یک تعریف مثال از زمینه خدمات HAL آورده شده است:

    type hal_foo_service, service_manager_type, hal_service_type;

برای اکثر سرویس‌هایی که توسط پلتفرم تعریف شده‌اند، یک زمینه سرویس با نوع صحیح از قبل اضافه شده است (به عنوان مثال، android.hardware.foo.IFoo/default قبلاً به عنوان hal_foo_service علامت‌گذاری شده است). با این حال، اگر یک کلاینت فریمورک از چندین نام نمونه پشتیبانی می کند، نام نمونه های اضافی باید در فایل های service_contexts مخصوص دستگاه اضافه شود.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

وقتی نوع جدیدی از HAL ایجاد می کنیم باید ویژگی های HAL اضافه شوند. یک ویژگی HAL خاص ممکن است با چندین نوع سرویس مرتبط باشد (که هرکدام ممکن است چندین نمونه داشته باشند همانطور که قبلاً بحث کردیم). برای یک HAL، foo ، ما hal_attribute(foo) داریم. این ماکرو ویژگی های hal_foo_client و hal_foo_server را تعریف می کند. برای یک دامنه معین، ماکروهای hal_client_domain و hal_server_domain یک دامنه را با ویژگی HAL معین مرتبط می‌کنند. به عنوان مثال، سرور سیستم که مشتری این HAL است با خط مشی hal_client_domain(system_server, hal_foo) مطابقت دارد. یک سرور HAL به طور مشابه شامل hal_server_domain(my_hal_domain, hal_foo) است. به طور معمول، برای یک ویژگی HAL داده شده، ما همچنین یک دامنه مانند hal_foo_default برای مرجع یا نمونه HAL ایجاد می کنیم. با این حال، برخی از دستگاه ها از این دامنه ها برای سرورهای خود استفاده می کنند. تمایز بین دامنه‌ها برای چندین سرور تنها در صورتی مهم است که چندین سرور داشته باشیم که یک رابط را ارائه می‌کنند و به مجموعه مجوزهای متفاوتی در پیاده‌سازی‌شان نیاز دارند. در همه این ماکروها، hal_foo در واقع یک شیء sepolicy نیست. در عوض، این توکن توسط این ماکروها برای اشاره به گروهی از ویژگی های مرتبط با یک جفت سرور مشتری استفاده می شود.

با این حال، تا کنون، ما hal_foo_service و hal_foo (جفت ویژگی از hal_attribute(foo) ) را مرتبط نکرده ایم. یک ویژگی HAL با خدمات AIDL HAL با استفاده از ماکرو hal_attribute_service مرتبط است (HIDL HAL ها از ماکرو hal_attribute_hwservice استفاده می کنند). به عنوان مثال، hal_attribute_service(hal_foo, hal_foo_service) . این بدان معنی است که فرآیندهای hal_foo_client می توانند HAL را به دست آورند و فرآیندهای hal_foo_server می توانند HAL را ثبت کنند. اجرای این قوانین ثبت نام توسط مدیر زمینه ( servicemanager ) انجام می شود. توجه داشته باشید، نام سرویس‌ها ممکن است همیشه با ویژگی‌های HAL مطابقت نداشته باشند. برای مثال، ممکن است hal_attribute_service(hal_foo, hal_foo2_service) را ببینیم. با این حال، به طور کلی، از آنجایی که این نشان می‌دهد که سرویس‌ها همیشه با هم استفاده می‌شوند، می‌توانیم hal_foo2_service را حذف کنیم و hal_foo_service برای همه زمینه‌های سرویس خود استفاده کنیم. اکثر HAL هایی که چندین hal_attribute_service تنظیم می کنند به این دلیل است که نام اصلی ویژگی HAL به اندازه کافی عمومی نیست و نمی توان آن را تغییر داد.

با کنار هم قرار دادن همه اینها، یک نمونه HAL به این شکل است:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

رابط های افزودنی پیوست شده

یک برنامه افزودنی را می توان به هر رابط بایندر متصل کرد، خواه یک رابط سطح بالا باشد که مستقیماً در سرویس مدیر ثبت شده است یا یک رابط فرعی. هنگام دریافت برنامه افزودنی، باید تأیید کنید که نوع پسوند مطابق انتظار است. برنامه‌های افزودنی را فقط می‌توان از طریق فرآیند خدمت‌رسانی به یک کلاسور تنظیم کرد.

هر زمان که یک برنامه افزودنی عملکرد HAL موجود را تغییر می‌دهد، باید از افزونه‌های پیوست استفاده شود. هنگامی که عملکرد کاملاً جدیدی مورد نیاز است، این مکانیسم نیازی به استفاده ندارد و یک رابط برنامه افزودنی می تواند مستقیماً با مدیر سرویس ثبت شود. اینترفیس های افزودنی پیوست شده زمانی که به واسط های فرعی متصل می شوند بیشترین معنا را دارند، زیرا این سلسله مراتب ممکن است عمیق یا چند نمونه ای باشند. استفاده از یک برنامه افزودنی جهانی برای منعکس کردن سلسله مراتب رابط کلاسور یک سرویس دیگر به حسابداری گسترده نیاز دارد تا عملکردی معادل برای برنامه های افزودنی پیوست شده مستقیم ارائه کند.

برای تنظیم یک افزونه در binder، از API های زیر استفاده کنید:

  • در باطن NDK: AIBinder_setExtension
  • در باطن جاوا: android.os.Binder.setExtension
  • در باطن CPP: android::Binder::setExtension
  • در باطن Rust: binder::Binder::set_extension

برای دریافت پسوند بر روی بایندر، از API های زیر استفاده کنید:

  • در باطن NDK: AIBinder_getExtension
  • در باطن جاوا: android.os.IBinder.getExtension
  • در باطن CPP: android::IBinder::getExtension
  • در باطن Rust: binder::Binder::get_extension

می‌توانید اطلاعات بیشتری برای این APIها در مستندات تابع getExtension در باطن مربوطه پیدا کنید. نمونه ای از نحوه استفاده از برنامه های افزودنی را می توان در سخت افزار/رابط/تست/برنامه افزودنی/ویبراتور یافت.

تفاوت های عمده AIDL و HIDL

هنگام استفاده از AIDL HAL یا استفاده از رابط های AIDL HAL، از تفاوت ها در مقایسه با نوشتن HIDL HAL آگاه باشید.

  • نحو زبان AIDL به جاوا نزدیکتر است. نحو HIDL شبیه به C++ است.
  • همه رابط های AIDL دارای وضعیت خطای داخلی هستند. به جای ایجاد انواع وضعیت سفارشی، ints وضعیت ثابت را در فایل های رابط ایجاد کنید و EX_SERVICE_SPECIFIC در پشتیبان های CPP/NDK و ServiceSpecificException در باطن جاوا استفاده کنید. به مدیریت خطا مراجعه کنید.
  • AIDL به طور خودکار threadpool ها را هنگام ارسال اشیاء binder شروع نمی کند. آنها باید به صورت دستی شروع شوند (به مدیریت رشته مراجعه کنید).
  • AIDL در خطاهای حمل و نقل بررسی نشده سقط نمی شود (HIDL Return در خطاهای بررسی نشده سقط می شود).
  • AIDL فقط می تواند یک نوع را در هر فایل اعلام کند.
  • آرگومان های AIDL را می توان علاوه بر پارامتر خروجی به صورت in/out/inout نیز مشخص کرد (هیچ "بازخوانی همزمان" وجود ندارد).
  • AIDL از fd به عنوان نوع اولیه به جای دسته استفاده می کند.
  • HIDL از نسخه های اصلی برای تغییرات ناسازگار و از نسخه های جزئی برای تغییرات سازگار استفاده می کند. در AIDL، تغییرات سازگار با عقب در جای خود انجام می شود. AIDL هیچ مفهوم صریحی از نسخه های اصلی ندارد. در عوض، این در نام بسته گنجانده شده است. به عنوان مثال، AIDL ممکن است از نام بسته bluetooth2 استفاده کند.
  • AIDL به طور پیش فرض اولویت بلادرنگ را به ارث نمی برد. تابع setInheritRt باید در هر بایندر برای فعال کردن وراثت اولویت بلادرنگ استفاده شود.