android驅動開發基礎

基本環境

  • 開發板:hikey960
  • 代碼:aosp,Android R
  • 開發環境:64bit ubuntu 16.04,

一、build

添加新的lunch選項(新產品):賦值COMMON_LUNCH_CHOICESPRODUCT_MAKEFILES如下 (參考device/sample/products/AndroidProducts.mk,實例參考device/linaro/hikey/AndroidProducts.mk)

PRODUCT_MAKEFILES := \
  $(LOCAL_DIR)/sample_addon.mk
 
COMMON_LUNCH_CHOICES := sample_addon-userdebug

二、編寫Android.mk

Android.mk模板如下所示:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
        hello.c

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE := hello_elf_arm64

include $(BUILD_EXECUTABLE)
  • 編譯報unused parameters錯時,需加上:LOCAL_CFLAGS := -Wno-unused-parameter 以忽略掉該報錯。

三、LOG系統

  • log系統的設備節點:
hikey960:/ # ls /dev/socket/log* -l                                            
srw-rw-rw- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logd
srw-rw-rw- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logdr
s-w--w--w- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logdw
  • 轉載來自文章《Android Log系統介紹 (基於Android N)》的log系統核心圖如下所示,更多細節請參考此文。
    在這裏插入圖片描述                      圖1 Android log系統

  • Android Q、R上面log的緩衝區有:mainsystemradioeventscrash

  • c/c++中打印調試信息:
     ① 宏定義標籤及添加頭文件:
      #define LOG_TAG "hello"
      #include <log/log.h>
     ② Android.mk中聲明所依賴的動態庫:
      LOCAL_SHARED_LIBRARIES := \
        liblog
     ③ 使用宏ALOGV()ALOGD(“xxx”)ALOGI()ALOGW()ALOGE(),進行打印log

  • java中打印log信息:
     ① 導入包:import android.util.Log;
     ② 在類內部定義標籤:private final String TAG = "listview";
     ③ 使用方法Log.d(TAG, "xxx");Log.i(TAG, "xxx");Log.w(TAG, "xxx");Log.e(TAG, "xxx");進行打印

四、init.rc

參考system/core/init/init.cpp中如下代碼,可知/init祖先進程通過解析/init.rc的內容來進行啓動各種本地服務

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");
  • android init語言說明文檔:system/core/init/README.md
  • 在廠商.rc中添加規則,實例:device/linaro/hikey/init.common.rc

五、JNI

以控制LED燈爲背景,進行JNI編程技術的介紹。aosp中JNI技術參考代碼:development/samples/SimpleJNI/

1. java調用jni接口

將native接口的聲明組織到LedControl\app\src\main\java\com\example\lowlevel\LedNative.java

package com.example.lowlevel;

public class LedNative {
	/* 1) 加載jni動態庫 */
    static {
        System.loadLibrary("led_jni");
    }
	/* 2) 使用關鍵字native聲明jni接口 */
    public native int openDev();
    public native int closeDev();
    public native int devOn();
    public native int devOff();
}

然後在class MainActivity中進行調用:

/* 3) 使用上面定義的LedNative類生成對象後調用接口 */
LedNative ledNative = new LedNative();
ledNative.devOn();
  • 注意:普通app沒有權限加載jni動態庫依賴的庫,需將apk預置或傳入到手機的/system/app/下作爲system_app

2. aosp中開發native代碼

 ① 定義JAVA和C/C++間的映射表:

static JNINativeMethod ledMethod[] = { 
  {"openDev", "()I", (void*)openLed},
  {"closeDev", "()I", (void*)closeLed},
  {"devOn", "()I", (void*)ledOn},
  {"devOff", "()I", (void*)ledOff},
};

 ② 通過定義jni層的回調函數jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/),來將上述映射表註冊到java虛擬機:

static const char *classPathName = "com/example/lowlevel/LedNative";

jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
    jint ret;
    JNIEnv* env = NULL;

    ALOGD("------%s", __FUNCTION__);
    ret = vm->GetEnv((void**)&env, JNI_VERSION_1_4);
    if (ret != JNI_OK) {
        ALOGE("ERROR: GetEnv failed");
        return -1; 
    }   

    jclass cls = env->FindClass(classPathName);
    if (cls == NULL) {
        ALOGE("Native registration unable to find class '%s'", classPathName);
        return JNI_FALSE;
    }

    ret = env->RegisterNatives(cls, ledMethod, sizeof(ledMethod)/sizeof(ledMethod[0]));
    if (ret < 0) {
        ALOGE("RegisterNatives failed for '%s'", classPathName);
        return JNI_FALSE;
    }

    return JNI_VERSION_1_4;
}

 ③ Android.mk中指定生成的是動態庫和編譯工具分別如下:

LOCAL_MODULE:= libled_jni
include $(BUILD_SHARED_LIBRARY)
  • 注意:測試時,需要修改設備節點權限:# chmod 0666 /sys/devices/platform/leds/leds/user_led3/brightness,以及關閉selinux:# setenforce 0

3. Android Studio中開發native代碼

 參考我的另一篇文章有詳細開發步驟:Android Studio開發NDK代碼

六、傳統HAL

本章介紹開發傳統HAL代碼和調用HAL的主要流程,如下出現的代碼場景是控制LED燈。

1. 接口聲明

開發HAL在頭文件,定義相應硬件模塊和設備結構,以繼承基礎模塊結構(struct hw_module_t)和基礎設備結構(struct hw_device_t)。另外還有暴露接口給調用者的目的。具體開發流程如下所示:

#ifndef __LED_HAL_H__
#define __LED_HAL_H__

#include <hardware/hardware.h>

__BEGIN_DECLS

/* 1) 定義生成的動態庫前綴的名稱, xxx.default.so */
#define LED_HARDWARE_MODULE_ID "led_hal"

/* 2) 定義專有硬件模塊,繼承 hw_module_t,並擴展要暴露的模塊控制接口 */
typedef struct led_module {
    struct hw_module_t common;
} led_module_t;

/* 3) 定義專有硬件硬件,繼承 hw_device_t */
typedef struct led_device {
    struct hw_device_t common;
/* 4) 擴展要暴露出來的設備控制接口 */
    int (*control)(int enable);
} led_device_t;

/* 5) 用來調用 hw_module_t.methods->open() */
static inline int led_hal_open(const struct hw_module_t* module,
        led_device_t** device) {
    return module->methods->open(module, NULL,
            TO_HW_DEVICE_T_OPEN(device));
}
/* 6) 用來調用 hw_device_t.close() */
static inline int led_hal_close(led_device_t* device) {
    return device->common.close(&device->common);
}

__END_DECLS

#endif

2. 接口實現

 ① 實例化自己派生的專有硬件模塊結構led_module_t

led_module_t HAL_MODULE_INFO_SYM = {		                /* 1.實例名必須爲 HAL_MODULE_INFO_SYM */
    .common = {
        .tag = HARDWARE_MODULE_TAG,							/* 2.基礎結構標籤必爲 HARDWARE_MODULE_TAG */
        .module_api_version = HARDWARE_MODULE_API_VERSION(1,0),		/* 3.模塊版本爲1.0 */
        .hal_api_version = HARDWARE_HAL_API_VERSION,		/* 4.固定 */
        .id = LED_HARDWARE_MODULE_ID,						/* 5.綁定最後生成的動態庫名:[id].default.so */
        .name = "Hikey960 led HAl",
        .author = "[email protected]",
        .methods = &led_module_methods,						/* 6.指定激活模塊下設備的方法,下文中介紹 */
    },
};

 ② 實例化struct hw_module_methods_t,該結構下成員只有open

struct hw_module_methods_t led_module_methods = {
    .open = led_module_open,								/* 在下文中定義 */
};

 ③ 定義hw_module_methods_t.open(),它的任務是實例化struct hw_device_t的派生結構體led_device_t

