Android HAL層の内部構造と実装解説: Mokoid LEDサービスの事例
Androidシステムにおいて、ハードウェアとフレームワーク層の連携を可能にする重要な役割を担うのがハードウェア抽象化レイヤー(HAL)です。本稿では、MokoidプロジェクトのLEDサービスを具体例として取り上げ、HAL層の実装がどのように行われ、JNI(Java Native Interface)を介してJavaフレームワーク層と連携するのかを詳細に解説します。
1. HAL層の実装 (C言語)
Android HALは、特定のハードウェア機能への標準化されたインターフェースを提供します。Mokoid LEDサービスの場合、以下の2つのファイルが主要な役割を果たします。
1.1. ヘッダーファイル: hardware/modules/include/mokoid/led.h
このファイルでは、HALモジュールとデバイスの構造体が定義されています。これらはAndroidの汎用ハードウェアインターフェースである hw_module_t と hw_device_t を拡張しています。
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
// LEDハードウェアモジュールの共通構造体
struct led_module_t {
struct hw_module_t common; // 共通モジュールヘッダー
// モジュール初期化関数(今回は実装が提供されない)
int (*init_led)(struct led_control_device_t *dev);
};
// LED制御デバイスの共通構造体
struct led_control_device_t {
struct hw_device_t common; // 共通デバイスヘッダー
int fd; // LEDデバイスのファイルディスクリプタ
// LED制御API群
int (*set_on)(struct led_control_device_t *dev, int32_t led_id);
int (*set_off)(struct led_control_device_t *dev, int32_t led_id);
};
// モジュールIDの定義
#define LED_HARDWARE_MODULE_ID "led"
/** ヘルパーAPI: LED制御デバイスを開く */
static inline int open_led_device(const struct hw_module_t* module,
struct led_control_device_t** device) {
// hw_module_tのopenメソッドを呼び出してデバイスインスタンスを取得
return module->methods->open(module,
LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
led_module_t はHALモジュール全体の情報と、デバイスを開くためのメソッドを保持します。一方、led_control_device_t は具体的なLED操作(点灯/消灯など)のための関数ポインタと、デバイスファイルディスクリプタを格納します。
1.2. 実装ファイル: hardware/modules/led/led.c
このファイルは、led.h で定義されたインターフェースの具体的な実装を提供します。最も重要なのは、モジュールがシステムに登録される際に使用される HAL_MODULE_INFO_SYM 構造体です。
#define LOG_TAG "MokoidLedHAL" // ログタグ
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <mokoid/led.h>
#include <stdlib.h> // malloc, free
#include <string.h> // memset
#include <unistd.h> // sleep
// デバイス開閉関数のプロトタイプ宣言
int led_device_open(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
int led_device_close(struct hw_device_t* device);
// LED点灯処理
int led_turn_on(struct led_control_device_t *dev, int32_t led_id)
{
// 実際のハードウェア操作はioctlなどで行うが、ここではスタブとしてログ出力
ALOGI("LED HAL Stub: set LED %d ON. (fd: %d)", led_id, dev->fd);
// 例: ioctl(dev->fd, IOCTL_LED_ON, led_id);
sleep(1); // 処理デモのために一時停止
return 0;
}
// LED消灯処理
int led_turn_off(struct led_control_device_t *dev, int32_t led_id)
{
// 実際のハードウェア操作はioctlなどで行うが、ここではスタブとしてログ出力
ALOGI("LED HAL Stub: set LED %d OFF. (fd: %d)", led_id, dev->fd);
// 例: ioctl(dev->fd, IOCTL_LED_OFF, led_id);
sleep(1); // 処理デモのために一時停止
return 0;
}
// デバイスオープン処理
int led_device_open(const struct hw_module_t* module, const char* id,
struct hw_device_t** device)
{
struct led_control_device_t *dev_instance;
// デバイス構造体を動的に確保
dev_instance = (struct led_control_device_t *)malloc(sizeof(*dev_instance));
if (!dev_instance) {
ALOGE("Failed to allocate led_control_device_t");
return -ENOMEM;
}
memset(dev_instance, 0, sizeof(*dev_instance));
// 共通ヘッダー情報の初期化
dev_instance->common.tag = HARDWARE_DEVICE_TAG;
dev_instance->common.version = 0;
dev_instance->common.module = module;
dev_instance->common.close = led_device_close;
// LED制御関数の割り当て
dev_instance->set_on = led_turn_on;
dev_instance->set_off = led_turn_off;
// 実際のデバイスファイルを開く(デモのため/dev/nullを使用)
// Mokoidの例では"/dev/cdata-test"だが、ここではより汎用的なスタブ
dev_instance->fd = open("/dev/null", O_RDWR);
if (dev_instance->fd < 0) {
ALOGE("Failed to open LED device: %s", strerror(errno));
free(dev_instance);
return -errno;
}
*device = &dev_instance->common; // 汎用デバイスポインタとして返す
ALOGI("LED device opened successfully. File descriptor: %d", dev_instance->fd);
return 0;
}
// デバイスクローズ処理
int led_device_close(struct hw_device_t* device)
{
struct led_control_device_t *dev_instance = (struct led_control_device_t *)device;
if (dev_instance) {
if (dev_instance->fd >= 0) {
close(dev_instance->fd);
ALOGI("LED device file descriptor %d closed.", dev_instance->fd);
}
free(dev_instance);
}
ALOGI("LED device closed.");
return 0;
}
// モジュールが提供するメソッド群(open関数を定義)
struct hw_module_methods_t led_module_methods = {
.open = led_device_open
};
/**
* HALモジュールのインスタンス
* このシンボル(HAL_MODULE_INFO_SYM)はシステムによって検出され、HALモジュールとしてロードされる
*/
const struct led_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = LED_HARDWARE_MODULE_ID,
.name = "Mokoid Sample LED HAL",
.author = "Open Source Project",
.methods = &led_module_methods, // デバイスオープンメソッド
},
// その他のモジュールAPI(今回は実装が提供されない)
};
led_device_open 関数は、新しい led_control_device_t インスタンスを作成し、その中にLEDの点灯/消灯関数ポインタとデバイスファイルディスクリプタを格納します。HAL_MODULE_INFO_SYM は、AndroidのHALローダーがモジュールを識別するためのエントリポイントです。これにより、システムは指定されたIDでこのLED HALモジュールを見つけ出し、その open メソッドを呼び出すことができます。
2. JNI層の実装 (C++言語)
JNI(Java Native Interface)は、JavaコードとC/C++コードの間で相互運用を可能にするブリッジです。Mokoid LEDサービスの場合、Javaフレームワークからの呼び出しをHAL層に転送する役割を担います。
frameworks/base/service/jni/com_mokoid_server_LedService.cpp
#define ALOG_TAG "MokoidLedJNI"
#include <utils/Log.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <jni.h>
#include <mokoid/led.h> // HALヘッダーをインクルード
// HALデバイスへのポインタを保持
static led_control_device_t *sLedDevice = NULL;
/**
* JNIメソッド: Java層から初期化要求を受け、HALモジュールを開く
* @return 成功した場合 JNI_TRUE, 失敗した場合 JNI_FALSE
*/
static jboolean jni_led_init(JNIEnv *env, jclass clazz)
{
led_module_t* module;
ALOGI("LedService JNI: Initializing HAL module.");
// LED_HARDWARE_MODULE_ID を使用してHALモジュールを検索
if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
ALOGI("LedService JNI: LED HAL module found.");
// モジュールのopenメソッドを呼び出し、デバイスインスタンスを取得
if (open_led_device(&module->common, &sLedDevice) == 0) {
ALOGI("LedService JNI: HAL device operations acquired successfully.");
return JNI_TRUE;
}
}
ALOGE("LedService JNI: Failed to initialize LED HAL operations.");
return JNI_FALSE;
}
/**
* JNIメソッド: Java層からLED点灯要求を受け、HAL層に転送
* @param env JNI環境ポインタ
* @param thiz Javaオブジェクトインスタンス
* @param led_id 制御するLEDのID
* @return 成功した場合 JNI_TRUE, 失敗した場合 JNI_FALSE
*/
static jboolean jni_led_set_on(JNIEnv* env, jobject thiz, jint led_id)
{
ALOGI("LedService JNI: Request to turn LED %d ON.", led_id);
if (sLedDevice == NULL) {
ALOGE("LedService JNI: sLedDevice is not initialized.");
return JNI_FALSE;
} else {
return sLedDevice->set_on(sLedDevice, led_id) == 0 ? JNI_TRUE : JNI_FALSE;
}
}
/**
* JNIメソッド: Java層からLED消灯要求を受け、HAL層に転送
* @param env JNI環境ポインタ
* @param thiz Javaオブジェクトインスタンス
* @param led_id 制御するLEDのID
* @return 成功した場合 JNI_TRUE, 失敗した場合 JNI_FALSE
*/
static jboolean jni_led_set_off(JNIEnv* env, jobject thiz, jint led_id)
{
ALOGI("LedService JNI: Request to turn LED %d OFF.", led_id);
if (sLedDevice == NULL) {
ALOGE("LedService JNI: sLedDevice is not initialized.");
return JNI_FALSE;
} else {
return sLedDevice->set_off(sLedDevice, led_id) == 0 ? JNI_TRUE : JNI_FALSE;
}
}
// Java nativeメソッドとC/C++関数とのマッピング定義
static const JNINativeMethod gMethods[] = {
{"_init", "()Z", (void*)jni_led_init},
{"_set_on", "(I)Z", (void*)jni_led_set_on},
{"_set_off", "(I)Z", (void*)jni_led_set_off},
};
// JNIメソッドテーブルの登録
int register_mokoid_led_methods(JNIEnv* env) {
static const char* const kClassName = "com/mokoid/server/LedService";
jclass clazz;
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
ALOGE("Can't find class %s\n", kClassName);
return -1;
}
if (env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
{
ALOGE("Failed registering methods for %s\n", kClassName);
return -1;
}
return 0;
}
// JNIライブラリロード時のエントリポイント
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed in JNI_OnLoad.\n");
return -1;
}
assert(env != NULL);
// 定義されたJNIメソッドを登録
if (register_mokoid_led_methods(env) != 0) {
return -1;
}
ALOGI("JNI_OnLoad for MokoidLedService completed.");
return JNI_VERSION_1_4;
}
JNI_OnLoad 関数は、このJNIライブラリがロードされる際にシステムによって呼び出されるエントリポイントです。ここから register_mokoid_led_methods を通じて、Javaの LedService クラスのネイティブメソッドがC++関数にマッピングされます。jni_led_init 関数は、Androidの hw_get_module APIを使用してHALモジュールを検索し、そのモジュールの open メソッドを呼び出してデバイスインスタンス sLedDevice を取得します。これにより、以降の点灯/消灯リクエストが直接HAL層の関数に送られるようになります。
3. フレームワーク層の実装 (Java言語)
AndroidアプリケーションがHAL機能にアクセスするためには、システムサービスを介して間接的に行われます。ここではAIDL(Android Interface Definition Language)とJavaサービスの実装を見ます。
3.1. AIDLインターフェース: frameworks/base/core/java/mokoid/hardware/ILedService.aidl
AIDLは、異なるプロセス間で通信するためのインターフェースを定義します。これにより、アプリケーションはサービスの実装の詳細を知ることなく、抽象化されたメソッドを呼び出すことができます。
package mokoid.hardware;
interface ILedService
{
boolean setOn(int ledId);
boolean setOff(int ledId);
boolean setAllOn(); // 例として追加されたメソッド
}
このインターフェースは、LEDの点灯や消灯を行うためのメソッドを宣言しています。システムサービスは、このAIDLによって生成されるスタブを実装し、クライアントからのリクエストを受け付けます。
3.2. Javaサービス: frameworks/base/service/java/com/mokoid/server/LedService.java
このクラスは、AIDLインターフェース ILedService を実装し、JNI層を通じてHALモジュールと通信します。これはAndroidのシステムサービスとして動作します。
package com.mokoid.server;
import android.util.Log;
import mokoid.hardware.ILedService;
/**
* Mokoid LEDサービス。
* アプリケーションからのLED制御リクエストを処理し、JNI層を介してHALに転送する。
*/
public final class LedService extends ILedService.Stub {
private static final String TAG = "MokoidLedService";
// JNIライブラリのロード
static {
// libmokoid_runtime.so はJNIメソッドを定義しているライブラリ
System.loadLibrary("mokoid_runtime");
}
public LedService() {
Log.i(TAG, "Initializing LedService and loading HAL.");
_init(); // JNIを介してHALモジュールを初期化
}
// ネイティブメソッドの宣言
private static native boolean _init();
private static native boolean _set_on(int ledId);
private static native boolean _set_off(int ledId);
/**
* 指定されたLEDを点灯させる。
* @param ledId 制御するLEDのID
* @return 成功した場合true
*/
@Override
public boolean setOn(int ledId) {
Log.i(TAG, "Java層: LED " + ledId + " を点灯します。");
return _set_on(ledId); // ネイティブメソッドを呼び出し
}
/**
* 指定されたLEDを消灯させる。
* @param ledId 制御するLEDのID
* @return 成功した場合true
*/
@Override
public boolean setOff(int ledId) {
Log.i(TAG, "Java層: LED " + ledId + " を消灯します。");
return _set_off(ledId); // ネイティブメソッドを呼び出し
}
@Override
public boolean setAllOn() {
Log.d(TAG, "Java層: 全てのLEDを点灯します (未実装)。");
// このメソッドはデモ用であり、JNI層には対応する関数がない
return false;
}
}
LedService のコンストラクタ内で System.loadLibrary("mokoid_runtime") が呼び出され、関連するJNIライブラリ(libmokoid_runtime.so)がロードされます。このとき、JNIライブラリ内の JNI_OnLoad 関数が実行され、JavaのネイティブメソッドがC++の実装にマッピングされます。その後、_init() ネイティブメソッドが呼び出され、JNI層を介してHALモジュールの初期化プロセスが開始されます。
4. Android HAL呼び出しフローの概要
ここまでの実装をまとめると、Androidアプリケーションからハードウェアが制御されるまでの基本的なフローは以下のようになります。
- アプリケーション/フレームワーク層 (Java): アプリケーションは
LedServiceを通じてsetOn(int ledId)などのメソッドを呼び出します。 - Javaサービス実装:
LedServiceクラスのsetOnメソッドは、自身のネイティブメソッド_set_on(int ledId)を呼び出します。このネイティブメソッドは、コンストラクタでロードされたJNIライブラリ内のC++関数にマッピングされています。 - JNI層 (C++):
jni_led_init関数は、最初のサービス初期化時に一度だけ実行され、hw_get_module(LED_HARDWARE_MODULE_ID, ...)を呼び出してHALモジュールをシステムから検索します。- モジュールが見つかると、そのモジュールの
methods->open関数が呼び出され、具体的なデバイス操作のためのled_control_device_tインスタンス(sLedDevice)が取得されます。 _set_onにマッピングされたjni_led_set_on関数は、取得済みのsLedDeviceポインタを介して、HAL層のset_on関数を直接呼び出します。
- HAL層 (C):
led_turn_on関数が実行され、led_control_device_t構造体に含まれるファイルディスクリプタ(fd)や他の情報を使用して、実際のハードウェア(この例ではLinuxデバイスファイル)と対話します。通常、ここではioctlなどのシステムコールを用いてデバイスドライバーにコマンドが送られます。
- デバイスドライバ/ハードウェア: HAL層からのコマンドを受け取ったデバイスドライバが、物理的なLEDハードウェアを制御します。
この多層構造により、Androidはハードウェアの実装詳細を抽象化し、フレームワークの安定性とポータビリティを向上させています。ベンダーはHALインターフェースに準拠するだけでよく、Androidフレームワーク自体を変更する必要がありません。