1. 程式人生 > >Android Native到HAL原始碼剖析,以sensor為例

Android Native到HAL原始碼剖析,以sensor為例

Google為了保護硬體廠商的資訊,在Android中添加了一層,它就是大名鼎鼎的HAL層。

在看HAL的編寫方法的過程中,會發現整個模組貌似沒有一個入口。一般說來模組都要有個入口,比如應用程式的main函式,可以被載入器進行載入執行,dll檔案有dllmain,而對於我們自己寫的動態連結庫,我們可以對庫中匯出的任何符號進行呼叫。

那麼問題來了,Android中的HAL是具有通用性的,需要上層的函式對其進行載入呼叫,Android的HAL載入器是如何實現對不同的Hardware Module進行通用性的呼叫的呢?我們今天將以Sensor hal模組的載入為例,剖析下hal層的具體呼叫邏輯。

SensorService啟動

在看Sensor架構的時候,SensorService服務啟動後,在隨後的第一次初始化時,其onFirstRef會被呼叫(均繼承自RefBase虛基類),緊接著,它會獲取我們的SensorDevice例項:

void SensorService::onFirstRef()
{
    ALOGD("nuSensorService starting...");
    SensorDevice& dev(SensorDevice::getInstance());

    if (dev.initCheck() == NO_ERROR) {
        sensor_t const* list;
        ssize_t count = dev.getSensorList(&list);

附上這部分的流程

SensorDevice作為Sensor架構中native的最後一個檔案,與Hal層進行通訊,故而在SensorDevice的構造方法中,我們就可以看到著名的hw_get_module和Sensor_Open方法了:

SensorDevice::SensorDevice()
    :  mSensorDevice(0),
       mSensorModule(0)
{
    status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,
            (hw_module_t const**)&mSensorModule);

    ALOGE_IF(err, "couldn't load %s module (%s)",
            SENSORS_HARDWARE_MODULE_ID, strerror(-err));

    if (mSensorModule) {
        err = sensors_open_1(&mSensorModule->common, &mSensorDevice);

        ALOGE_IF(err, "couldn't open device for module %s (%s)",
                SENSORS_HARDWARE_MODULE_ID, strerror(-err));

        if (mSensorDevice) {
            if (mSensorDevice->common.version == SENSORS_DEVICE_API_VERSION_1_1 ||
                mSensorDevice->common.version == SENSORS_DEVICE_API_VERSION_1_2) {
                ALOGE(">>>> WARNING <<< Upgrade sensor HAL to version 1_3");
            }

            sensor_t const* list;
            ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list);
            mActivationCount.setCapacity(count);
            Info model;
            for (size_t i=0 ; i<size_t(count) ; i++) {
                mActivationCount.add(list[i].handle, model);
                mSensorDevice->activate(
                        reinterpret_cast<struct sensors_poll_device_t *>(mSensorDevice),
                        list[i].handle, 0);
            }
        }
    }
}

其中SENSORS_HARDWARE_MODULE_ID是在hardware/sensors.h中定義的module名字:

/**
 * The id of this module
 */
#define SENSORS_HARDWARE_MODULE_ID "sensors"

而mSensorModule就是我們的sensors_module_t結構體,這些都是在hal層sensors.h中定義的:

struct sensors_module_t {
    struct hw_module_t common;

    /**
     * Enumerate all available sensors. The list is returned in "list".
     * @return number of sensors in the list
     */
    int (*get_sensors_list)(struct sensors_module_t* module,
            struct sensor_t const** list);

    /**
     *  Place the module in a specific mode. The following modes are defined
     *
     *  0 - Normal operation. Default state of the module.
     *  1 - Loopback mode. Data is injected for the the supported
     *      sensors by the sensor service in this mode.
     * @return 0 on success
     *         -EINVAL if requested mode is not supported
     *         -EPERM if operation is not allowed
     */
    int (*set_operation_mode)(unsigned int mode);
};

可以看到sensors_module_t結構體擴充套件了hw_module_t,他裡面額外提供了get_sensor_list方法來獲取系統支援的sensor列表以及一個模式設定方法。

接下來,我們跟進hw_get_module方法,看看它到底做了什麼?

hw_get_module

該函式具體實現在hardware/libhardware/hardware.c中

