서비스 및 데이터 전송

이 섹션에서는 .hal 파일의 인터페이스에 정의된 메서드를 호출하여 서비스를 등록 및 검색하는 방법과 데이터를 서비스로 보내는 방법을 설명합니다.

서비스 등록하기

인터페이스를 구현하는 객체인 HIDL 인터페이스 서버는 이름이 지정된 서비스로 등록할 수 있습니다. 등록된 이름은 인터페이스나 패키지 이름과 관련이 없어도 됩니다. 이름을 지정하지 않으면 'default'라는 이름이 사용됩니다. 'default'는 인터페이스가 동일한 구현 두 가지를 모두 등록할 필요가 없는 HAL에 사용해야 합니다. 예를 들어 각 인터페이스에 정의된 서비스 등록에 관한 C++ 호출은 다음과 같습니다.

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

HIDL 인터페이스의 버전은 인터페이스 자체에 포함됩니다. HIDL 인터페이스의 버전은 자동으로 서비스 등록에 연결되며 모든 HIDL 인터페이스상에서 메서드 호출(android::hardware::IInterface::getInterfaceVersion())을 통해 검색할 수 있습니다. 서버 객체는 등록할 필요가 없으며 HIDL 메서드 매개변수를 통해 다른 프로세스로 전달될 수 있습니다. 이 프로세스는 HIDL 메서드 호출을 서버로 전달합니다.

서비스 검색하기

클라이언트 코드는 특정 인터페이스에 관한 요청을 이름 및 버전별로 생성하여 원하는 HAL 클래스의 getService를 호출합니다.

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

HIDL 인터페이스의 각 버전은 별도의 인터페이스로 취급됩니다. 따라서, IFooService 버전 1.1 및 IFooService 버전 2.2는 모두 'foo_service'로 등록될 수 있으며 양 인터페이스의 getService("foo_service")는 해당 인터페이스에 등록된 서비스를 제공받습니다. 그래서 대부분의 경우 등록 또는 검색에 이름 매개변수를 제공하지 않아도 됩니다('default'로 이름 지정)

공급업체 인터페이스 객체는 반환된 인터페이스의 전송 메서드에도 관여합니다. 패키지 android.hardware.foo@1.0에 있는 인터페이스 IFoo의 경우 IFoo::getService에서 반환한 인터페이스는 항목이 존재하면 기기 매니페스트의 android.hardware.foo에 관해 선언된 전송 메서드를 항상 사용합니다. 전송 메서드를 사용할 수 없는 경우에는 nullptr이 반환됩니다.

경우에 따라 서비스를 제공받지 않은 상태에서 즉시 계속해야 할 수도 있습니다. 예를 들어 이러한 상황은 클라이언트가 서비스 알림을 자체적으로 관리하거나 모든 하드웨어 서비스를 확보하고 이를 검색해야 하는 진단 프로그램(atrace 등)에서 관리하고자 할 때 발생합니다. 이 경우 C++에서는 tryGetService 등의 추가 API가 제공되며 Java에서는 getService("instance-name", false)가 제공됩니다. Java에서 제공되는 기존 API getService 또한 서비스 알림과 함께 사용해야 합니다. 이 API를 사용하면 클라이언트가 이러한 no-retry API 중 하나를 사용하여 서버에 요청한 후에 서버가 자체 등록되는 경합 상태를 피할 수 없습니다.

서비스 종료 알림

서비스가 종료될 때 알림을 받으려는 클라이언트는 프레임워크에서 전달하는 종료 알림을 받을 수 있습니다. 알림을 받으려면 클라이언트가 다음 단계를 따라야 합니다.

  1. HIDL이 아닌 C++ 코드의 HIDL 클래스/인터페이스 hidl_death_recipient를 서브클래스로 만듭니다.
  2. serviceDied() 메서드를 재정의합니다.
  3. hidl_death_recipient 서브클래스의 객체를 인스턴스화합니다.
  4. 모니터링할 서비스의 linkToDeath() 메서드를 호출하여 IDeathRecipient 인터페이스 객체를 전달합니다. 이 메서드는 호출이 발생하는 종료 수신자 또는 프록시의 소유권을 갖지 않습니다.

