Android電池管理系統系統分析

Android電池管理系統總體實現:
電池管理在Android系統中的主要作用是檢測我們的電池狀態,剩餘電量實時更新,高溫報警,低電量關機等。Android的電池管理模塊,從Android的應用層到底層內核分爲了4層來理解,從上到下依次爲,應用層,framwork層,本地框架層,內核驅動層。
在這裏插入圖片描述

一、Android 電池服務

Android電池服務BatteryService,用來監聽內核上報的電池事件,並將最新的電池數據上報給系統,系統收到新數據後會去更新電池顯示狀態、剩餘電量等信息。如果收到過溫報警和低電報警,系統會自動觸發關機流程,保護電池和機器不受到危害。

Android電池服務的源碼結構 :

        frameworks/base/services/java/com/android/server/ 
        ├── SystemServer.java 
                 創建BatteryService、PowerManagerService、ActivityManagerService 
        frameworks/base/services/core/java/com/android/server/
        ├── BatteryService.java
                 監聽底層上報的battery事件,廣播電池發生改變的消息 (廣播Intent.ACTION_BATTERY_CHANGED)

        frameworks/base/services/core/java/com/android/server/am/
        ├── ActivityManagerService.java 
                 創建BatteryStatsService 
        ├── BatteryStatsService.java 
                 統計和記錄電池參數的信息 

        frameworks/base/services/core/java/com/android/server/power/
        ├── PowerManagerService.java 
                 監聽電池發生變化的廣播消息,並調節系統的電源狀態,例如亮屏 

        frameworks/base/core/java/com/android/internal/os/
        ├── BatteryStatsImpl.java 
                 統計和記錄電池參數的信息,並通知其他模塊 

二、Healthd

healthd是android4.4之後提出來的一種中介模型,安卓源碼路徑下system/core/healthd, 主要是通過binder機制去調用healthd向下監聽來自底層的電池事件,向上傳遞電池數據信息給Framework層的BatteryService用來計算電池電量相關信息,BatteryService通過傳遞來的數據來計算電池電量等信息,因此healthd在電池管理系統中起着承上啓下的作用。

主要是通過BatteryMonitor.cpp中的bool BatteryMonitor::update(void)函數上報信息,其中,內核首先會更新數據到/sys/class/power_supply/battery節點下各個屬性。

Healthd的源碼結構:

         System/core/healthd/
        ├── healthd.cpp 
                 創建uevent socket,監聽內核上報的內核事件 
        ├── BatteryMonitor.cpp 
                 初始化本地電池數據結構,將power_supply路徑下屬性節點路徑填充進去, 
        ├── BatteryMonitor.h 
        ├── BatteryPropertiesRegistrar.cpp 
                 創建電池屬性監聽器,並將其註冊到Android的系統服務中 
        ├── BatteryPropertiesRegistrar.h

health模塊的代碼位置位於/system/core/healthd/,其入口在Healthd.cpp中的main函數中:

    healthd_mode_ops = &android_ops;  //開機充電時初始化結構體
    healthd_mode_ops = &charger_ops;  //關機充電時初始化結構體

healthd_mode_ops是一個充電狀態的結構體,正常開機情況下會 將android_ops結構體賦值給healthd_mode_ops ,如果在關機情況下會將 charget_ops結構體賦值給healthd_mode_opos,就是關機充電的使用。

然後在healthd_init中,主要做一些初始化工作,並且創建了一個epoll,主要用於將文件指針掛在工作隊列中,用於輪詢查看驅動層是否發來信息,然後調用uevent_init()對uevent進行註冊,應爲從上篇文章我們明白,電池驅動是通過uevent與health進行通信的,uevent通信本質上就是socket通信。

    epollfd = epoll_create(MAX_EPOLL_EVENTS);

初始化之後,執行healthd_mainloop(),開啓了一個無線循環,監聽是否有uevent事件到來,如果在for循環中檢測到uevent事件到來的時候,會直接調用 uevent_event函數

在uevent_event函數中,會判斷SSUBSYSTEM是否等於POWER_SUPPLY_SUBSYSTEM,如果是的話,說明是電池驅動傳過來的信息,會執行healthd_battery_update()函數。在該函數中會直接調用BatteryMonitor中的update()函數,對電池的各個屬性進行更新,並在最後調用在前面註冊的監聽進行上報。