int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}
int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t **module)
{
    int i = 0;
    char prop[PATH_MAX] = {0};
    char path[PATH_MAX] = {0};
    char name[PATH_MAX] = {0};
    char prop_name[PATH_MAX] = {0};


    if (inst)
        snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
    else
        strlcpy(name, class_id, PATH_MAX);

    /*
     * Here we rely on the fact that calling dlopen multiple times on
     * the same .so will simply increment a refcount (and not load
     * a new copy of the library).
     * We also assume that dlopen() is thread-safe.
     */

    /* First try a property specific to the class and possibly instance */
    snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
    if (property_get(prop_name, prop, NULL) > 0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

    /* Loop through the configuration variants looking for a module */
    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

    /* Nothing found, try the default */
    if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
        goto found;
    }

    return -ENOENT;

found:
    /* load the module, if this fails, we're doomed, and we should not try
     * to load a different variant. */
    return load(class_id, path, module);
}

我們主要看hw_get_module_by_class,這裡傳入的引數分別是“sensors”,null,以及我們的mSensorModule結構體。

首先將字串拷貝給name:

strlcpy(name, class_id, PATH_MAX);

接著拼接prop_name為ro.hardware.name,即prop_name=ro.hardware.sensors

通過property_get方法並沒有得到這個值的定義(因為在系統中並沒有對其定義),所以接下來會進入下面的迴圈:

for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }
/**
 * There are a set of variant filename for modules. The form of the filename
 * is "<MODULE_ID>.variant.so" so for the led module the Dream variants 
 * of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
 *
 * led.trout.so
 * led.msm7k.so
 * led.ARMV6.so
 * led.default.so
 */

static const char *variant_keys[] = {
    "ro.hardware",  /* This goes first so that it can pick up a different
                       file on the emulator. */
    "ro.product.board",
    "ro.board.platform",
    "ro.arch"
};

根據上面的解析我門也可以看到,將會分別查詢sensors.variant.so,sensors.product.so,sensors.platform.so,以及sensors.default.so,最終我們會在/system/lib/hw/路徑下找到sensors.msm8952.so,然後將其通過load方法載入進記憶體中執行。由此也可知,我分析的是高通8952平臺。

小細節:當我們實現了自己的HAL層module,並且寫了一個應用程式測試module是否正常工作,那麼在編譯的時候,下面的引數應該要這樣寫:

LOCAL_MODULE := moduleName.default

或者

LOCAL_MODULE := moduleName.$(TARGET_BOARD_PLATFORM)

由於上面原始碼的原因,如果module名字對應不到,你的這個模組將不會被正常的load進去,因而也就無法正常工作了。

接著我們分析load的實現。

static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
{
    int status = -EINVAL;
    void *handle = NULL;
    struct hw_module_t *hmi = NULL;

    /*
     * load the symbols resolving undefined symbols before
     * dlopen returns. Since RTLD_GLOBAL is not or'd in with
     * RTLD_NOW the external symbols will not be global
     */
    handle = dlopen(path, RTLD_NOW);
    if (handle == NULL) {
        char const *err_str = dlerror();
        ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
        status = -EINVAL;
        goto done;
    }

    /* Get the address of the struct hal_module_info. */
    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    hmi = (struct hw_module_t *)dlsym(handle, sym);
    if (hmi == NULL) {
        ALOGE("load: couldn't find symbol %s", sym);
        status = -EINVAL;
        goto done;
    }

    /* Check that the id matches */
    if (strcmp(id, hmi->id) != 0) {
        ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
        status = -EINVAL;
        goto done;
    }

    hmi->dso = handle;

    /* success */
    status = 0;

    done:
    if (status != 0) {
        hmi = NULL;
        if (handle != NULL) {
            dlclose(handle);
            handle = NULL;
        }
    } else {
        ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
                id, path, *pHmi, handle);
    }

    *pHmi = hmi;

    return status;
}

1. 首先通過dlopen開啟sensors.xxx.so模組,獲得其控制代碼handle

2.呼叫dlsym去獲取結構體hw_module_t結構體的地址,注意這裡傳入的字串為HAL_MODULE_INFO_SYM_AS_STR,定義在hardware.h標頭檔案中

/**
 * Name of the hal_module_info
 */
#define HAL_MODULE_INFO_SYM         HMI

