Android6.0 Sensor架構和問題分析

本文在借鑑網友的資料後再重新梳理了一遍,都是站在前人的基礎、巨人的肩膀上再次總結分析出來的,僅供大家參考!

本文主要描述了在Android 6.0系統、MTK6755平臺上sensor相關軟硬件的體系架構的分析理解。
一、系統架構(Architecture)
1.1 Android體系架構圖
 
圖1.1
圖1.1是Android系統的典型五層體系架構圖,分別爲Applications、Framework、Native、HAL和Kernel五大層次。我們接下來分析的sensor子系統也基本按照這五個層次分別進行分析。
1.2  Sensor子系統架構圖
 
圖1.2
1、黃色部分表示硬件,它要掛在I2C總線上(目前大部分sensor基本都通過I2C進行通信和控制)。
2、紅色部分表示驅動,驅動中會申請Input設備,註冊到Kernel的Input Subsystem上,然後通過Event Device把Sensor數據傳到HAL層,準確說是HAL從Event讀。
    在MTK6755平臺中,這裏的驅動driver實際上分成了兩層,一層爲虛擬邏輯Hub層,用來管理兼容不同類型的所有的sensor,如有可能有兩家廠商的不同g-sensor,則可以通過這個虛擬邏輯出來的Hub進行統一管理;另一層爲每款具體sensor的真正驅動,通過註冊掛載到Hub中。如圖1.3:
 
圖1.3
從圖1.3可以看出,MTK平臺對虛擬邏輯Hub層使用了兩種實現方式,一種是以前平臺一直使用的hwmsen虛擬邏輯Hub,由它統一管理所有類型所有廠商的sensor,目前我司沒有使用這種方式,因此在後面就不對它進行分析了;另一種是分別爲每一種類型的sensor實現了一個虛擬邏輯Hub,如accel負責管理所有G-sensor。

3、圖1.2綠色部分表示動態庫,它封裝了整個Sensor的IPC機制,如SensorManager是客戶端,SensorService是服務端,而HAL部分是封裝了服務端對Kernel的直接訪問。在MTK平臺的HAL層裏,也同樣爲對接下層的兩種虛擬邏輯Hub實現了不同的HAL層代碼。

4、藍色部分就是我們的Framework和Application了,JNI負責訪問Sensor的客戶端,而Application就是具體的應用程序,用來接收Sensor返回的數據,並處理實現對應的UI效果,如屏幕旋轉,打電話時滅屏,自動調節背光等。
二、應用(Applications)
2.1 傳感器介紹
    Android 6.0 系統sensors.h文件中定義了26種傳感器類型,其中主要的8種傳感器類型分別是:加速度傳感器(accelerometer)、磁力傳感器(magnetic field)、方向傳感器(orientation)、陀螺儀(gyroscope)、環境光照傳感器(light)、壓力傳感器(pressure)、溫度傳感器(temperature)和距離傳感器(proximity)。其他的幾種傳感器基本都可以由上述的傳感器進行算法實現,如動作探測傳感器(significant motion)、步行探測傳感器(step detector)、計步傳感器(step counter)、心率計傳感器(heart rate)等。
2.2 應用開發
    Android定義的API接口給應用層提供了幾種方法,可以很容易讓你在運行時確定設備上有哪些sensor。API還提供了讓你確定每個sensor信息和功能的接口,比如sensor名字、類型、廠商、數據值的範圍、數據精度等。
    Android開發者官網上給出的典型的應用開發代碼如下:
 
圖2.1
在構造函數中,獲取SystemSensorManager,並獲取到加速傳感器的Sensor;在onResume方法中調用SystemSensorManager.registerListener註冊監聽器;
Activity同時實現了SensorEventListener接口。當Sensor數據有改變的時候將會調用onSensorChanged方法。
 
圖2.2
三、框架(Framework)
3.1 Sensor總體調用關係圖
 
