Android System & AOSP Engineering/AOSP Framework & Custom Services

안드로이드 HAL의 진화: 레거시 구조부터 HIDL을 거쳐 AIDL HAL까지 완벽 분석

임베디드 친구 2025. 3. 20. 09:50
반응형

안드로이드 플랫폼 레벨의 소스 코드를 분석하거나 하드웨어 제조사(Vendor)의 커스텀 드라이버를 이식하다 보면, 똑같은 카메라나 오디오 제어 코드인데도 어떤 폴더에는 .c/.cpp 파일로 덩그러니 놓여 있고, 어떤 폴더에는 .hal이나 .aidl이라는 낯선 확장자로 인터페이스가 정의된 모습을 보게 됩니다.

구글이 안드로이드 OS의 해묵은 과제였던 '버전 파편화'와 '업데이트 지연' 문제를 해결하기 위해 프레임워크 구조를 완전히 뜯어고쳤기 때문인데요. 이번 포스팅에서는 안드로이드 초창기부터 사용되던 전통적인 레거시 HAL 방식부터 시작해 안드로이드 8.0의 혁신이었던 HIDL, 그리고 현재의 표준으로 자리 잡은 AIDL 기반 HAL까지 그 진화 과정과 코드 레벨의 특징을 명확하게 짚어보겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. 안드로이드 8.0(Oreo) 이전의 레거시 HAL은 프레임워크 프로세스 내부에서 공유 라이브러리(.so)를 직접 로드하여 실행하는 단일 구조였습니다.
  2. 구글은 OS와 벤더 영역을 격리하기 위해 HIDL(.hal) 구조를 도입했고, 각 HAL을 독립된 바인더(Binder) 서비스 프로세스로 분리해 안정성을 높였습니다.
  3. 최신 안드로이드 아키텍처는 시스템 전반의 인터페이스 언어를 통일하기 위해 AIDL 기반 HAL로 변환되는 추세이며, 성능과 유지보수 효율을 극대화하고 있습니다.

1. 안드로이드 HAL의 진화 과정 한눈에 보기

HAL이 발전해 온 과정을 기술적 핵심 키워드 중심으로 비교하면 다음과 같습니다.

분류 기준 전통적 레거시 HAL (Legacy) HIDL 기반 HAL (Android 8.0~) AIDL 기반 HAL (최신 표준)
통신 메커니즘 동일 프로세스 내 직접 호출 (dlopen) 독립 프로세스 간 바인더 IPC 통신 고속 안정화된 바인더 IPC 통신
인터페이스 정의 C/C++ 헤더 파일 (hardware.h) HIDL 전용 언어 (.hal) 안드로이드 표준 인터페이스 언어 (.aidl)
핵심 파일 포맷 공유 라이브러리 (*.so) 스텁/프록시 자동 생성 소스 코드 컴파일 시 C++/Java 타겟 툴체인 생성
서비스 관리자 없음 (시스템 서비스가 직접 로드) hwservicemanager servicemanager (통합 관리)
아키텍처 형태 통짜형 (Passthrough) 프로세스 분리형 (Binderized) 안정형/고성능 분리형 (Binderized)

2. 전통적인 레거시 HAL (Legacy HAL)

안드로이드 8.0 이전에 널리 쓰이던 방식으로, 프레임워크가 하드웨어 제어 라이브러리를 자신의 메모리 공간 안으로 직접 끌어와 주소 포인터를 찍어 누르는 구조였습니다.

2.1 레거시 HAL 구조 및 예제 코드

레거시 구조에서는 구글이 정의한 고정 구조체(hw_module_t)를 파일 내부에 선언해 두면, 시스템이 hw_get_module()을 통해 메모리에 올려 작동했습니다.

C
 
#include <hardware/hardware.h>

// 1. 하드웨어 장치를 열고 닫는 물리 제어 콜백 함수
static int open_device(const struct hw_module_t* module, const char* id, struct hw_device_t** device) {
    // 여기에 실제 디바이스 노드(/dev/example)를 여는 로직이 들어갑니다.
    return 0;
}

// 2. 모듈 메서드 매핑 테이블 정의
static struct hw_module_methods_t module_methods = {
    .open = open_device,
};

