لغة AIDL لـ HALs

يوفّر Android 11 إمكانية استخدام لغة AIDL الخاصة بأنظمة HALs في نظام Android. وهذا يجعل من الممكن تنفيذ أجزاء من Android بدون HIDL. الانتقال إلى HALs لاستخدام AIDL حصريًا حيثما أمكن ذلك (عند استخدام HALs في البداية وHIDL وHIDL)

يجب أن تستخدم أجهزة HALs التي تستخدم لغة AIDL للتواصل بين مكوّنات إطار العمل، مثل تلك المتوفرة في system.img، ومكوّنات الأجهزة، مثل تلك المتوفرة في vendor.img، لغة AIDL الثابتة. ومع ذلك، للاتصال داخل أحد الأقسام، على سبيل المثال، من HAL إلى آخر، ليس هناك أي قيود على آلية IPC التي يجب استخدامها.

الحافز

كانت لغة AIDL أطول من HIDL تقريبًا، ويتم استخدامها في العديد من الأماكن الأخرى، كـ بين مكوّنات إطار عمل Android أو في التطبيقات. والآن بعد أن أصبحت AIDL متوافقة، أصبح من الممكن تنفيذ حزمة كاملة ببيئة تشغيل IPC واحدة. لدى AIDL أيضًا نظام تحديد إصدارات أفضل من HIDL.

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

الإنشاء استنادًا إلى بيئة تشغيل AIDL

تحتوي لغة AIDL على ثلاث خلفيات مختلفة: Java وNDK وCPP. لاستخدام الإصدار الثابت AIDL، يجب دائمًا استخدام نسخة النظام من libbinder من خلال system/lib*/libbinder.so والتحدّث عن /dev/binder. بالنسبة إلى الرمز البرمجي على صورة المورّد، يعني هذا أنّه لا يمكن استخدام libbinder (من VNDK) لأنّ هذه المكتبة تحتوي على واجهة برمجة تطبيقات C++ غير مستقرة وأجزاء داخلية غير مستقرة. بدلاً من ذلك، يجب أن يستخدم رمز المورّد الأصلي الواجهة الخلفية NDK الخاصة بـ AIDL، وأن يرتبط بـ libbinder_ndk (المدعوم من النظام libbinder.so)، وأن يرتبط بمكتبات NDK التي تم إنشاؤها بواسطة إدخالات aidl_interface. لمعرفة أسماء الوحدات بالضبط، راجِع قواعد تسمية الوحدات.

كتابة واجهة AIDL HAL

لاستخدام واجهة AIDL بين النظام والمورد، تحتاج الواجهة إلى تغييرين:

  • يجب إضافة تعليقات توضيحية لكل نوع من التعريفات باستخدام @VintfStability.
  • يجب أن يتضمّن بيان aidl_interface السمة stability: "vintf",.

ولا يمكن لأحد إجراء هذه التغييرات سوى مالك الواجهة.

عند إجراء هذه التغييرات، يجب أن تكون الواجهة في بيان VINTF لكي تعمل. اختبِر ذلك (والمتطلبات ذات الصلة، مثل التحقّق من أنّ الواجهات التي تم إصدارها مجمّدة) باستخدام اختبار VTS vts_treble_vintf_vendor_test. يمكنك استخدام واجهة @VintfStability بدون هذه المتطلّبات من خلال استدعاء إما AIBinder_forceDowngradeToLocalStability في خلفية NDK أو android::Stability::forceDowngradeToLocalStability في خلفية C++ أو android.os.Binder#forceDowngradeToSystemStability في خلفية Java على كائن أداة الربط قبل إرسالها إلى عملية أخرى. لا يتوفر الرجوع إلى إصدار سابق من الخدمة إلى استقرار البائع في Java لأن جميع التطبيقات تعمل في سياق النظام.

بالإضافة إلى ذلك، للحصول على أقصى قدر من إمكانية نقل الرمز ولتجنُّب المشاكل المحتملة مثل المكتبات الإضافية غير الضرورية، يمكنك إيقاف الواجهة الخلفية لـ CPP.

يُرجى العِلم أنّ استخدام backends في مثال الرمز أدناه صحيح، إذ إنّ هناك ثلاث خلفيات (Java وNDK وCPP). ويوضّح الرمز أدناه كيفية اختيار الواجهة الخلفية لـ "CPP" تحديدًا لإيقافها.

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

العثور على واجهات AIDL HAL

تتوفّر واجهات AOSP الثابتة AIDL الخاصة بـ HALs في الأدلة الأساسية نفسها مثل واجهات HIDL، في aidl مجلد.

  • الأجهزة/الواجهات
  • أُطر العمل/الأجهزة/الواجهات
  • النظام/الأجهزة/الواجهات

يجب إضافة واجهات الإضافات في أدلّة hardware/interfaces الفرعية الأخرى في vendor أو hardware.

واجهات الإضافات