圖3.1
sensor框架分爲三個層次,客戶端、服務端、HAL層,服務端負責從HAL讀取數據,並將數據寫到管道中,客戶端通過管道讀取服務端數據。
客戶端主要類
SensorManager.java
    從android4.1開始,把SensorManager定義爲一個抽象類,定義了一些主要的方法,該類主要是應用層直接使用的類,提供給應用層的接口
SystemSensorManager.java
    繼承於SensorManager,客戶端消息處理的實體,應用程序通過獲取其實例,並註冊監聽接口,獲取sensor數據。
SensorEventListener接口
    用於註冊監聽的接口
Looper
    用申請註冊的線程Looper或系統初始化線程的Looper,來負責讀取sensor數據
android_hardware_SensorManager.cpp
    負責與java層通信的JNI接口
SensorManager.cpp
    sensor在Native層的客戶端,負責與服務端SensorService.cpp的通信
SenorEventQueue.cpp
    消息隊列
服務端主要類
SensorService.cpp
    服務端數據處理中心
SensorEventConnection
    從BnSensorEventConnection繼承來,實現接口ISensorEventConnection的一些方法,ISensorEventConnection在SensorEventQueue會保存一個指針,指向調用服務接口創建的SensorEventConnection對象
BitTube.cpp
    在這個類中創建了管道,用於服務端與客戶端讀寫數據
SensorDevice
    負責與HAL讀取數據
HAL層
  sensor.h是google爲Sensor定義的Hal接口,單獨提出去

3.2客戶端實現
調用時序圖
 
圖3.2
初始化SystemSensorManager
 
圖3.3
系統開機啓動的時候,會創建SystemSensorManager的實例,在其構造函數中,主要做了三件事情:
1、初始化Sensor列表:調用JNI函數nativeCreate,對sensor模塊進行初始化。創建了native層SensorManager的實例。
2、初始化JNI:調用JNI函數nativeClassInit()進行初始化
3、獲取Sensor列表:調用JNI函數nativeGetSensorAtIndex獲取sensor,並存在mFullSensorsList和mHandleToSensor列表中
實現註冊監聽器
 
圖3.4
當有應用程序調用registerListenerImpl()方法註冊監聽的時候,會用註冊線程的Looper或系統初始化線程的Looper來循環等待SensorEvent事件並讀取來自服務端的數據。
    接着實例化內部類SensorEventQueue,通過調用JNI函數nativeBaseEventQueue()來創建消息隊列,JNI最終調用Native SensorManager來完成真正的創建消息隊列。
    然後通過實例化的SensorEventQueue添加需監聽的sensor並對其使能和設置採樣事件等。
3.3 服務端實現
調用時序圖
 
圖3.5
啓動SensorService服務
    在SystemServer進程中調用native 方法startSensorService()函數,通過JNI調用,在JNI函數中調用SensorService::instantiate()實例化SensorService對象。
SensorService初始化
SensorService創建完之後,將會調用SensorService::onFirstRef()方法,在該方法中完成初始化工作。
首先獲取SensorDevice實例,在其構造函數中,完成了對Sensor模塊HAL的初始化:
 
圖3.6
這裏主要做了三個工作:
1、調用HAL層的hw_get_modele()方法,加載Sensor模塊so文件
2、調用sensor.h的sensors_open_1()方法打開設備
3、調用sensors_poll_device_1_t->activate()對Sensor模塊使能
再看看SensorService::onFirstRef()方法:
 
圖3.7
在這個方法的前面部分中,主要做了3件事情:
1、創建SensorDevice實例
2、調用SensorDevice.getSensorList(),獲取Sensor模塊所有傳感器列表
3、爲每個傳感器註冊監聽器,Android還同時註冊實現了6個虛擬傳感器
 