// 3. 안드로이드 시스템이 검색할 통로 역할을 하는 마스터 구조체 선언
struct hw_module_t HAL_MODULE_INFO_SYM = {
    .tag = HARDWARE_MODULE_TAG,
    .module_api_version = 1,
    .hal_api_version = 0,
    .id = "example_hal",
    .name = "Legacy Example HAL",
    .author = "Vendor Corporation",
    .methods = &module_methods,
};

2.2 레거시 방식의 치명적인 약점

구조가 단순해서 개발하긴 편했지만, 시스템 서버(system_server) 프로세스가 벤더 제조사들이 만든 .so 라이브러리를 직접 품고 돌리다 보니 소스 코드 하나만 수정해도 전체 안드로이드를 다시 컴파일해야 했습니다. 무엇보다 제조사 코드가 버그로 인해 메모리 오염을 일으키면 안드로이드 OS 전체가 뻗어버리는 치명적인 안정성 문제를 안고 있었습니다.


3. 현대적 독립 프로세스 구조: HIDL 기반 HAL

구글은 안정성 문제를 완전히 뿌리 뽑기 위해 안드로이드 8.0부터 HIDL(HAL Interface Definition Language)을 선언하고 OS와 HAL을 독립된 별개의 프로세스로 떼어내 버렸습니다.

3.1 인터페이스 정의 (.hal)

HAL이 어떤 기능을 제공할지 규격서 명세서를 작성하듯 전용 스크립트 파일에 기술합니다.

코드 스니펫
 
// vendor/example/hardware/device/1.0/IExampleDevice.hal
package vendor.example.hardware.device@1.0;

interface IExampleDevice {
    // 벤더 칩셋에 파라미터를 인가하는 메서드
    setParameter(int32_t param) generates (bool success);
    // 현재 세팅된 파라미터 값을 읽어오는 메서드
    getParameter() generates (int32_t value);
};

3.2 C++ 서버 구현부 (Binderized HAL)

위 인터페이스 파일을 기반으로 hidl-gen 도구를 돌리면 뼈대 코드가 자동으로 튀어나옵니다. 개발자는 이를 상속받아 알맹이 로직만 채워주면 됩니다.

C++
 
#include <vendor/example/hardware/device/1.0/IExampleDevice.h>

using ::android::hardware::Return;

class ExampleDevice : public IExampleDevice {
public:
    // 프레임워크가 바인더로 요청을 보내오면 가동될 실제 로직 구현
    Return<bool> setParameter(int32_t param) override {
        // 하드웨어 칩셋 레지스터 제어
        return true;
    }

    Return<int32_t> getParameter() override {
        return 42; // 드라이버가 읽어온 계측 데이터 값 반환
    }
};

이 구조에서는 HAL이 독립된 별도의 앱 프로세스처럼 백그라운드에 가동되며, 프레임워크와는 오직 바인더 IPC 통로를 통해서만 아규먼트를 주거나 받습니다. HAL 내부에서 널 포인터 예외 에러가 터져 프로세스가 죽더라도, 시스템 서비스는 영향을 받지 않고 크래시된 HAL 프로세스만 깔끔하게 재가동하면 그만입니다.


4. 안드로이드 프레임워크와의 하드웨어 상호작용 흐름

실제 사용자가 스마트폰에서 카메라 앱을 실행하거나 소리를 켤 때, 이 계층들이 어떤 순서로 바톤 터치를 하며 하드웨어까지 도달하는지 핵심 파이프라인 흐름을 정리했습니다.

[1. Application] App UI 가동 (Java/Kotlin)
       │
       ▼
[2. Android Framework] 자바 카운터파트 서비스 활성화 (`CameraService` / `AudioService`)
       │
       ▼
[3. Native System Daemon] 네이티브 단 인프라 전송 (C++ 런타임 레이어)
       │
       ▼
[4. HAL Interface Client] 바인더 프록시를 통해 하드웨어 인터페이스 서비스 핑(Ping)
       │  (Binder IPC 통신 체널 작동)
       ▼
[5. Vendor HAL Process] 독립 가동 중인 벤더 구현 프로세스가 바인더 페이로드 수신 및 연산
       │
       ▼
