(原创)android6.0系统Healthd深入分析

概述

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();
}

前面三条语句做初始化工作,设置线程池最大线程数,禁用后台调度,以及将gBinderfd加入到epoll中。healthd_register_event将binder_event事件注册到gBinderfd文件节点用以监听Binder事件。gBatteryPropertiesRegistrar->publish将"batteryproperties"这个Service注册到ServiceManager中

再来看看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());

        }

    }

}

这一段代码其实实现的功能很简单,通过读取/sys/class/power_supply/文件夹下不同类型的充电设备的online节点,如果不为空则表示处于充电状态,判断充电类型为AC充电器,usb充电还是无线充电并将其相应的属性置true。

char dmesgline[256];

if (props.batteryPresent) {

snprintf(dmesgline, sizeof(dmesgline),

         "battery l=%d v=%d t=%s%d.%d h=%d st=%d",

         props.batteryLevel, props.batteryVoltage,

         props.batteryTemperature < 0 ? "-" : "",

         abs(props.batteryTemperature / 10),

         abs(props.batteryTemperature % 10), props.batteryHealth,

         props.batteryStatus);

    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {

        int c = getIntField(mHealthdConfig->batteryCurrentNowPath);

        char b[20];

        snprintf(b, sizeof(b), " c=%d", c / 1000);

        strlcat(dmesgline, b, sizeof(dmesgline));

    }

将电池当前的电量级别,电压,温度,健康状况,电池状态以及充放电倍率存入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层交互的主要通道。

时间: 04-07

(原创)android6.0系统Healthd深入分析的相关文章

Android6.0系统权限那些事

Android6.0带来了新的权限管理方式,本文主要来源于官方文档,加入了自己的理解,目的是想总结Android6.0权限管理的新方式,其他部分可能主要是总结式的带过,后续再详细分析. 一.Security Architecture(安全体系结构) Android安全体系结构的核心是: 默认情况下没有任何应用有权限去执行对其他应用.操作系统.用户有不利影响的操作.这是一个核心的设计理念.记住这句话对后面的权限管理可以很好的理解. 正式由于这样的设计理念,默认情况下应用不能去读写用户的私有数据(比

Android6.0系统添加那些新特性

??? 北京时间9月30日凌晨在美国旧金山举行2015年秋季新品公布会.在公布会上代号为"Marshmallow(棉花糖)"的安卓6.0系统正式推出.新系统的总体设计风格依旧保持扁平化的MeterialDesign风格. Android6.0在对软件体验与执行性能上进行了大幅度的优化.安卓权限系统被又一次设计了. ??? 全新的Android M相比眼下的Android Lollipop(5.0)有二十项重大的改进: ? ? 原文博客请參考:点击打开链接 ??? 一:App Permi

[RK3288][Android6.0] 系统按键驱动流程分析【转】

本文转载自:http://blog.csdn.net/kris_fei/article/details/77894406 Rockchip的按键驱动位于 kernel/drivers/input/keyboard/rk_keys.c 默认支持的keys在dts中定义: 其中power key作为普通gpio,具有唤醒功能.而其他按键比如,volume up/down 可以通过adc精确读取到gpio的电压值,原理图如下:   和一般的按键一样,驱动是通过内核input子系统来将keys注册供用户

Android6.0使用BaiDu地图SDK动态获取定位权限

1.报错原因: 在集成百度地图SDK的时候在手机上无法定位,检查没有任何错误,最后通过搜索才知道是Android版本为6.0的问题,这是因为在Android6.0采用了运行时权限(RuntimePermissions),Android6.0的权限一般分为两种,一种时普通权限,可以直接获取,其它的运行时权限,需要提示用户手动同意之后,才能获取. 失败的原因就是,小米手机MIUI是Android6.0.1,如果不加动态获取权限的代码,是不会提示的,没有得到权限,当然无法定位. 2.解决代码: pri

Android6.0运行时权限管理

自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有的权限而导致的无法安装的事情,也不会再不征求用户授权的情况下,就可以任意的访问用户隐私,而且即使在授权之后也可以及时的更改权限.这就是6.0版本做出的更拥护和注重用户的一大体现. 一.认知 今天我们就来学习下Android6.0的权限管理. Android6.0系统把权限分为两个级别: 一个是Normal Permiss

Android5.0系统的优缺点

Android L(5.0)正式定名为 Lollipop(棒棒糖).安卓已经六岁了,也总算有一次重大改观了.安卓5.0 Lollipop带来了全新的,扁平化的外观,更好的通知中心,重新设计的核心应用,并提升了在安卓设备上的性能表现--增加了一些以前所缺失的重要应用类别. 首先来看一下Android L相比之前的版本有什么新鲜的东西. 原文博客请参考:点击打开链接 一:自定义通知中心 Lollipop为用户带来了对通知中心前所未有的控制性,每一个应用程序都可以在通知中心进行单独的设置,并且只有在用

android6.0 Launcher2应用解析

在之前我们分析了Android6.0系统在启动时安装应用程序的过程,这些应用程序安装好之后,Launcher应用就负责把它们在桌面上展示出来. 一.AMS启动Launcher Launcher应用是在AMS的systemReady方法中直接调用startHomeActivityLocked启动的,下面是systemReady启动Launcher的代码. startHomeActivityLocked(mCurrentUserId, "systemReady"); 我们来看下这个函数,先

Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题

版权声明:本文为博主原创文章,未经博主允许不得转载. 正常情况下调用系统相机拍照: 如果拍照后点击的是“确定”图标,返回的resultCode = -1(Activity.RESULT_OK): 如果点击的是底部的“返回”键,返回的resultCode = 0(Activity.RESULT_CANCELED). 简单的调用系统相机的写法: //调用系统拍照 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); String p

Android6.0权限系统

Android6.0权限系统 Android权限系统是一个非常重要的安全问题,因为它只有在安装时会询问一次.一旦软件本安装之后,应用程序可以在用户毫不知情的情况下使用这些权限来获取所有的内容. 很多坏蛋会通过这个安全缺陷来收集用户的个人信息并使用它们来做坏事的情况就不足为奇了. Android团队也意识到了这个问题.在经过了7年后,权限系统终于被重新设置了.从Anroid 6.0(API Level 23)开始,应用程序在安装时不会被授予任何权限,取而代之的是在运行时应用回去请求用户授予对应的权