圖3.8
注意!:如果O-sensor需要使用廠商自己的算法,需如上圖添加宏控制過濾,不然會默認使用Android自帶的算法計算O-sensor的值,這樣有可能會導致數據不準確。
在新的線程中讀取HAL層數據
SensorService實現了Thread類,當在onFirstRef中最後調用run方法,將在新的線程中調用SensorService::threadLoop()方法。在該方法while循環中一直poll讀取HAL層數據,再調用SensorEventConnection->sendEvents將數據寫到管道中。

3.4 客戶端與服務端通信
 
圖3.9
客戶端服務端線程
    在圖中可以看到有兩個線程:
        1、一個是服務端的一個線程,這個線程負責源源不斷的從HAL讀取數據。
        2、另一個是客戶端的一個線程,客戶端線程負責從消息隊列中讀數據。
創建消息隊列
    客戶端可以創建多個消息隊列,一個消息隊列對應有一個與服務器通信的連接接口
創建連接接口
    服務端與客戶端溝通的橋樑,服務端讀取到HAL層數據後,會掃面有多少個與客戶端連接的接口,然後往每個接口的管道中寫數據
創建管道
    每一個連接接口都有對應的一個管道。
那麼數據的形式是怎麼從HAL層傳到JAVA層的呢?
    其實數據是以一個結構體sensors_event_t的形式從HAL層傳到JNI層。看HAL的結構體:
 
圖3.10
在JNI層有一個ASensorEvent結構體與sensors_event_t向對應,
 
圖3.11
    經過前面的介紹,現在知道了客戶端實現的方式及服務端的實現,但是沒有具體講到它們是如何進行通信的,現在看看客戶端與服務端之間的通信。
  主要涉及的是進程間通信,有IBind和管道通信。
  客戶端通過IBind通信獲取到服務端的遠程調用,然後通過管道進行sensor數據的傳輸。
服務端
  native層實現了sensor服務的核心實現,Sensor服務的主要流程的實現在sensorservice類中,下面重點分析下這個類的流程。
 
圖3.12
看看sensorService繼承的類:繼承BinderService<SensorService>這個模板類添加到系統服務,用於Ibinder進程間通信。
 
圖3.13
SensorService服務的實例是在JNI函數中調用SensorService::instantiate()創建的,即調用了上面的instantiate()方法,接着調用了publish().在該方法中,我們看到了new SensorService的實例,並且調用了defaultServiceManager::addService()將Sensor服務添加到了系統服務管理中,客戶端可以通過defaultServiceManager:getService()獲取到Sensor服務的實例。
    繼承BnSensorServer這個是sensor服務抽象接口類提供給客戶端調用:
 
圖3.14
ISensorServer接口提供了兩個抽象方法給客戶端調用,關鍵在於createSensorEventConnection()方法,該在服務端被實現,在客戶端被調用,並返回一個SensorEventConnection的實例,創建連接,客戶端拿到SensorEventConnection實例之後,可以對sensor進行通信操作,僅僅作爲通信的接口而已,它並沒有用來傳送Sensor數據,因爲Sensor數據量比較大,IBind實現比較困難。真正實現Sensor數據傳送的是管道,在創建SensorEventConnection實例中,創建了BitTube對象,裏面創建了管道,用於客戶端與服務端的通信。
客戶端
    客戶端主要在SensorManager.cpp中創建消息隊列
 
圖3.15
SensorEventQueue類作爲消息隊列,作用非常重要,在創建其實例的時候,傳入了SensorEventConnection的實例,SensorEventConnection繼承於ISensorEventConnection。
  SensorEventConnection其實是客戶端調用SensorService的createSensorEventConnection()方法創建的,它是客戶端與服務端溝通的橋樑,通過這個橋樑,可以完成一下任務:
1.    獲取管道的句柄
2.    往管道讀寫數據
3.    通知服務端對Sensor使能
3.5 流程解析
交互調用時序圖
 
圖3.16
客戶端獲取SensorService服務實例
  客戶端初始化的時候,即SystemSensorManager的構造函數中,通過JNI調用,創建native層SensorManager的實例,然後調用SensorManager::assertStateLocked()方法做一些初始化的動作。
 
