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 HAL

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

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

يمكن فقط لمالك الواجهة إجراء هذه التغييرات.

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

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

لاحظ أن استخدام backends في مثال الكود أدناه صحيح ، حيث توجد ثلاث واجهات خلفية (Java و NDK و CPP). يوضح الكود أدناه كيفية تحديد الواجهة الخلفية لـ CPP على وجه التحديد ، لتعطيلها.

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

البحث عن واجهات AIDL HAL

توجد واجهات AOSP Stable AIDL لـ HALs في نفس الدلائل الأساسية مثل واجهات HIDL ، في مجلدات aidl .

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

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

واجهات التمديد

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

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

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

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

ملحقات التمديد: ParcelableHolder

ParcelableHolder هو Parcelable التي يمكن أن تحتوي على Parcelable آخر. حالة الاستخدام الرئيسية لـ ParcelableHolder هي جعل Parcelable extensible. على سبيل المثال ، الصورة التي يتوقع منفذوها أن يكونوا قادرين على توسيع 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 في الإصدارات التالية من 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

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

أسماء مثيلات خادم 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 ، من المتوقع أن تتوفر جميع فئات 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 المنشأة حديثًا مع تمكين جميع الخلفيات الخلفية
  • إنشاء طرق ترجمة في الخلفيات الخلفية لـ 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 backend على الخلفية CPP ، راجع اختيار وقت التشغيل .
    • قم بإزالة مكتبات الترجمة أو أي من التعليمات البرمجية التي تم إنشاؤها والتي لن يتم استخدامها.
  5. انظر الاختلافات الرئيسية AIDL / HIDL .

    • عادةً ما يؤدي استخدام Status والاستثناءات المضمنة في AIDL إلى تحسين الواجهة وإزالة الحاجة إلى نوع حالة آخر خاص بالواجهة.
    • وسائط واجهة AIDL في الأساليب ليست @nullable افتراضيًا كما كانت في HIDL.

Sepolicy لـ AIDL HALs

يجب أن يحتوي نوع خدمة AIDL المرئي لكود البائع على السمة hal_service_type . وبخلاف ذلك ، فإن التكوين الفردي هو نفس أي خدمة AIDL أخرى (على الرغم من وجود سمات خاصة لـ HALs). فيما يلي مثال لتعريف سياق خدمة 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 كمرجع أو مثال HALs. ومع ذلك ، تستخدم بعض الأجهزة هذه المجالات لخوادمها الخاصة. لا يهم التمييز بين المجالات لخوادم متعددة إلا إذا كان لدينا خوادم متعددة تخدم نفس الواجهة وتحتاج إلى مجموعة أذونات مختلفة في تطبيقاتها. في كل وحدات الماكرو هذه ، لا يعد hal_foo في الواقع كائنًا منفصلاً. بدلاً من ذلك ، يتم استخدام هذا الرمز المميز بواسطة وحدات الماكرو هذه للإشارة إلى مجموعة السمات المرتبطة بزوج خادم عميل.

ومع ذلك ، حتى الآن ، لم نقم بربط hal_foo_service و hal_foo (زوج السمة من hal_attribute(foo) ). ترتبط سمة HAL بخدمات AIDL HAL باستخدام ماكرو hal_attribute_service (HIDL HALs تستخدم ماكرو 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 لجميع سياقات الخدمة لدينا. معظم 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)

واجهات التمديد المرفقة

يمكن إرفاق الامتداد بأي واجهة بيندر ، سواء كانت واجهة من المستوى الأعلى مسجلة مباشرة مع مدير الخدمة أو واجهة فرعية. عند الحصول على تمديد ، يجب عليك التأكد من أن نوع الامتداد كما هو متوقع. لا يمكن تعيين الامتدادات إلا من العملية التي تخدم الموثق.

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

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

  • في الخلفية NDK: AIBinder_setExtension
  • في الواجهة الخلفية لجافا: 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 في الواجهة الخلفية المقابلة. يمكن العثور على مثال على كيفية استخدام الامتدادات في الأجهزة / الواجهات / الاختبارات / التمديد / الهزاز .

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

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

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