HAL용 AIDL

Android 11은 Android에서 HAL용 AIDL을 사용할 수 있는 기능을 제공합니다. 이를 사용하면 HIDL 없이도 Android의 일부를 구현할 수 있습니다. 가능하면 HAL을 전환하여 AIDL을 독점적으로 사용합니다(업스트림 HAL이 HIDL을 사용하는 경우 HIDL을 사용해야 함).

system.img의 경우와 같은 프레임워크 구성요소 및 vendor.img의 경우와 같은 하드웨어 구성요소 간에 커뮤니케이션하는 HAL은 안정적 AIDL을 사용해야 합니다. 그러나, 파티션 내에서 통신하기 위해(예: 한 HAL에서 다른 HAL로 통신) 사용할 수 있는 IPC 메커니즘은 제한이 없습니다.

동기

AIDL은 HIDL보다 오래 되었으며 Android 프레임워크 구성요소 간이나 앱 내부 등 많은 곳에 사용됩니다. 이제 AIDL의 안정성이 지원되므로 단일 IPC 런타임으로 전체 스택을 구현할 수 있습니다. AIDL에는 HIDL보다 더 나은 버전 관리 시스템이 있습니다.

  • 단일 IPC 언어를 사용하게 되어 한 가지만 학습, 디버그, 최적화, 보호하면 됩니다.
  • AIDL은 인터페이스 소유자가 실행 중인 버전 관리 시스템을 지원합니다.
    • 소유자는 인터페이스 끝부분에 메서드를 추가하거나 parcelable에 필드를 추가할 수 있습니다. 이에 따라 시간이 지나면서 더 간편하게 코드의 버전을 관리할 수 있으며, 유형을 바로 수정할 수 있고 인터페이스 버전별로 라이브러리를 추가하지 않아도 되므로 전년 대비 비용이 감소합니다.
    • 확장 인터페이스는 유형 시스템이 아닌 런타임에 첨부할 수 있으므로 새 버전의 인터페이스에 다운스트림 확장을 리베이스하지 않아도 됩니다.
  • 소유자가 기존 AIDL 인터페이스를 안정화하기로 선택하면 직접 사용할 수 있습니다. 이렇게 하려면 전체 인터페이스 사본이 HIDL로 제작되어야 합니다.

AIDL HAL 인터페이스 작성

AIDL 인터페이스를 시스템 및 공급업체 간에 사용하려면 인터페이스의 다음 두 가지를 변경해야 합니다.

  • 모든 유형 정의는 @VintfStability로 주석을 달아야 합니다.
  • aidl_interface 선언에 stability: "vintf",를 포함해야 합니다.

인터페이스 소유자만 이와 같이 변경할 수 있습니다.

이렇게 변경할 경우 인터페이스가 VINTF 매니페스트에 있어야 작동합니다. VTS 테스트 vts_treble_vintf_vendor_test를 사용하여 이 항목 및 이 항목과 관련된 요구사항(예: 출시된 인터페이스가 고정되어 있는지 확인)을 테스트합니다. 바인더 객체가 다른 프로세스로 전송되기 전에 바인더 객체에서 NDK 백엔드의 AIBinder_forceDowngradeToLocalStability, C++ 백엔드의 android::Stability::forceDowngradeToLocalStability, 자바 백엔드의 android.os.Binder#forceDowngradeToSystemStability 중 하나를 호출하여 이러한 요구사항 없이 @VintfStability 인터페이스를 사용할 수 있습니다. 모든 앱이 시스템 컨텍스트에서 실행되므로 서비스를 공급업체로 다운그레이드하면 자바에서 안정성이 지원되지 않습니다.

또한 최대의 코드 이동성을 보장하고 불필요한 추가 라이브러리와 같은 잠재적인 문제를 방지하려면 CPP 백엔드를 사용 중지해야 합니다.

아래의 코드 예에서는 3가지 백엔드(자바, NDK, CPP)가 있으므로 backends의 사용이 올바릅니다. 아래 코드는 구체적으로 CPP 백엔드를 선택하여 사용 중지하는 방법을 설명합니다.

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

AIDL HAL 인터페이스 찾기

HAL용 AOSP 안정적 AIDL 인터페이스는 HIDL 인터페이스와 동일한 기본 디렉터리인 aidl 폴더에 있습니다.

  • hardware/interfaces
  • frameworks/hardware/interfaces
  • system/hardware/interfaces