/**
 * Name of the hal_module_info as a string
 */
#define HAL_MODULE_INFO_SYM_AS_STR  "HMI"

這裡為什麼要去取名字為HMI的地址,我猜想它應該是HAL模組的入口了。

課外知識—ELF檔案格式:

ELF = Executable and Linkable Format,可執行連線格式,是UNIX系統實驗室(USL)作為應用程式二進位制介面(Application Binary Interface,ABI)而開發和釋出的,副檔名為elf。一個ELF頭在檔案的開始,儲存了路線圖(road map),描述了該檔案的組織情況。sections儲存著object 檔案的資訊,從連線角度看:包括指令,資料,符號表,重定位資訊等等。通過file命令我們可知sensors.xx.so是一個ELF檔案格式

[email protected]:~$ file sensors.msm8952.so
sensors.msm8952.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[md5/uuid]=0x25812b01ab4700281b41f61327075611, not stripped

因此,通過linux的readelf命令我們可以檢視該檔案的內部佈局及符號表等資訊。

[email protected]:~$ readelf -s sensors.msm8952.so

Symbol table '.dynsym' contains 157 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected] (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected] (2)
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected] (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected] (2)
        …………………………// 省略無關資訊
   108: 00006d5c    16 FUNC    WEAK   DEFAULT   13 __aeabi_ldiv0
   109: 000042d5    14 FUNC    WEAK   DEFAULT   13 _ZNSt3__13mapIi10FullHand
   110: 000053d7    12 FUNC    WEAK   DEFAULT   13 _ZTv0_n12_NSt3__114basic_
   111: 0000a0cd     0 NOTYPE  GLOBAL DEFAULT  ABS _end
   112: 000054b1    40 FUNC    GLOBAL DEFAULT   13 _ZN16SensorEventQueue7deq
   113: 0000a00c   136 OBJECT  GLOBAL DEFAULT   23 HMI
   114: 000053eb    52 FUNC    GLOBAL DEFAULT   13 _ZN16SensorEventQueueC1Ei
   115: 00006d5c    16 FUNC    WEAK   DEFAULT   13 __aeabi_idiv0
   116: 00003879    14 FUNC    WEAK   DEFAULT   13 _ZNSt3__115basic_streambu
   117: 00003c75    76 FUNC    WEAK   DEFAULT   13 _ZNSt3__113basic_filebufI
   118: 0000a098    12 OBJECT  GLOBAL DEFAULT   24 full_to_global

由符號表可知,HMI的地址為000a00c,拿到函式地址,當然就可以執行對應的程式碼了。

QualComm Sensor HAL

因此我們接著看sensor_hal層,高通的Sensor實現了自己的HAL,其原始碼在vendor/qcom/proprietary/sensors/dsps/libhalsensors路徑下,通過Android.mk我們也可以確定他確實是我們前面load方法開啟的動態連結庫,其編譯後會生成sensor.msm8952.so:

# vendor/qcom/proprietary/sensors/dsps/libhalsensors/Android.mk
ifeq ($(USE_SENSOR_MULTI_HAL),true)
LOCAL_MODULE := sensors.ssc
LOCAL_CLANG := false
else
LOCAL_MODULE := sensors.$(TARGET_BOARD_PLATFORM)
LOCAL_MODULE_RELATIVE_PATH := hw
endif

那麼HMI的入口到底定義在這裡的那個檔案中呢? 

功夫不負有心人,在sensors_hal.cpp中,我們終於找到了HMI的入口,即下面的結構體:

static struct hw_module_methods_t sensors_module_methods = {
    .open = sensors_open
};

struct sensors_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = (uint16_t)SENSORS_DEVICE_API_VERSION_1_3,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = SENSORS_HARDWARE_MODULE_ID,
        .name = "QTI Sensors Module",
        .author = "Qualcomm Technologies, Inc.",
        .methods = &sensors_module_methods,
        .dso = NULL,
        .reserved = {0},
    },
    .get_sensors_list = sensors_get_sensors_list,
    .set_operation_mode = sensors_set_operation_mode
};

HAL_MODULE_INFO_SYM即上文提到的HMI變數,恭喜各位,這裡我們就開啟了QualComm Sensor HAL的大門。

最後這個hw_module_t的結構體控制代碼會返回給我們的SensorDevice的建構函式裡:

SensorDevice::SensorDevice()
    :  mSensorDevice(0),
       mSensorModule(0)
{
    status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,
            (hw_module_t const**)&mSensorModule);

    ALOGE_IF(err, "couldn't load %s module (%s)",
            SENSORS_HARDWARE_MODULE_ID, strerror(-err));

    if (mSensorModule) {
        err = sensors_open_1(&mSensorModule->common, &mSensorDevice);

接著,通過sensors_open_1方法將module->common傳入,開啟我們的sensor驅動。

// hardware/libhardware/include/hardware/sensors.h
static inline int sensors_open_1(const struct hw_module_t* module,
        sensors_poll_device_1_t** device) {
    return module->methods->open(module,
            SENSORS_HARDWARE_POLL, (struct hw_device_t**)device);
}

static inline int sensors_close_1(sensors_poll_device_1_t* device) {
    return device->common.close(&device->common);
}

回過頭去看看HMI的結構體定義,其中module->common->open被賦值為sensors_module_methods,其只有一個open方法,因此,module->methods->open最終會呼叫sensors_open方法來開啟驅動程式。

到這裡native到hal層的邏輯其實已經基本上分析完了。

總結

通過hw_get_module去載入我們的HAL層實現庫,並且得到入口函式控制代碼,拿著這個控制代碼就可以繼而操作HAL層提供的方法了,通過上面的分析,相信大家已經對這部分已經有了一個更清晰的認識,其他hal層的業務邏輯大抵如此,當你看到hw_get_module,你就該知道去哪裡看接下來的邏輯了。

後續sensors_open的內容,我會在後面的章節連載剖析,歡迎關注。

相關推薦

Android Native到HAL原始碼剖析sensor

Google為了保護硬體廠商的資訊,在Android中添加了一層,它就是大名鼎鼎的HAL層。 在看HAL的編寫方法的過程中,會發現整個模組貌似沒有一個入口。一般說來模組都要有個入口,比如應用程式的main函式,可以被載入器進行載入執行,dll檔案有dllmain,而對於我們

【Flume】【原始碼分析】flume中攔截器的原始碼分析TimestampInterceptor

本文將以TimestampInterceptor為例來分析一下flume中攔截器的工作原理 首先來看下改攔截器的實現結構 1、實現了Interceptor介面 該介面的方法定義如下: public void initialize(); public Event in

手把手教你基於SqlSugar4編寫一個視覺化程式碼生成器(生成實體SqlServer文末附原始碼

  在開發過程中免不了建立實體類,欄位少的表可以手動編寫,但是欄位多還用手動建立的話不免有些浪費時間,假如一張表有100多個欄位,手寫有些不現實。 這時我們會藉助一些工具,如:動軟程式碼生成器、各種ORM框架自帶的程式碼生成器等等,都可以使用。 我們現在就基於SqlSugar(ORM框架)自己動手製造一個輪子

SVN+SSH協議工作方式全解析Sourceforge講解如何在Windows下配置TortoiseSVN和Su

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

利用HTML5上傳檔案並顯示在前端預覽圖片

由於專案中有上傳檔案的功能,所以這次單獨拿出來研究研究,我上網查了查,以前都是用iframe,但是自從HTML5出世之後,就可以利用H5的一些特性來上傳檔案了,啥也不說了,我上程式碼了 <!DOCTYPE html> <html lang

修改網站的圖示tomcat! (修改tomcat中的網站圖示)

在用瀏覽器開啟網站的時候,瀏覽器標籤頁上面有網站的圖示,類似於logo小圖示,如下圖: 原來這個是tomcat的一個小貓圖示,現在我們替換成我們自己的logo小圖示! 步驟1:開啟你的tomca

linux驅動由淺入深系列:塊裝置驅動之三(塊裝置驅動結構分析mmc

linux驅動由淺入深系列:塊裝置驅動之一(高通eMMC分割槽例項)前一篇文章介紹了塊裝置驅動在linux框架張的位置關係,本文來分析一下驅動本身。塊裝置驅動的模型還是基本基於字元裝置驅動的,可以簡單理解為塊裝置僅僅增加了操作緩衝區,對使用者操作請求進行佇列重排。因此只在有了

pytorch在fintune時將sequential中的層輸出vgg

pytorch將sequential中的層輸出,以vgg為例 有時候我們在fintune時發現pytorch把許多層都集合在一個sequential裡,但是我們希望能把中間層的結果引出來做下一步操作,於是我自己琢磨了一個方法,以vgg為例,有點僵硬哈! 首先

基於 Gogs Golang

實際的 DevOps 專案中,在 pipeline 流水線中包含下載程式碼、測試、構建、釋出、部署、通知等步驟。基本流程如下,當然不同的語言或不同的需求下流程會有所差異: clone -> test -> build -> publi

利用Hibernate進行簡單的增刪改查(未使用JPA註解Oracle

第一步、建立一個Java專案,名為Hibernate_one 第二步、載入jar包,在src下建立了lib資料夾,用來存放jar包(jar包必須一個都不能少)。如圖 我連線的資料庫是Oracle,所以得載入Oracle的驅動包ojdbc6,如果用mysql資料庫,可從網上

docker+lnmp 報錯小結laradock其它同理

用vagrant + centos7 + lnmp開發都快兩年,這是一個很好用的本地開發環境。對於我來說,它更像是一臺真正的linux電腦,能真正直接把握它的每一處地方。而且現在記憶體都普遍8G以上的本子,加上SSD真的是毫無壓力。 但時間一長,暴露出很

阿里雲伺服器部署FTP服務(CentOS 7.3 64位vsftpd

一、安裝vsftpd yum -y install vsftpd 二、配置 vsftpd的配置檔案在/etc/vsftpd,其中vsftp.conf檔案是主配置檔案,開啟如下: # Example config file /etc/vsftpd

Java環境搭建win10

一、下載地址     根據你的系統選擇你需要下載的jdk,32位系統對應x86,64位系統對應x64     下載完後得到一個可執行檔案,點選執行進入安裝   二、安裝     1

如何使用代理chrome

開啟谷歌瀏覽器,進入設定(或位址列輸入:chrome://settings/)下拉點選高階設定,會發現代理設定:如下,點選開啟選擇區域網設定:輸入地址和埠儲存。然後瀏覽器會彈窗提示輸入 代理賬號密碼,輸

Linux下寫定時任務tp5

tp5框架 首先在框架裡面寫個介面,然後在框架頂層建一個.sh檔案,跑這個指令碼,指令碼內容為 curl -g 'http://www.baidu.com/api/Index/index' 2.然後登陸linux伺服器進去etc下面

javascript的字串拼接和引號問題onclick

今天寫“刪除”超連結,試了兩次: $onclick = "javascript:return confirm(/”Are you sure to delete? /”);"; echo "<a h

idea,webstorm,pycharm,phpstorm等的啟用方式webstorm

這裡參考了網上的許多資料,不是我原創的,窮逼一個沒錢入正,僅拿來玩玩,各位有條件還是要支援正版。 2,將破解補丁複製到軟體的 bin目錄 下 3,搜尋 vmoptions 結尾的這兩個檔案 4,在第三步搜尋的 兩個檔案 裡面最後一行新增破解補丁的絕對路徑

tp5使用cookie加密演算法登入

首先,我們為什麼要對cookie加密? 之所以要對cookie加密是以為cookie是儲存在客戶端的,稍微懂一點技術的人都能找到cookie的儲存位置,如果我們儲存cookie的時候沒有加密,而是明文儲存的話也就是說我們的使用者名稱和密碼就完全暴露了,這是一個非常大的安全隱

剖析IBM Cloud Private服務Kubernetes排程核心_Kubernetes中文社群

IBM Cloud Private重點策略 主打PaaS應用管理需求,強調快速自建的私有容器雲 兼顧傳統IT需求,能同步管理容器及VM 上有AWS、GCP以及Azure,下有甲骨文競爭對手的IBM,在今年3月時宣佈,正式在Bluemix容器服務上支援Kubernetes。目前這家公司支援

原創:PHP利用session實現用戶登錄後回到點擊的頁面(本文TP

con gop query php代碼 自帶 ttr strpos 手機 roo 1、以下內容純屬原創,請謹慎選擇: ①目的:用戶登錄超時,session過期,點擊後跳轉到登錄頁,登錄成功再跳轉到鼠標點擊的頁面。 ②流程:用戶登錄---session過期---點擊跳