int led_module_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device)
{
    led_device_t* led_dev = (led_device_t*)calloc(1, sizeof(led_device_t)); /* 1.申請內存實例化派生結構 */

    led_dev->common.tag = HARDWARE_DEVICE_TAG;				/* 2.HAL設備標籤,值固定 */
    led_dev->common.version = HARDWARE_DEVICE_API_VERSION(1, 0);
    led_dev->common.module = (struct hw_module_t*)module;
    led_dev->common.close = led_device_close;				/* 2.close函數與本函數任務相反,用於釋放相關資源 */
    led_dev->control = led_control;							/* 3.對接自定義的接口 */

    *device = (hw_device_t*) led_dev;	/* 4.通過參數返回實例化後設備指針,HAL調用者通過此指針調用設備的控制接口 */

    return 0;
};

 ④ 定義hw_device_t.close(),HAL調用者調用此函數用於關閉設備釋放hw_module_methods_t.open()中申請的資源

int led_device_close(struct hw_device_t* device)
{
    led_device_t* priv = (led_device_t*) device;
    if (priv)
        free(priv);
    return 0;
}

 ⑤ 實現暴露給HAL調用者的接口,本場景要實現int led_control(int enable);

3. 添加編譯規則

 ① 動態庫名應爲[id].default.so,所以LOCAL_MODULE:= led_hal.default
 ② HAL動態庫應部署到/system/lib64/hw//system/lib/hw/下,需加上規則LOCAL_MODULE_RELATIVE_PATH := hw
 ③ 導出暴露給調用者的接口頭文件。部署到system分區的HAL庫,使用LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include;部署到vendor分區的HAL庫,應使用LOCAL_HEADER_LIBRARIES := libhardware_headers

  • 若要HAL動態庫部署到/vendor/lib64/hw//vendor/lib/hw/,還需加上規則LOCAL_PROPRIETARY_MODULE := true
    HAL參考代碼:hardware/libhardware/modules/gralloc

4. 使用HAL的接口

 ① 加載硬件的HAL模塊,使用 int hw_get_module(const char *id, const struct hw_module_t **module)

  • id——HAL動態庫名字的前綴,本場景值爲led_hal
  • module——通過此參數獲得hw_module_t 實例
#include <hardware/hardware.h>
#include <led_hal.h>

static led_module_t* pModule = NULL;	/* 全局變量 */
int ret = hw_get_module(LED_HARDWARE_MODULE_ID, (const struct hw_module_t **)&pModule);
if (ret != 0) {
    ALOGE("get hardware module '%s' failed", LED_HARDWARE_MODULE_ID);
    return -1; 
}

 ② 通過hw_module_t 的派生結構體led_module_t,獲取hw_device_t的派生結構體led_device_t,方法如下所示。但爲了方便調用者,HAL接口頭文件一般會將下面的代碼實現到內聯函數中,如調用前文的led_hal_open((const struct hw_module_t*)pModule, &pDevice);

static led_device_t* pDevice = NULL;
pModule->common.methods->open((const struct hw_module_t *)pModule, NULL, (struct hw_device_t**)&pDevice);

 ③使用HAL接口進行硬件控制的形式:pDevice->foo(xxx);;如本場景使用:pDevice->control(1);來點亮LED;
 ④ 在需要關閉設備和釋放有關內存資源時,需調用hw_device_t.close(),所以調用方法如下所示。但爲了方便調用者,HAL接口頭文件一般會將下面的代碼實現到內聯函數中,如調用前文的led_hal_close(pDevice);

pDevice->common.close((struct hw_device_t*)pDevice);
  • 注:調用HAL動態庫的代碼,需要鏈接動態庫libhardware,故Android.mkLOCAL_SHARED_LIBRARIES := libhardware
    aosp中調用HAL的參考代碼:frameworks/native/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp

參考文獻

  [1] moasm. Android Log系統介紹 (基於Android N)[EB/OL].簡書,2019

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章