圖3.17
前面我們講到過,SensorService的創建的時候調用了defaultServiceManager:getService()將服務添加到了系統服務管理中。
  現在我們又調用defaultServiceManager::geService()獲取到SensorService服務的實例。
  在通過IBind通信,就可以獲取到Sensor列表,所以在客戶端初始化的時候,做了兩件事情:
    1. 獲取SensorService實例引用
    2. 獲取Sensor傳感器列表
創建消息隊列
    當客戶端第一次註冊監聽器的時候,就需要創建一個消息隊列,也就是說,android在目前的實現中,一個監聽器對應一個消息隊列,一個消息隊列中有一個管道,用於服務端與客戶端傳送Sensor數據。
  在SensorManager.cpp中的createEventQueue方法創建消息隊列:
 
圖3.18
客戶端與服務端創建一個SensorEventConnection連接接口,而一個消息隊列中包含一個連接接口。
創建連接接口:
 
 
圖3.19
關鍵在於BitTube,在構造函數中創建了管道:
 
 
圖3.20
其中:sockets[0]就是對應的mReceiveFd,是管道的讀端,sensor數據的讀取端,對應的是客戶端進程訪問的。
  sockets[1]就是對應mSendFd,是管道的寫端,sensor數據寫入端,是sensor的服務進程訪問的一端。
  通過socketpair()創建管道,通過fcntl來設置操作管道的方式,設置通道兩端的操作方式爲O_NONBLOCK,非阻塞IO方式,read或write調用返回-1和EAGAIN錯誤。
總結下消息隊列
  客戶端第一次註冊監聽器的時候,就需要創建一個消息隊列,客戶端通過Looper從消息隊列裏面讀取數據。
  SensorEventQueue中有一個SensorEventConnection實例的引用,SensorEventConnection中有一個BitTube實例的引用。
使能Sensor
  客戶端創建了連接接口SensorEventConnection後,可以調用其方法使能Sensor傳感器:
 
圖3.21
服務端往管道寫數據
前面介紹過,在SensorService中,創建了一個線程不斷從HAL層讀取Sensor數據,就是在threadLoop方法中。關鍵在於下面的一個for循環,其實是掃描有多少個客戶端連接接口,然後就往每個連接的管道中寫數據。
 
 
圖3.22
調用了SensorEventQueue消息隊列的write()方法
 
圖3.23
調用該連接接口的BitTube::sendObjects()和BitTube::write():
 
 
圖3.24
到此,服務端就完成了往管道的寫端寫入數據。
客戶端讀管道數據
服務端寫完數據後,客戶端的Looper收到有數據可讀事件後,調用JNI中的回調函數從消息隊列讀取數據。
 
圖3.25
然後調用到了BitTube::read():
 
 
圖3.26
讀取到sensor數據後,通過JNI調回JAVA層的SystemSensorManager類dispatchSensorEvent()方法:
 
 
圖3.27
到此,客戶端就完成了從管道的讀端讀入數據,並把數據發給對應監聽器的應用。

四、硬件抽象層(HAL)
4.1 基本框架圖
 
圖4.1
    MTK平臺的HAL層和Kernel層,跟高通平臺的很不一樣。之前說過,MTK平臺使用兩套機制來實現與sensor的管理和通信。一種是通過hwmsen來管理控制所有sensor,並通過ioctl接口來操作底層sensor和獲取數據;另一種是通過爲每一種類型的sensor分配一個對象來管理各自類型的sensor,並通過/sys節點來操作底層sensor,通過Input子系統和Event設備來獲取sensor數據。我司使用的是第二種方式,因此暫不對第一種方式進行分析。