[6. Linux Kernel Driver] 최하단 커널 드라이버 시스템 콜(`ioctl`) 전송으로 물리 소자 제어

💡 안드로이드 HAL 시스템 개발을 위한 실전 팁

  1. 과도기 프로젝트용 'Passthrough Mode' 활용법: 구형 안드로이드 소스를 최신 최신 아키텍처로 급하게 마이그레이션할 때, 수많은 Android.mk 기반 코드를 한 번에 바인더 프로세스 구조로 바꾸기는 불가능에 가깝습니다. 이때 HIDL이 제공하는 Passthrough 모드를 사용하면, 인터페이스 규격은 HIDL 형식을 갖추되 내부적으로는 레거시 공유 라이브러리(.so)를 기존처럼 직접 로드(dlopen)하여 에뮬레이션해 주므로 개발 공수를 크게 아낄 수 있습니다.
  2. 최신 트렌드: AIDL HAL로의 적극적인 이주 준비: 안드로이드 11 이후부터 구글은 기존 HIDL 구조의 관리가 복잡해지자 앱 아키텍처에서 오랫동안 검증된 AIDL(Android Interface Definition Language)을 HAL 레이어에도 전면 도입하기 시작했습니다. 최근 새로 제작하는 시스템 레이어 모듈이나 커스텀 센서 인터페이스는 HIDL이 아닌 AIDL HAL 구조로 작성하는 것이 구글의 공식 권장사항이며 향후 유지보수면에서도 안전합니다.

⚠️ 흔히 하는 실수

  1. 바인더 메모리 대역폭 한계로 인한 대용량 데이터 전송 에러: 카메라의 원본 YUV 프레임 이미지나 대용량 오디오 레코딩 버퍼 생데이터(RAW Data)를 HAL 프로세스에서 프레임워크 프로세스로 넘길 때, 일반적인 AIDL/HIDL 메서드의 리턴 값이나 아규먼트 배열 변수에 그대로 담아 던지는 실수를 하곤 합니다. 안드로이드 바인더 통신은 프로세스당 공유 메모리 풀 크기가 약 1MB(일부 환경 변동) 수준으로 극히 제한되어 있어서 대용량 생데이터를 그냥 태우면 TransactionTooLargeException을 뿜으며 프로세스가 폭발합니다. 이 경우 데이터 자체는 공유 메모리(Ashmem / SharedMemory)나 HardwareBuffer에 담고, HAL 인터페이스로는 해당 메모리의 주소 가리키는 파일 디스크립터(File Descriptor, FD) 헨들러만 넘겨야 하는 것이 아키텍처 철칙입니다.
  2. hwservicemanager 등록 누락으로 인한 널 포인터 크래시: HIDL 서비스를 열심히 구동 코딩하고 데몬으로 띄웠으나, 시스템 부팅 시 구동되는 바인더 레지스트리 관리 시스템인 hwservicemanager에 인스턴스 등록 코드를 누락하는 실수가 잦습니다. 프레임워크는 부팅이 완료되면 해당 이름의 HAL이 바인더 월드에 존재하는지 찾게 되는데, 등록이 누락되어 있으면 nullptr를 반환받게 되고 이를 예외 처리하지 않은 프레임워크 서비스 단이 통째로 널 참조 크래시를 일으키며 단말기 무한 부팅 리부팅의 늪에 빠지게 됩니다.

5. 결론

안드로이드 HAL 아키텍처는 단순히 하드웨어 코드를 감싸는 단계를 넘어, 보안 강화, 소스 보안 유지, 신속한 OS 판올림 배포라는 거대한 생태계적 목적을 달성하기 위해 발전을 거듭해 왔습니다. 과거의 단순했던 공유 라이브러리 형태에서 프로세스 분리형 바인더 구조인 HIDL로, 그리고 현재의 대통합 표준인 AIDL HAL로 이어지는 기술 흐름의 본질을 정확히 꿰뚫고 있어야 유연하고 탄탄한 시스템 임베디드 코드를 설계할 수 있습니다.

이로써 안드로이드 OS의 뿌리부터 하드웨어 통로까지 이어지는 핵심 3부작 연재(빌드 시스템 - 개발 환경 셋업 - HAL 구조)가 모두 마무리되었습니다!

반응형