의사코드 예시(C++와 Java가 유사함):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

동일한 종료 수신자가 여러 다른 서비스에 등록될 수 있습니다.

데이터 전송

.hal 파일에 있는 인터페이스에 정의된 메서드를 호출하여 서비스로 데이터를 전송할 수 있습니다. 메서드에는 다음 두 가지가 있습니다.

  • 차단 메서드는 서버가 결과를 생성할 때까지 대기합니다.
  • 일방향 메서드는 데이터를 한 방향으로만 보내고 차단하지 않습니다. RPC 호출에서 이동 중인 데이터의 양이 구현 한도를 초과하는 경우, 호출이 오류 표시를 차단하거나 반환할 수 있습니다(동작이 결정되지 않음).

값을 반환하지 않으면서 oneway로 선언되지 않은 메서드는 계속 차단 중인 것입니다.

HIDL 인터페이스에서 선언된 모든 메서드는 HAL에서 호출되거나 HAL로 호출되며 한 방향으로만 호출됩니다. 인터페이스가 호출되는 방향을 지정하지 않습니다. HAL에서 시작되는 호출이 필요한 아키텍처는 HAL 패키지에 있는 인터페이스 두 개 이상을 제공해야 하며 각 프로세스에서 적절한 인터페이스를 제공해야 합니다. 클라이언트서버라는 단어는 인터페이스의 호출 방향과 관련하여 사용됩니다. 즉, HAL은 어떤 인터페이스에서는 서버이고 다른 인터페이스에서는 클라이언트일 수 있습니다.

콜백

콜백이라는 단어는 동기식 콜백비동기식 콜백으로 구분되는 두 가지 서로 다른 개념을 나타냅니다.

동기식 콜백은 데이터를 반환하는 일부 HIDL 메서드에서 사용됩니다. 값을 두 개 이상 반환하거나 기본형이 아닌 값을 하나 반환하는 HIDL 메서드는 콜백 함수를 통해 결과를 반환합니다. 값이 하나만 반환되고 기본형인 경우 콜백이 사용되지 않으며 값은 메서드에서 반환됩니다. 서버는 HIDL 메서드를 구현하고 클라이언트는 콜백을 구현합니다.

비동기식 콜백은 HIDL 인터페이스의 서버가 호출을 시작하도록 허용합니다. 이는 첫 번째 인터페이스를 통해 두 번째 인터페이스의 인스턴스를 전달하는 방식으로 진행됩니다. 첫 번째 인터페이스의 클라이언트는 두 번째 인터페이스의 서버 역할을 해야 합니다. 첫 번째 인터페이스의 서버는 두 번째 인터페이스 객체의 메서드를 호출할 수 있습니다. 예를 들어 HAL 구현은 프로세스에서 생성 및 제공된 인터페이스 객체의 메서드를 호출하여 정보를 사용하는 프로세스에 비동기식으로 정보를 다시 보낼 수 있습니다. 비동기식 콜백에 사용되는 인터페이스의 메서드는 차단(및 값을 호출자에게 전달) 또는 oneway일 수 있습니다. 예시를 보려면 HIDL C++에서 '비동기식 콜백'을 참고하세요.

메모리 소유권을 단순화하기 위해 메서드 호출 및 콜백은 in 매개변수만 취하며 out 또는 inout 매개변수를 지원하지 않습니다.

트랜잭션당 한도

트랜잭션당 한도는 HIDL 메서드 및 콜백에서 전송되는 데이터 규모에 적용되지 않습니다. 하지만 트랜잭션당 4KB를 초과하는 호출은 과도한 것으로 간주됩니다. 이 경우 HIDL 인터페이스를 다시 설계하는 것이 좋습니다. HIDL 인프라에서 다수의 동시 트랜잭션을 처리하는 데 사용할 수 있는 리소스에도 한도가 있습니다. 프로세스로 호출을 보내는 다수의 스레드나 프로세스 또는 수신 프로세스에 의해 빠르게 처리되지 않는 다수의 oneway 호출로 인해 여러 트랜잭션이 동시 진행될 수 있습니다. 모든 동시 트랜잭션에 사용할 수 있는 최대 총 공간은 기본적으로 1MB입니다.

