AIDL لـ HALs

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

يجب أن تستخدم HALs التي تستخدم AIDL للاتصال بين مكونات إطار العمل، مثل تلك الموجودة في system.img ، ومكونات الأجهزة، مثل تلك الموجودة في vendor.img ، Stable 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++، أو android.os.Binder#forceDowngradeToSystemStability في الواجهة الخلفية لـ Java على كائن رابط قبل إرساله. إلى عملية أخرى. إن الرجوع إلى مستوى خدمة ما إلى استقرار البائع غير مدعوم في 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 قابلاً للتوسيع. على سبيل المثال، الصورة التي يتوقع منفذو الأجهزة أن يكونوا قادرين على توسيع 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 HALs المعلنة متاحة.

كتابة عميل 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;

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

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

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

  • في الواجهة الخلفية لـ 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 على حالات خطأ مضمنة. بدلاً من إنشاء أنواع حالة مخصصة، قم بإنشاء ints للحالة الثابتة في ملفات الواجهة واستخدم EX_SERVICE_SPECIFIC في الواجهات الخلفية لـ CPP/NDK و ServiceSpecificException في الواجهة الخلفية لـ Java. راجع معالجة الأخطاء .
  • لا يقوم AIDL ببدء تشغيل مجموعات الخيوط تلقائيًا عند إرسال كائنات الموثق. يجب أن تبدأ يدويًا (راجع إدارة سلاسل الرسائل ).
  • لا يتم إحباط AIDL في حالة وجود أخطاء نقل لم يتم التحقق منها (يتم إحباط HIDL Return في حالة وجود أخطاء لم يتم التحقق منها).
  • بإمكان AIDL الإعلان عن نوع واحد فقط لكل ملف.
  • يمكن تحديد وسيطات AIDL على أنها داخل/خارج/داخل خارج بالإضافة إلى معلمة الإخراج (لا توجد "استدعاءات متزامنة").
  • يستخدم AIDL fd كنوع بدائي بدلاً من المقبض.
  • يستخدم HIDL الإصدارات الرئيسية للتغييرات غير المتوافقة والإصدارات الثانوية للتغييرات المتوافقة. في AIDL، يتم إجراء التغييرات المتوافقة مع الإصدارات السابقة. ليس لدى AIDL مفهوم واضح للإصدارات الرئيسية؛ بدلاً من ذلك، يتم دمج هذا في أسماء الحزم. على سبيل المثال، قد يستخدم AIDL اسم الحزمة bluetooth2 .
  • لا يرث AIDL أولوية الوقت الفعلي افتراضيًا. يجب استخدام الدالة setInheritRt لكل رابط لتمكين وراثة الأولوية في الوقت الفعلي.