AIDL עבור HALs

אנדרואיד 11 מציגה את היכולת להשתמש ב-AIDL עבור HALs באנדרואיד. זה מאפשר ליישם חלקים מאנדרואיד ללא HIDL. העבר HALs לשימוש ב-AIDL באופן בלעדי במידת האפשר (כאשר HALs במעלה הזרם משתמשים ב-HIDL, יש להשתמש ב-HIDL).

HALs המשתמשים ב-AIDL כדי לתקשר בין רכיבי מסגרת, כגון אלה ב- system.img , ורכיבי חומרה, כגון אלה ב- vendor.img , חייבים להשתמש ב-Stable AIDL. עם זאת, כדי לתקשר בתוך מחיצה, למשל מ-HAL אחד לאחר, אין הגבלה על מנגנון IPC לשימוש.

מוֹטִיבָצִיָה

AIDL קיים יותר מ-HIDL, והוא נמצא בשימוש במקומות רבים אחרים, כמו בין רכיבי מסגרת אנדרואיד או באפליקציות. כעת, כאשר ל-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 backend, android::Stability::forceDowngradeToLocalStability ב-C++ backend, או android.os.Binder#forceDowngradeToSystemStability ב-Java backend על אובייקט מקשר שנשלח לתהליך אחר. שדרוג לאחור של שירות ליציבות הספק אינו נתמך ב-Java מכיוון שכל האפליקציות פועלות בהקשר מערכתי.

בנוסף, עבור ניידות קוד מקסימלית וכדי למנוע בעיות פוטנציאליות כגון ספריות נוספות מיותרות, השבת את ה-CPP backend.

שים לב שהשימוש ב- backends בדוגמה של הקוד שלהלן הוא נכון, שכן ישנם שלושה backends (Java, NDK ו-CPP). הקוד שלהלן אומר כיצד לבחור את ה-CPP backend באופן ספציפי, כדי להשבית אותו.

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

מציאת ממשקי AIDL HAL

ממשקי AIDL יציבים של AOSP עבור HALs נמצאים באותם ספריות בסיס כמו ממשקי HIDL, בתיקיות aidl .

  • חומרה/ממשקים
  • מסגרות/חומרה/ממשקים
  • מערכת/חומרה/ממשקים

עליך להכניס ממשקי הרחבות לתוך ספריות משנה אחרות hardware/interfaces vendor או hardware .

ממשקי הרחבה

לאנדרואיד יש קבוצה של ממשקי AOSP רשמיים בכל מהדורה. כאשר שותפי אנדרואיד רוצים להוסיף פונקציונליות לממשקים אלה, הם לא צריכים לשנות אותם ישירות מכיוון שזה אומר שזמן הריצה של אנדרואיד שלהם אינו תואם לזמן הריצה של אנדרואיד AOSP. עבור מכשירי GMS, הימנעות משינוי ממשקים אלה היא גם מה שמבטיח שתמונת GSI יכולה להמשיך לעבוד.

הרחבות יכולות להירשם בשתי דרכים שונות:

עם זאת הרחבה רשומה, כאשר רכיבים ספציפיים לספק (כלומר לא חלק מ-AOSP במעלה הזרם) משתמשים בממשק, אין אפשרות להתנגשות מיזוג. עם זאת, כאשר מתבצעים שינויים במורד הזרם של רכיבי AOSP במעלה הזרם, עלולות להיגרם התנגשויות מיזוג, ומומלצות האסטרטגיות הבאות:

  • ניתן לשדר את תוספות הממשק ל-AOSP במהדורה הבאה
  • ניתן להעלות תוספות ממשק המאפשרות גמישות נוספת, ללא התנגשויות מיזוג, במהדורה הבאה

הרחבה Parcelables: 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 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): לספרייה זו יש API לא יציב של C++ וחלק פנימי לא יציב. במקום זאת, קוד ספק מקורי חייב להשתמש ב-NDK backend של 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.

מדיניות כללית עבור 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 עבור כל הקשרי השירות שלנו. רוב ה-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 קיים. כאשר יש צורך בפונקציונליות חדשה לחלוטין, אין צורך להשתמש במנגנון זה, וניתן לרשום ממשק הרחבה ישירות למנהל השירות. ממשקי הרחבות מצורפים הם הגיוניים ביותר כאשר הם מחוברים לממשקי משנה, מכיוון שההיררכיות הללו עשויות להיות עמוקות או מרובות מופעים. שימוש בהרחבה גלובלית כדי לשקף את היררכיית ממשק הקלסר של שירות אחר ידרוש הנהלת חשבונות מקיפה כדי לספק פונקציונליות מקבילה להרחבות המצורפות ישירות.

כדי להגדיר הרחבה ב-Binder, השתמש בממשקי ה-API הבאים:

  • בקצה האחורי של NDK: AIBinder_setExtension
  • ב-Java backend: android.os.Binder.setExtension
  • ב-CPP backend: android::Binder::setExtension
  • ב-Rust backend: binder::Binder::set_extension

כדי לקבל הרחבה על קלסר, השתמש בממשקי ה-API הבאים:

  • בקצה האחורי של NDK: AIBinder_getExtension
  • ב-Java backend: android.os.IBinder.getExtension
  • ב-CPP backend: android::IBinder::getExtension
  • ב-Rust backend: binder::Binder::get_extension

תוכל למצוא מידע נוסף עבור ממשקי API אלה בתיעוד של הפונקציה getExtension ב-backend המתאים. דוגמה לשימוש בהרחבות ניתן למצוא בחומרה/ממשקים/בדיקות/הרחבה/רטט .

הבדלי AIDL/HIDL עיקריים

בעת שימוש ב-AIDL HALs או שימוש בממשקי AIDL HAL, היו מודעים להבדלים בהשוואה לכתיבת HIDL HALs.

  • התחביר של שפת ה-AIDL קרוב יותר ל-Java. תחביר HIDL דומה ל-C++.
  • לכל ממשקי ה-AIDL יש מצבי שגיאה מובנים. במקום ליצור סוגי סטטוסים מותאמים אישית, צור נקודות מצב קבועות בקבצי ממשק והשתמש ב- EX_SERVICE_SPECIFIC ב-CPP/NDK-backends וב- ServiceSpecificException ב-Java backend. ראה טיפול בשגיאות .
  • AIDL אינו מפעיל אוטומטית חיבורי threadpool כאשר אובייקטי קלסר נשלחים. יש להפעיל אותם באופן ידני (ראה ניהול שרשורים ).
  • AIDL אינו מבטל על שגיאות העברה לא מסומנות (HIDL Return מבטל על שגיאות לא מסומנות).
  • AIDL יכולה להכריז רק על סוג אחד לכל קובץ.
  • ניתן לציין ארגומנטים של AIDL כ-in/out/inout בנוסף לפרמטר הפלט (אין "התקשרות סינכרונית").
  • AIDL משתמש ב-fd כסוג הפרימיטיבי במקום הידית.
  • HIDL משתמשת בגרסאות עיקריות לשינויים לא תואמים וגרסאות קטנות לשינויים תואמים. ב-AIDL, שינויים תואמים לאחור נעשים במקום. ל-AIDL אין מושג מפורש של גרסאות מרכזיות; במקום זאת, זה משולב בשמות החבילות. לדוגמה, AIDL עשוי להשתמש בשם החבילה bluetooth2 .
  • AIDL לא יורש עדיפות בזמן אמת כברירת מחדל. יש להשתמש בפונקציה setInheritRt לכל מקשר כדי לאפשר תורשת עדיפות בזמן אמת.