잘 설계된 인터페이스의 경우 이러한 리소스 한도를 초과하면 안 됩니다. 한도를 초과하는 경우 리소스 한도를 초과하는 호출은 리소스가 사용 가능해질 때까지 차단하거나 전송 오류를 표시할 수 있습니다. 트랜잭션당 한도를 초과하거나 진행 중인 트랜잭션의 총합으로 인해 HIDL 구현 리소스에 오버플로우가 발생할 때마다, 디버깅을 용이하게 하기 위해 기록됩니다.

메서드 구현

HIDL은 C++ 또는 Java와 같은 대상 언어로 필요한 형식, 메서드, 콜백을 선언하는 헤더 파일을 생성합니다. HIDL에서 정의한 메서드와 콜백의 프로토타입은 클라이언트 및 서버 코드에서 동일합니다. HIDL 시스템은 호출자 측에서 IPC 전송을 위한 데이터를 조직하는 메서드의 프록시 구현을 제공하며 피호출자 측에서 데이터를 메서드의 개발자 구현으로 전달하는 스텁 코드를 제공합니다.

함수 호출자(HIDL 메서드 또는 콜백)는 함수로 전달되는 데이터 구조의 소유권을 가지며 호출 후에도 소유권을 유지합니다. 어떤 경우든지 피호출자는 저장공간을 해제할 필요가 없습니다.

  • C++에서 데이터는 읽기 전용일 수 있으며 쓰기 시 세분화 오류가 발생할 수 있습니다. 데이터는 호출이 진행되는 동안 유효합니다. 클라이언트는 호출 이후에 데이터를 전파하기 위해 데이터를 깊은 복사 처리할 수 있습니다.
  • Java에서 코드는 일반 Java 객체인 데이터의 로컬 복사본을 수신하여 유지, 수정하거나 가비지 컬렉션 처리되도록 할 수 있습니다.

비RPC 데이터 전송

HIDL은 공유 메모리와 빠른 메시지 큐(FMQ)라는 두 가지 방법을 통해 RPC 호출을 사용하지 않고 데이터를 전송하며 두 방법 모두 C++에서만 지원됩니다.

  • 공유 메모리. 내장된 HIDL 유형 memory는 할당된 공유 메모리를 나타내는 객체를 전달하는 데 사용됩니다. 수신 프로세스에서 공유 메모리를 매핑하는 데 사용할 수 있습니다.
  • 빠른 메시지 큐(FMQ). HIDL은 비대기 메시지 전달을 구현하는 템플릿화된 메시지 큐 유형을 제공합니다. 이는 커널 또는 스케줄러를 패스 스루 또는 바인더화 모드에서 사용하지 않습니다(기기 간 통신에는 이러한 속성이 없습니다). 일반적으로 HAL은 큐의 끝을 설정하여 내장 HIDL 유형 MQDescriptorSync 또는 MQDescriptorUnsync의 매개변수를 이용하여 RPC를 통해 전달될 수 있는 객체를 만듭니다. 수신 프로세스는 이 객체를 사용하여 큐의 다른 쪽 끝을 설정할 수 있습니다.
    • 동기식 큐는 오버플로가 허용되지 않으며 리더를 하나만 가질 수 있습니다.
    • 비동기식 큐는 오버플로가 허용되며 다수의 리더를 가질 수 있습니다. 각각의 리더는 시간 내에 데이터를 읽어야 하며 그러지 않으면 데이터가 손실됩니다.
    두 유형 모두 언더플로가 허용되지 않으며(빈 큐에서 읽는 경우 오류 발생), 각 형식은 하나의 작성자만 가질 수 있습니다.

FMQ에 관한 자세한 내용은 빠른 메시지 큐(FMQ)를 참고하세요.