在update()函數中調用 healthd_mode_ops->battery_update(&props) 進行,其中battery_update(&props)爲在health.cpp中初始化的結構體,最後返回當前的充電狀態。

battery_update()調用的函數如下,調用註冊的監聽進行上報:

    void healthd_mode_android_battery_update(
    struct android::BatteryProperties *props) {
    if (gBatteryPropertiesRegistrar != NULL)
        gBatteryPropertiesRegistrar->notifyListeners(*props); //調用監聽並傳入屬性值
    return;
    }

接着gBatteryPropertiesRegistrar 這個監聽就會回調在framwork層實現的函數,然後通過Binder機制向上層通信

ps:這篇文章介紹Healthd說的比較詳細:https://blog.csdn.net/zhou12314/article/details/79404348

三、驅動

驅動部分大概流程是這樣的:
Android內核中的電池驅動採取的是linux 內核驅動中的 power_supply子系統框架進行上報電池狀態。power_supply主要通過sys文件系統向用戶層提供讀取電池狀態的接口,路徑爲 /sys/class/power_supply/ , 該目錄下通常會有 ac , battery, usb 三個目錄,代表給Android系統供電的三種能源類型,其中電池的狀態就在battery的目錄下,當電池狀態變化的時候會通過uevent機制通知上層,然後上層通過讀取該目錄下相應的值來動態的顯示電池狀態。

驅動的源碼結構 :

kernel/drivers/power

1、對電源(ac , battery, usb)進行初始化(這裏說的是rk818):

對應驅動文件:drivers/power/rk818_charger.c

     469 static const struct power_supply_desc rk818_ac_desc = {
     470         .name           = "ac",//設備名稱
     471         .type           = POWER_SUPPLY_TYPE_MAINS,//類型
     472         .properties     = rk818_ac_props,//屬性
     473         .num_properties = ARRAY_SIZE(rk818_ac_props),//屬性數目
     474         .get_property   = rk818_cg_ac_get_property,//得到屬性的函數
     475 };
     476
     477 static const struct power_supply_desc rk818_usb_desc = {
     478         .name           = "usb",
     479         .type           = POWER_SUPPLY_TYPE_USB,
     480         .properties     = rk818_usb_props,
     481         .num_properties = ARRAY_SIZE(rk818_usb_props),
     482         .get_property   = rk818_cg_usb_get_property,
     483 };
    

對應驅動文件:drivers/power/rk818_battery.c

    1017 static const struct power_supply_desc rk818_bat_desc = {
    1018         .name           = "battery",
    1019         .type           = POWER_SUPPLY_TYPE_BATTERY,
    1020         .properties     = rk818_bat_props,
    1021         .num_properties = ARRAY_SIZE(rk818_bat_props),
    1022         .get_property   = rk818_battery_get_property,
    1023 };

這裏主要是實現給電源名字類型等賦初值,最主要是將get_property函數指向我們寫好的可以得到電源的屬性的函數的起始地址,以便當內核需要用到驅動的信息的時候進行回調。

2、通過power_supply_register(devm_power_supply_register最終也是調用power_supply_register)將所提供的電源進行註冊,即把他們的屬性寫到sys文件系統裏,使用戶空間可以得到有關電源的信息。

    489   cg->usb_psy = devm_power_supply_register(cg->dev, &rk818_usb_desc,
    490        															&psy_cfg);

power_supply_register調用內核提供的函數device_create()和power_supply_create_attrs來實現電源的註冊,這裏電源類型是usb。

3、Power Supply驅動程序頭文件是kernel/include/linux/power_supply.h,註冊和註銷驅動程序的函數如下:

int power_supply_register(struct device *parent,struct power_supply *psy);

	void power_supply_unregister(struct power_supply *psy);

	struct power_supply {

	const char *name; /*設備名稱*/

	enum power_supply_type type; /* 類型 */

	enum power_supply_property *properties; /* 屬性指針 */

	size_t num_properties; /*屬性的數目*/

	char* *supplied_to;

	size_t num_supplicants;

	int (*get_property)(struct power_supply *psy, /*獲得屬性*/

	enum power_supply_property psp,

	union power_supply_propval *val);

	void (*external_power_changed)(struct power_supply *psy);

	/* ...... 省略部分內容 */

內核主要通過get_property這個函數指針來獲得驅動中的有關電池的信息,而這個函數在內核中只給出了聲明,我們在寫驅動的時候要自己實現這個函數,即將自己寫的函數賦值給這個函數指針,當內核需要驅動中電源信息的時候就回調這個get_property函數。另外,我們寫驅動程序的時候又要給用戶提供接口,內核中提供給用戶的接口就是sysfs,通過讀取sysfs文件系統中文件內容,就可以得到電源的信息。內核主要通過兩個文件power_supply_class.c 和power_supply_core.c,我們調用其中的函數就可以把電源(BATTERY,USB或AC)的信息展現給用戶,有關電源的屬性寫在/sys/class/powersupply文件夾下(此文件夾爲程序運行後所生成的)。

ac和usb只創建了一個online屬性,上層通過判斷ac和usb的online狀態(1表示設備接入,0表示設備拔出)便可知道當前系統是由什麼設備在充電了;而battery則創建瞭如status、health、present、capacity、batt_vol等等和電池相關的諸多屬性,上層通過這些電池屬性uevent便可監控電池的當前工作狀態了。下面舉例是battery,ac和usb同理。

  	955 	static int rk818_battery_get_property(struct power_supply *psy,
    956                                       enum power_supply_property psp,
    957                                       union power_supply_propval *val)
    958 	{
    959         struct rk818_battery *di = power_supply_get_drvdata(psy);
    960
    961         switch (psp) {
    962         case POWER_SUPPLY_PROP_CURRENT_NOW:
    963                 val->intval = di->current_avg * 1000;/*uA*/ //獲取電池電流
    964                 if (di->pdata->bat_mode == MODE_VIRTUAL)
    965                         val->intval = VIRTUAL_CURRENT * 1000;
    966                 break;
    967         case POWER_SUPPLY_PROP_VOLTAGE_NOW:
    968                 val->intval = di->voltage_avg * 1000;/*uV*/ //獲取電池電壓
    969                 if (di->pdata->bat_mode == MODE_VIRTUAL)
    970                         val->intval = VIRTUAL_VOLTAGE * 1000;
    971                 break;
    972         case POWER_SUPPLY_PROP_PRESENT:
    973                 val->intval = is_rk818_bat_exist(di);
    974                 if (di->pdata->bat_mode == MODE_VIRTUAL)
    975                         val->intval = VIRTUAL_PRESET;
    976                 break;
    977         case POWER_SUPPLY_PROP_CAPACITY:
    978                 val->intval = di->dsoc; //獲取電池電量
    979                 if (di->pdata->bat_mode == MODE_VIRTUAL)
    980                         val->intval = VIRTUAL_SOC;
    981                 DBG("<%s>. report dsoc: %d\n", __func__, val->intval);
    982                 break;
    983         case POWER_SUPPLY_PROP_HEALTH:
    984                 val->intval = POWER_SUPPLY_HEALTH_GOOD;
    985                 break;
    986         case POWER_SUPPLY_PROP_TEMP:
    987                 val->intval = di->temperature;
    988                 if (di->pdata->bat_mode == MODE_VIRTUAL)
    989                         val->intval = VIRTUAL_TEMPERATURE;
    990                 break;

各能源設備屬性概況如下(adb工具cat可以查看):

	/sys/class/power_supply/ac/online AC 電源連接狀態

	/sys/class/power_supply/usb/online USB電源連接狀態

	/sys/class/power_supply/battery/status 充電狀態

	/sys/class/power_supply/battery/health 電池狀態

	/sys/class/power_supply/battery/present 使用狀態

	/sys/class/power_supply/battery/capacity 電池 level

	/sys/class/power_supply/battery/batt_vol 電池電壓

	/sys/class/power_supply/battery/batt_temp 電池溫度

	/sys/class/power_supply/battery/technology 電池技術

當供電設備的狀態或者電量發生變化後,會調用power_supply_changed(&battery_data->battery)更新這些文件;

	//Send uevent.
	power_supply_changed(&battery_data->battery);

備註:
1、Uevent機制
Uevent是內核通知android有狀態變化的一種方法,比如USB線插入、拔出,電池電量變化等等。其本質是內核發送(可以通過socket)一個字符串,應用層(android)接收並解釋該字符串,獲取相應信息。

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