概述 Healthd是android4.4之後提出來的一種中介模型,該模型向下監聽來自底層的電池事件,向上傳遞電池數據信息給Framework層的BatteryService用以計算電池電量相關狀態信息,BatteryServcie通過傳遞來的數據來計算電池電量顯示,剩餘電量,電量級別等信息,如果收 ...
概述
Healthd是android4.4之後提出來的一種中介模型,該模型向下監聽來自底層的電池事件,向上傳遞電池數據信息給Framework層的BatteryService用以計算電池電量相關狀態信息,BatteryServcie通過傳遞來的數據來計算電池電量顯示,剩餘電量,電量級別等信息,如果收到過溫報警或者嚴重低電報警等信息,系統會直接關機,保護硬體。
主模塊處理流程
Healthd模塊代碼是在system/core/healthd/,其模塊入口在healthd的main函數,函數代碼如下:
int main(int argc, char **argv) {
int ch;
int ret;
klog_set_level(KLOG_LEVEL);
healthd_mode_ops = &android_ops;
if (!strcmp(basename(argv[0]), "charger")) {
healthd_mode_ops = &charger_ops;
} else {
while ((ch = getopt(argc, argv, "cr")) != -1) {
switch (ch) {
case 'c':
healthd_mode_ops = &charger_ops;
break;
case 'r':
healthd_mode_ops = &recovery_ops;
break;
case '?':
default:
KLOG_ERROR(LOG_TAG, "Unrecognized healthd option: %c\n",
optopt);
exit(1);
}
}
}
ret = healthd_init();
if (ret) {
KLOG_ERROR("Initialization failed, exiting\n");
exit(2);
}
healthd_mainloop();
KLOG_ERROR("Main loop terminated, exiting\n");
return 3;
}
可以看出Main函數並不長,但是其作用確實巨大的,main函數起著一個統籌兼顧的作用,其他各個模塊函數去做一些具體相應的工作,最後彙總到main函數中被調用。
代碼中開始便是解析參數,healthd_mode_ops是一個關於充電狀態結構體變數,結構體變數里的參數是函數指針,在初始化時指向各個不同的操作函數,當開機充電時變數賦值為&android_ops,關機充電時候變數賦值為&charger_ops。
在ret = healthd_init();中進行一些初始化工作。
static int healthd_init() {
epollfd = epoll_create(MAX_EPOLL_EVENTS);
if (epollfd == -1) {
KLOG_ERROR(LOG_TAG,
"epoll_create failed; errno=%d\n",
errno);
return -1;
}
healthd_board_init(&healthd_config);
healthd_mode_ops->init(&healthd_config);
wakealarm_init();
uevent_init();
gBatteryMonitor = new BatteryMonitor();
gBatteryMonitor->init(&healthd_config);
return 0;
}
創建一個epoll的變數將其賦值給epollfd,在healthd_board_init中未作任何事便返回了。
healthd_mode_ops->init調用有兩種情況:關機情況下調用charger_ops的init函數;開機情況下調用android_ops的init函數,這裡就開機情況來分析。android_ops的init函數指針指向healthd_mode_android_init函數
代碼如下:
void healthd_mode_android_init(struct healthd_config* /*config*/) {
ProcessState::self()->setThreadPoolMaxThreadCount(0);//線程池裡最大線程數
IPCThreadState::self()->disableBackgroundScheduling(true);//禁用後臺調度
IPCThreadState::self()->setupPolling(&gBinderFd);//
if (gBinderFd >= 0) {
if (healthd_register_event(gBinderFd, binder_event))
KLOG_ERROR(LOG_TAG,
"Register for binder events failed\n");
}
gBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
gBatteryPropertiesRegistrar->publish();
}
再來看看wakealarm_init函數:
static void wakealarm_init(void) {
wakealarm_fd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK);
if (wakealarm_fd == -1) {
KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failed\n");
return;
}
if (healthd_register_event(wakealarm_fd, wakealarm_event))
KLOG_ERROR(LOG_TAG,
"Registration of wakealarm event failed\n");
wakealarm_set_interval(healthd_config.periodic_chores_interval_fast);
}
首先創建一個wakealarm_fd的定時器與之對應的文件描述符,healthd_register_event將wakealarm事件註冊到wakealarm_fd文件節點用以監聽wakealarm事件,wakealarm_set_interval設置alarm喚醒的間隔
再看看uevent_init函數:
static void uevent_init(void) {
uevent_fd = uevent_open_socket(64*1024, true);
if (uevent_fd < 0) {
KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n");
return;
}
fcntl(uevent_fd, F_SETFL, O_NONBLOCK);
if (healthd_register_event(uevent_fd, uevent_event))
KLOG_ERROR(LOG_TAG,
"register for uevent events failed\n");
}
創建並打開一個64k的socket文件描述符uevent_fd,設置文件狀態標誌為非阻塞模,將uevent事件註冊到uevent_fd文件節點用以監聽uevent事件。
我們可以看到android利用epoll監聽了三個文件節點的改變事件,分別是:通過gBinderfd監聽線程Binder通信事件;通過wakealarm_fd監聽wakealarm事件;通過uevent_fd監聽wakealarm事件。至於如何監聽後面做詳細分析
在healthd_init中最後創建BatteryMonitor的對象,並將其初始化。BatteryMonitor主要接受healthd傳來的數據,做電池狀態的計算並更新。
我們可以看到在BatterMonitor中的init函數中有以下語句:
DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
struct dirent* entry;
。。。。。。。
while ((entry = readdir(dir))) {
const char* name = entry->d_name;
。。。。。。
}
POWER_SUPPLY_SYSFS_PATH定義為"/sys/class/power_supply",在init函數中打開系統該文件夾,然後一一讀取該文件夾下的文件內容,在while迴圈中判斷該文件夾下各個文件節點的內容,並將其初始化給相關的參數.
至此,healthd_init函數就分析完了,其主要工作就是:創建了三個文件節點用來監聽相應的三種事件改變;創建BatteryMonitor對象,並通過讀取/sys/class/power_supply將其初始化。
Healthd_init走完之後,接著就是調用healthd_mainloop函數,該函數維持了一個死迴圈,代碼如下:
static void healthd_mainloop(void) {
while (1) {
struct epoll_event events[eventct];
int nevents;
int timeout = awake_poll_interval;
int mode_timeout;
mode_timeout = healthd_mode_ops->preparetowait();
if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
timeout = mode_timeout;
nevents = epoll_wait(epollfd, events, eventct, timeout);
if (nevents == -1) {
if (errno == EINTR)
continue;
KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n");
break;
}
for (int n = 0; n < nevents; ++n) {
if (events[n].data.ptr)
(*(void (*)(int))events[n].data.ptr)(events[n].events);
}
if (!nevents)
periodic_chores();
healthd_mode_ops->heartbeat();
}
return;
}
Healthd_mainloop中維持了一個死迴圈,死迴圈中變數nevents 表示從epollfd中輪循中監聽得到的事件數目,這裡介紹一下輪詢機制中重要函數epoll_waite().
epoll_wait運行的道理是:等侍註冊在epfd上的socket fd的事務的產生,若是產生則將產生的sokct fd和事務類型放入到events數組中。且timeout如果為-1則為阻塞式,timeowout為0則表示非阻塞式。可以看到代碼中timeout為-1,故為阻塞式輪詢,當epollfd上有事件發生,則會走到下麵的處理邏輯。事件處理主要在for迴圈中:
在periodic_chores()中調用到healthd_battery_update()更新電池狀態。
void healthd_battery_update(void) {
int new_wake_interval = gBatteryMonitor->update() ?
healthd_config.periodic_chores_interval_fast :
healthd_config.periodic_chores_interval_slow;
if (new_wake_interval != wakealarm_wake_interval)
wakealarm_set_interval(new_wake_interval);
if (healthd_config.periodic_chores_interval_fast == -1)
awake_poll_interval = -1;
Else
awake_poll_interval = new_wake_interval == healthd_config.periodic_chores_interval_fast ?
-1 : healthd_config.periodic_chores_interval_fast * 1000;
}
可以看出該函數並不長,new_wake_interval表示新的wakealarm喚醒間隔,通過調用BatteryMonitor的update函數(後面詳細分析如何更新),其返回值為是否處於充電狀態,當處於充電狀態,則喚醒間隔為healthd_config.periodic_chores_interval_fast(短間隔),當不再充電狀態時喚醒間隔為healthd_config.periodic_chores_interval_slow(長間隔)
當新的間隔變數new_wake_interval與舊的變數wakealarm_wake_interval不一樣,則將新的喚醒間隔設置成wakealarm的喚醒間隔;
awake_poll_internal作為下一次epoll_waite的timeout參數,在這裡將其更新,在充電狀態下awake_poll_internal為-1,沒有充電的狀態下awake_poll_internal為60000ms
healthd主流程都是在main函數中處理,至此main已經分析完成,其簡要流程圖如下
Healthd處理邏輯
初始化處理
前面將healthd模塊中main函數分析完了,其主要工作流程有個大概的瞭解,但是其詳細處理邏輯並未做分析,在此之後,對Healthd的初始化,事件處理,狀態更新將做一個詳細的分析。
前面已經說過在healthd_init中創建了三個文件節點gBinderfd,uevent_fd,wakealarm_fd,並用以註冊監聽三種事件,註冊監聽都是通過healthd_register_event函數實現的。
healthd_register_event(gBinderFd, binder_event);
healthd_register_event(wakealarm_fd, wakealarm_event);
healthd_register_event(uevent_fd, uevent_event);
其healthd_register_event實現代碼如下:
int healthd_register_event(int fd, void (*handler)(uint32_t)) {
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLWAKEUP;
ev.data.ptr = (void *)handler;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
KLOG_ERROR(LOG_TAG,
"epoll_ctl failed; errno=%d\n", errno);
return -1;
}
eventct++;
return 0;
}
函數將相應的文件節點事件賦值為函數的第二個形參,也就是說相應的gBinderfd的事件處理函數為binder_event函數,同理wakealarm_fd,ueven_fd的事件事件處理分別為wakealarm_event,uevent_event函數。然後將其三個文件節點加入到epollfd中。
事件獲取與處理
Healthd中維持了一個阻塞式的死迴圈healthd_mainloop,在該函數中提供阻塞式的監聽已發送的事件函數epoll_wait(),healthd_mainloop中有如下代碼
nevents = epoll_wait(epollfd, events, eventct, timeout);
for (int n = 0; n < nevents; ++n) {
if (events[n].data.ptr)
(*(void (*)(int))events[n].data.ptr)(events[n].events);
}
當epoll_waite接受到gBinderfd,wakealarm_fd,uevent_fd其中的事件,便會將監聽到的事件加入到event數組中。在for迴圈中做處理,for迴圈中代碼看起來非常難懂,其實if判斷的便是event有沒有相應的處理函數,在前面註冊事件時候已經提到,三種句柄上的事件都有對應的處理函數,也就是當收到gBinderfd上的事件,便用binder_event函數處理,當收到uevent_fd上的事件便用uevent_event處理,當收到wakealarm_fd上的事件便用wakealarm_event處理。
這裡以較為重要的uevent_event事件處理為例:
#define UEVENT_MSG_LEN 2048
static void uevent_event(uint32_t /*epevents*/) {
char msg[UEVENT_MSG_LEN+2];
char *cp;
int n;
n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);
if (n <= 0)
return;
if (n >= UEVENT_MSG_LEN) /* overflow -- discard */
return;
msg[n] = '\0';
msg[n+1] = '\0';
cp = msg;
while (*cp) {
if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {
healthd_battery_update();
break;
}
/* advance to after the next \0 */
while (*cp++)
;
}
}
處理函數首先從uevent_fd 獲取事件數目,然後迴圈判斷是否是來自與power_supply目錄下的事件,如果是,則調用到healthd_battery_update中去更新電池狀態。
更新電池狀態
當收到事件,做一些判斷工作便需要更新電池狀態,其更新函數為healthd.cpp下的healthd_battery_update函數,但是主要更新並不在heathd中完成的,而是在BatteryMonitor中的update函數,其代碼較多,分段分析:
props.chargerAcOnline = false;
props.chargerUsbOnline = false;
props.chargerWirelessOnline = false;
props.batteryStatus = BATTERY_STATUS_UNKNOWN;
props.batteryHealth = BATTERY_HEALTH_UNKNOWN;
if (!mHealthdConfig->batteryPresentPath.isEmpty())
props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
else
props.batteryPresent = mBatteryDevicePresent;
props.batteryLevel = mBatteryFixedCapacity ?
mBatteryFixedCapacity :
getIntField(mHealthdConfig->batteryCapacityPath);
props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
props.batteryTemperature = mBatteryFixedTemperature ?
mBatteryFixedTemperature :
getIntField(mHealthdConfig->batteryTemperaturePath);
const int SIZE = 128;
char buf[SIZE];
String8 btech;
if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)
props.batteryStatus = getBatteryStatus(buf);
if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
props.batteryHealth = getBatteryHealth(buf);
if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
props.batteryTechnology = String8(buf);
在init函數中將healthd_config 對象傳入,並且將裡面的成員的一些地址信息去初始化保存起來。主要是保存一些地址信息,以及充電方式。在BatteryMonitor初始化中,heathd_config傳入init函數中,賦值為mHealthdConfig,上面一段主要是讀取/sys/class/power_supply下的文件節點信息初更新電池數據屬性值,
path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
mChargerNames[i].string());
if (readFromFile(path, buf, SIZE) > 0) {
if (buf[0] != '0') {
path.clear();
path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
mChargerNames[i].string());
switch(readPowerSupplyType(path)) {
case ANDROID_POWER_SUPPLY_TYPE_AC:
props.chargerAcOnline = true;
break;
case ANDROID_POWER_SUPPLY_TYPE_USB:
props.chargerUsbOnline = true;
break;
case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
props.chargerWirelessOnline = true;
break;
default:
KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
mChargerNames[i].string());
}
}
}
將電池當前的電量級別,電壓,溫度,健康狀況,電池狀態以及充放電倍率存入dmesgline變數中,在後面會將電池充電類型,電池使用時間都以字元串存入dmesgline變數中,然後:
KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);//向log記錄電池當前各種狀態信息
}
healthd_mode_ops->battery_update(&props);//更新電池
return props.chargerAcOnline | props.chargerUsbOnline |
props.chargerWirelessOnline;//返回是否在充電狀態
整個update函數做完更新數據,記錄數據到log之後,然後調用到BatteryPropertiesRegistrar的update函數繼續更新電池狀態,最後返回值為是否處於充電狀態。
BatteryPropertiesRegistrar的update函數未作任何操作調用Healthd_mode_android.cpp中的healthd_mode_android_battery_update函數,我們可以看看該函數
void healthd_mode_android_battery_update(
struct android::BatteryProperties *props) {
if (gBatteryPropertiesRegistrar != NULL)
gBatteryPropertiesRegistrar->notifyListeners(*props);
return;
}
這裡這裡直接調用到BatteryPropertiesRegistrar的notifyListeners去通知props改變了,props是什麼呢?props是定義的一個BatteryProperties屬性集,裡面的成員變數包含了所有的電池狀態信息,在update開始便通過讀取各個文件節點的實時數據更新電池屬性props,更新完成後通過BatteryPropertiesRegistrar通知其屬性監聽者去更新狀態,但是誰是監聽呢?
我們可以看到framework層中的BatteryService.java的onStart函數中有如下代碼:
public void onStart() {
IBinder b = ServiceManager.getService("batteryproperties");
final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
IBatteryPropertiesRegistrar.Stub.asInterface(b);
try {
batteryPropertiesRegistrar.registerListener(new BatteryListener());
} catch (RemoteException e) {
// Should never happen.
}
我們在初始化的時候已經提到過,當healthd初始化時候會創建BatteryPropertiesRegistrar的對象並將其publish註冊到系統服務中,註冊服務的語句如下:
defaultServiceManager()->addService(String16("batteryproperties"), this);
所以BatteryService在這裡獲取該服務,並以此註冊其監聽器為BatteryListener(),該監聽器監聽到BatteryProperties改變便會調用到BatteryService的update函數,去做電池電量相關計算以及顯示。
至此更新操作基本分析完成,其簡要流程如下圖所示
總結
Healthd是framework層傳遞來自底層電池事件信息並調用相關模塊更新電池狀態的一個中間層,其向下監聽來自底層PMU驅動上報的uevent電池事件,向上調用BatteryService去計算電池,電量,使用等相關信息,它通過一個阻塞式的死迴圈不斷監聽底層三個文件節點上的事件信息,當監聽到事件便調用到BatteryMonitor執行更新操作,通過BatteryService.java中註冊監聽電池屬性改變的函數,當電池屬性信息發生改變,即回調到BatteryService中做更新操作,更新完成一次電池事件的上報到更新整個流程就完成;總之Healthd是連接Battery模塊framework中java層與HAL層交互的主要通道。