확장 인터페이스는 vendor 또는 hardware의 다른 hardware/interfaces 하위 디렉터리에 두어야 합니다.

확장 인터페이스

Android에는 모든 버전에 공식 AOSP 인터페이스가 있습니다. Android 파트너에서 이러한 인터페이스에 기능을 추가하고 싶은 경우 인터페이스를 직접 변경하면 안 됩니다. Android 파트너가 변경한 Android 런타임이 AOSP Android 런타임과 호환되지 않을 수 있기 때문입니다. GMS 기기의 경우 이러한 인터페이스를 변경하지 않아야 계속 GSI 이미지가 작동된다고 보장할 수 있습니다.

확장 인터페이스는 두 가지 방법으로 등록할 수 있습니다.

확장 인터페이스가 등록될 때 공급업체별(업스트림 AOSP의 일부가 아님) 구성요소가 인터페이스를 사용하면 병합 충돌이 일어날 가능성이 없습니다. 하지만, 업스트림 AOSP 구성요소를 다운스트림으로 수정하면 병합 충돌이 발생할 수 있습니다. 이때는 다음과 같은 전략을 사용하는 것이 좋습니다.

  • 인터페이스 추가는 다음 버전에서 AOSP로 업스트림될 수 있습니다.
  • 병합 충돌 없이 유연성을 높여주는 인터페이스 추가 기능은 다음 버전에서 업스트림될 수 있습니다.

확장 Parcelable: ParcelableHolder

ParcelableHolder는 다른 Parcelable을 포함할 수 있는 Parcelable입니다. ParcelableHolder의 주요 사용 사례는 Parcelable을 확장하는 것입니다. 예를 들어, 기기 구현자가 부가 가치 기능을 포함하기 위해 AOSP에 정의된 Parcelable, 즉 AospDefinedParcelable을 확장할 수 있을 것으로 예상되는 이미지입니다.

이전에는 ParcelableHolder가 없으면 기기 구현자가 AOSP에서 정의한 안정적 AIDL 인터페이스를 수정할 수 없었습니다. 다음과 같이 필드를 추가하는 것이 오류였기 때문입니다.

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

앞선 코드에서 본 것처럼 다음 Android 버전에서 Parcelable을 수정하면 기기 구현자가 추가한 필드와 충돌이 일어날 수 있으므로 이 구현은 제대로 작동하지 않습니다.

ParcelableHolder를 사용하면 parcelable 소유자가 Parcelable에서 확장 지점을 정의할 수 있습니다.

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

그러면 기기 구현자는 확장 parcelable에 맞는 확장 Parcelable을 정의할 수 있습니다.

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

마지막으로 ParcelableHolder 필드를 통해 새 Parcelable을 원래의 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 런타임에 따라 구축하기

AIDL에는 자바, NDK, CPP라는 3가지 백엔드가 있습니다. 안정적 AIDL을 사용하려면 항상 system/lib*/libbinder.so에 있는 libbinder의 시스템 사본을 사용하고 /dev/binder에서 대화해야 합니다. 공급업체 이미지의 코드의 경우 이는 VNDK의 libbinder를 사용할 수 없음을 의미합니다. 이 라이브러리에는 불안정한 C++ API 및 불안정한 내부 요소가 포함되어 있습니다. 대신 기본 공급업체 코드는 AIDL의 NDK 백엔드, libbinder_ndk에 연결되는 링크(시스템 libbinder.so에서 지원됨), aidl_interface 입력값에 의해 생성된 -ndk_platform 라이브러리로 연결되는 링크를 사용해야 합니다.

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로 변환

HIDL 인터페이스를 AIDL로 변환하려면 hidl2aidl 도구를 사용하세요.

hidl2aidl 기능은 다음과 같습니다.

  • 지정된 패키지의 .hal 파일을 기반으로 .aidl 파일을 만듭니다.
  • 모든 백엔드가 사용 설정된 상태에서 새로 생성된 AIDL 패키지의 빌드 규칙을 만듭니다.
  • HIDL 유형을 AIDL 유형으로 변환하기 위해 자바, CPP, NDK 백엔드에 변환 메서드를 만듭니다.
  • 필수 종속 항목을 포함하는 변환 라이브러리에 맞는 빌드 규칙을 만듭니다.
  • CPP와 NDK 백엔드에서 HIDL과 AIDL 열거자가 같은 값을 갖는지 확인할 수 있는 정적 어설션을 만듭니다.