يوفّر Android مجموعة من واجهات AOSP الرسمية مع كل إصدار. عندما يرغب شركاء Android في إضافة وظائف إلى هذه الواجهات، عليهم عدم تغيير هذه الواجهات مباشرةً لأنّ ذلك يعني أنّ وقت تشغيل Android الخاص بهم غير متوافق مع بيئة تشغيل AOSP Android. بالنسبة إلى أجهزة GMS، فإنّ تجنب تغيير هذه الواجهات هو أيضًا ما يضمن استمرار عمل صورة GSI.

يمكن تسجيل الإضافات بطريقتين مختلفتين:

  • في وقت التشغيل، يُرجى الاطّلاع على الإضافات المرفقة.
  • مستقلّة، ومسجّلة عالميًا وفي VINTF.

ومع ذلك، يتم تسجيل الإضافة، عندما تستخدم المكوّنات الخاصة بالمورّدين (أي ليست جزءًا من AOSP) الواجهة، لا يكون من المحتمل حدوث تعارض في عملية الدمج. ومع ذلك، عند إجراء تعديلات في المراحل الأساسية لمكوّنات AOSP الأولية، يمكن أن ينشأ عن عمليات الدمج تضاربات في عمليات الدمج، ويُنصَح باستخدام الاستراتيجيات التالية:

  • يمكن تطبيق إضافات الواجهة على AOSP في الإصدار التالي
  • يمكن نشر إضافات واجهات برمجة التطبيقات التي توفر مزيدًا من المرونة بدون تعارضات الدمج في الإصدار التالي

عناصر الإضافات: ParcelableHolder

ParcelableHolder هو Parcelable ويمكن أن يحتوي على Parcelable آخر. إنّ حالة الاستخدام الرئيسية للسمة ParcelableHolder هي جعل Parcelable قابلاً للتوسُّع. على سبيل المثال، يتوقّع منفّذو الأجهزة أن يكونوا قادرين على توسيع Parcelable، AospDefinedParcelable الذي يحدده AOSP، لتضمين الميزات ذات القيمة المضافة.

في السابق، بدون 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) في الإصدارات التالية من Android.

باستخدام ParcelableHolder، يمكن لمالك قطعة العنصر تحديد نقطة تمديد في Parcelable.

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

بعد ذلك، يمكن لأدوات التنفيذ على الأجهزة تحديد Parcelable الخاصة بالإضافة.

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

أخيرًا، يمكن إرفاق Parcelable الجديد بالحقل Parcelable الأصلي باستخدام الحقل ParcelableHolder.


// 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

@VintfStability يجب الإعلان عن خوادم AIDL في بيان VINTF، على سبيل المثال:

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

وبخلاف ذلك، يجب عليهم تسجيل خدمة AIDL بشكل طبيعي. عند إجراء اختبارات VTS، من المتوقع أن تتوفر جميع HALs المُعلَن عنها في AIDL.

كتابة عميل 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 التي تم إنشاؤها حديثًا مع تفعيل جميع الخلفيات
  • أنشِئ طُرق ترجمة في خلفيات Java وCPP وNDK للترجمة من أنواع HIDL إلى أنواع AIDL.
  • إنشاء قواعد تصميم لترجمة المكتبات ذات التبعيات المطلوبة
  • إنشاء تأكيدات ثابتة لضمان أنّ أدوات عدّاد HIDL وAIDL لها القيم نفسها في الواجهة الخلفية لـ 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 في الطُرق ليست @nullable تلقائيًا كما كانت في HIDL.

سياسة SEPolicy لـ AIDL HALs

إنّ نوع خدمة AIDL الذي يظهر لرمز المورّد يجب أن يتضمّن السمة hal_service_type. بخلاف ذلك، يكون ضبط sepolicy هو نفسه أي خدمة AIDL أخرى (على الرغم من وجود سمات خاصة لـ HALs). وفي ما يلي مثال على تعريف سياق خدمة HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

بالنسبة إلى معظم الخدمات التي تحدّدها المنصة، تتم إضافة سياق خدمة بالنوع الصحيح (على سبيل المثال، سيتم وضع علامة hal_foo_service بالفعل على android.hardware.foo.IFoo/default). ومع ذلك، إذا كان عميل إطار العمل يتيح أسماء مثيلات متعددة، يجب إضافة أسماء مثيلات إضافية في ملفات 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_foo_default كمرجع أو أمثلة على HALs لسمة HAL معيّنة. ومع ذلك، تستخدم بعض الأجهزة هذه النطاقات لخوادمها الخاصة. لا يُعد التمييز بين النطاقات للخوادم المتعددة أمرًا مهمًا إلا إذا كانت لدينا خوادم متعددة تعمل على الواجهة نفسها وتحتاج إلى مجموعة أذونات مختلفة في عمليات التنفيذ بها. في جميع وحدات الماكرو هذه، لا تُعد hal_foo في الواقع كائن sepolicy. بدلاً من ذلك، تستخدم وحدات الماكرو هذه هذا الرمز للإشارة إلى مجموعة السمات المرتبطة بزوج من خادم العميل.