4.2 基本定義
    在linux操作系統中,應用同硬件之間的交互都是通過設備驅動來實現,Android系統爲了降低應用開發人員開發難度,屏蔽硬件差異,定義出硬件抽象層,爲開發人員提供獲取各種設備相關的信息的接口。
    Google爲Sensor提供了統一的HAL接口,不同的硬件廠商需要根據該接口來實現並完成具體的硬件抽象層。Android中Sensor的HAL接口定義在:hardware/libhardware/include/hardware/sensors.h
Android6.0定義了26中傳感器類型:
 
圖4.2
傳感器模塊的定義結構體如下:
 
圖4.3
對任意一個sensor設備都會有一個sensor_t結構體,其定義如下:
 
圖4.4
每個傳感器的數據由sensors_event_t結構體表示,定義如下:
 
圖4.5
其中,sensor爲傳感器的標誌符,而不同的傳感器則採用union方式來表示。
sensors_vec_t結構體用來表示不同傳感器的數據:
 
圖4.6
Sensor設備結構體sensors_poll_device_1_t,對標準硬件設備hw_device_t結構體的擴展,主要完成讀取底層數據,並將數據存儲在struct sensors_poll_device_t結構體中;
poll函數用來獲取底層數據,調用時將被阻塞。定義如下:
 
圖4.7
其中batch()、flush()、inject_sensor_data()等方法,一般在外掛sensorhub或帶有MCU、memory的器件上時才使用,因我司沒有使用這些器件,因此本文討論的sensor架構都不涉及該相關實現。
控制設備打開/關閉結構體定義如下:
 
圖4.8
4.2 Sensor HAL實現
Native和HAL交互調用時序圖
 
圖4.9
SensorDevice屬於JNI層,是與HAL進行通信的接口;在JNI層調用了HAL層的open_sensors()方法打開設備模塊,再調用poll__activate()對設備使能,然後調用poll__poll讀取數據。
創建SensorDevice
在SensorDevice的構造函數中,首先加載sensor設備模塊:
 
圖4.10
這句代碼的意思是JNI加載HAL的庫文件,並創建SensorModle的對象,Sensor的庫文件通常是sensor.default.so
上圖接下來是sensors_open_1(),這個函數並沒有在SensorDevice中實現,而是調用的HAL層的函數。
 
 
圖4.11
我們先看new sensors_poll_device_t();
 
圖4.12
這部分代碼就創建HAL和Kernel Event通信的類,還有Sensor數據讀寫管道的創建。
返回open_sensors再看剩下的代碼,就是創建sensors_poll_device_t對象並把sensor控制的相關函數指針賦值給它。
SensorDevice 調用get_sensors_list
這個方法還是調用到了HAL中,而HAL中的這個函數也就是返回以下數組:
 
圖4.13
我們需要特別關組的是第4,5個參數,第4參數handle是對kernel而言的,如激活,讀寫event;而第五個參數是相對於上層代碼而言。
SensorDevice的activate方法
在該方法中,會先調用HAL的activate方法,在調用HAL的setDelay方法。
 
圖4.14
傳進來的就是我們上面說的第4個參數handle,handleToDriver()函數返回的是對應的和kernel交互的類的數組下標(Sensors[acc])。從上圖可以看出MTK平臺就是這樣兼容操作底層sensor的兩種實現方式的。
最終通過讀寫/sys節點來操作底層的sensor設備。
 
圖4.15
SensorDevice的poll方法
這個方法同樣調用到了HAL中poll__poll()函數,該函數再調用到pollEvents()函數來輪詢獲取所有sensor數據。
 
圖4.16
可以看到,最終會通過對應sensor和kernel交互的類來獲取底層的數據,並把數據以sensors_event_t的格式存儲在上層傳遞下來的內存區裏,然後返回數量值。
 
圖4.17
HAL和kernel通過Input子系統和Event設備來完成數據的傳送。

至此,HAL層的部分就分析到這裏。Kernel的部分就不再進行詳細分析,具體可以閱讀MTK文檔sensor_all_in_one.pdf來了解MTK平臺底層的sensor模塊架構和實現。


發佈了31 篇原創文章 · 獲贊 10 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章