.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 파일에는 조치가 필요한 경고와 제안이 있을 수 있습니다. 이 댓글은 //로 시작합니다.
    • 패키지를 정리하고 개선할 수 있습니다.
    • toString 또는 equals와 같이 필요할 수 있는 기능의 @JavaDerive 주석을 확인합니다.
  4. 필요한 타겟만 빌드합니다.

    • 사용하지 않는 백엔드는 사용 중지합니다. CPP 백엔드보다 NDK 백엔드를 선호합니다(런타임 선택 참고).
    • 사용하지 않는 변환 라이브러리 또는 생성된 코드는 삭제합니다.
  5. AIDL과 HIDL의 주요 차이점을 참고합니다.

    • AIDL의 기본 Status 및 예외를 사용하면 일반적으로 인터페이스가 개선되고 다른 인터페이스별 상태 유형을 사용하지 않아도 됩니다.
    • 메서드의 AIDL 인터페이스 인수는 HIDL에서처럼 기본적으로 @nullable이 아닙니다.

AIDL HAL용 sepolicy

공급업체 코드에 표시되는 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_domainhal_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_servicehal_foo(hal_attribute(foo)의 속성 쌍)를 연결하지 않았습니다. HAL 속성은 hal_attribute_service 매크로를 사용하여 AIDL HAL 서비스와 연결됩니다(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_attribute_service를 설정하는 HAL 대부분은 원래 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의 기능을 수정할 때마다 사용되어야 합니다. 완전히 새로운 기능이 필요한 경우 이 메커니즘을 사용할 필요가 없으며 확장 인터페이스를 서비스 관리자에 직접 등록할 수 있습니다. 첨부된 확장 인터페이스는 하위 인터페이스에 연결될 때 가장 적합합니다. 이러한 계층 구조는 깊거나 멀티 인스턴스일 수 있기 때문입니다. 전역 확장 인터페이스를 사용하여 다른 서비스의 바인더 인터페이스 계층 구조를 미러링하려면 직접 연결된 확장 인터페이스에 상응하는 기능을 제공하기 위해 광범위한 기록 관리를 제공해야 합니다.

바인더에 확장을 설정하려면 다음 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 함수 문서에서 확인할 수 있습니다. 확장 프로그램을 사용하는 방법의 예는 hardware/interfaces/tests/extension/vibrator에서 확인할 수 있습니다.

AIDL/HIDL 주요 차이점

AIDL HAL을 사용하거나 AIDL HAL 인터페이스를 사용할 때 HIDL HAL 작성과의 차이점에 유념하세요.

  • AIDL 언어의 구문은 자바와 비슷하고 HIDL 구문은 C++와 유사합니다.
  • 모든 AIDL 인터페이스에는 내장된 오류 상태가 있습니다. 맞춤 상태 유형을 생성하는 대신 인터페이스 파일에서 상수 상태 int를 생성하고 CPP/NDK 백엔드에 있는 EX_SERVICE_SPECIFIC 및 자바 백엔드에 있는 ServiceSpecificException을 사용합니다. 오류 처리를 참고하세요.
  • 바인더 객체가 전송될 때 AIDL에서 스레드 풀이 자동으로 시작되지 않습니다. 스레드 풀을 수동으로 시작해야 합니다(스레드 관리 참고).
  • AIDL은 확인되지 않은 전송 오류로 취소되지 않습니다(HIDL Return은 확인되지 않은 오류로 취소됨).
  • AIDL은 파일당 하나의 유형만 선언할 수 있습니다.
  • AIDL 인수는 출력 매개변수와 함께 in/out/inout으로 지정할 수 있습니다('동기식 콜백'은 없음).
  • AIDL은 핸들 대신 fd를 기본형으로 사용합니다.
  • HIDL은 호환되지 않는 변경사항에는 메이저 버전을 사용하고 호환되는 변경사항에는 마이너 버전을 사용합니다. AIDL에서는 하위 호환성 변경사항이 적용됩니다. AIDL에는 주 버전에 관한 명시적인 개념이 없으며 대신 패키지 이름에 통합됩니다. 예를 들어 AIDL은 패키지 이름 bluetooth2를 사용할 수 있습니다.
  • AIDL은 기본적으로 실시간 우선순위를 상속받지 않습니다. 실시간 우선순위 상속을 사용 설정하려면 setInheritRt 함수를 바인더별로 사용해야 합니다.