لم نربط حتى الآن hal_foo_service وhal_foo (زوج السمات من hal_attribute(foo)). ترتبط سمة HAL بخدمات AIDL HAL باستخدام وحدة ماكرو hal_attribute_service (تستخدم فلاتر HIDL الماكرو hal_attribute_hwservice). مثلاً: hal_attribute_service(hal_foo, hal_foo_service) وهذا يعني أنّ عمليات hal_foo_client يمكنها إيقاف HAL، ويمكن لعمليات hal_foo_server تسجيله. يتولى مدير السياق (servicemanager) تنفيذ قواعد التسجيل هذه. ملاحظة: قد لا تتوافق أسماء الخدمات دائمًا مع سمات HAL. على سبيل المثال، يمكن أن نرى hal_attribute_service(hal_foo, hal_foo2_service). بشكل عام، وبما أنّ ذلك يشير إلى استخدام الخدمات معًا دائمًا، يمكننا إزالة hal_foo2_service واستخدام hal_foo_service لجميع سياقات خدمتنا. معظم HALs التي تحدد 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" الحالي. وعند الحاجة إلى وظائف جديدة تمامًا، لا يلزم استخدام هذه الآلية، ويمكن تسجيل واجهة الإضافة لدى مدير الخدمة مباشرةً. تكون واجهات الإضافات المرفقة أكثر منطقية عندما تكون متصلة بواجهات فرعية، لأنّ هذه التدرّجات الهرمية قد تكون عميقة أو متعددة المثيلات. يتطلب استخدام إضافة عمومية لمطابقة التسلسل الهرمي لواجهة الحافظ لخدمة أخرى إجراء تدقيق مكثف للسجلات لتوفير وظائف مكافئة للإضافات المرفقة بشكل مباشر.

لضبط إضافة على المربط، استخدم واجهات برمجة التطبيقات التالية:

  • في خلفية NDK: AIBinder_setExtension
  • في خلفية Java: android.os.Binder.setExtension
  • في خلفية CPP: android::Binder::setExtension
  • في خلفية Rust: binder::Binder::set_extension

للحصول على إضافة على ملف ربط، استخدِم واجهات برمجة التطبيقات التالية:

  • في خلفية NDK: AIBinder_getExtension
  • في خلفية Java: android.os.IBinder.getExtension
  • في خلفية CPP: android::IBinder::getExtension
  • في خلفية Rust: binder::Binder::get_extension

يمكنك العثور على مزيد من المعلومات عن واجهات برمجة التطبيقات هذه في مستندات الوظيفة getExtension في الخلفية ذات الصلة. ويمكن العثور على مثال حول كيفية استخدام الإضافات في hardware/interfaces/tests/extension/vibrator.

الاختلافات الرئيسية في AIDL وHIDL

عند استخدام AIDL HALs أو استخدام واجهات AIDL HAL، يجب أن تكون على دراية بالاختلافات مقارنةً بكتابة درجات HIDL HALs.

  • بنية لغة AIDL أقرب إلى Java. بناء جملة HIDL مشابه لـ C++.
  • تحتوي جميع واجهات AIDL على حالات خطأ مضمّنة. بدلاً من إنشاء أنواع حالات مخصّصة، يمكنك إنشاء عدد صحيح لحالة ثابتة في ملفات الواجهة واستخدام EX_SERVICE_SPECIFIC في خلفيات CPP/NDK وServiceSpecificException في خلفية Java. يُرجى الاطّلاع على معالجة الأخطاء.
  • لا تبدأ AIDL تلقائيًا مجموعات سلاسل المحادثات عند إرسال عناصر الربط. ويجب بدء هذه العمليات يدويًا (يُرجى الاطّلاع على إدارة سلاسل المحادثات).
  • لا يتم إيقاف AIDL عند أخطاء النقل التي لم يتم التحقّق منها (يتم إلغاء HIDL Return عند حدوث أخطاء غير محدَّدة).
  • يمكن لـ AIDL تعريف نوع واحد فقط لكل ملف.
  • يمكن تحديد وسيطات AIDL على أنّها داخل/خارج/داخل بالإضافة إلى معلَمة الناتج (لا تتوفّر "عمليات استدعاء متزامنة").
  • تستخدم AIDL fd كنوع أساسي بدلاً من الاسم المعرِّف.
  • يستخدم HIDL الإصدارات الرئيسية للتغييرات غير المتوافقة والإصدارات الثانوية للتغييرات المتوافقة. في AIDL، يتم تنفيذ التغييرات المتوافقة مع الإصدارات القديمة. لا تمتلك لغة AIDL مفهومًا صريحًا للإصدارات الرئيسية، بل يتم دمجها في أسماء الحزم بدلاً من ذلك. على سبيل المثال، قد تستخدم AIDL اسم الحزمة bluetooth2.
  • لا تكتسب لغة AIDL الأولوية تلقائيًا في الوقت الفعلي. يجب استخدام الدالة setInheritRt لكل ملف ربط لتفعيل اكتساب الأولوية في الوقت الفعلي.