power_host进程占用CPU高的案例分析
1 关键字 power_supply; battery; 设备属性文件;CPU占用; 2 问题描述 拔掉电池使用USB供电重启系统后,power_host进程的CPU占用较高,每5秒钟CPU的占用在0%~17%之间跳跃。对比插上电池重启系统的场景,power_host进程的CPU占用反而比较低,CPU占用不超过0.
1 关键字
power_supply; battery; 设备属性文件;CPU占用;
2 问题描述
拔掉电池使用USB供电重启系统后,power_host进程的CPU占用较高,每5秒钟CPU的占用在0%~17%之间跳跃。对比插上电池重启系统的场景,power_host进程的CPU占用反而比较低,CPU占用不超过0.6%。
3 问题原因
电池内核驱动每隔5秒钟会通过uevent上报电池的状态信息,power_host中的battery_thread线程专门负责接受驱动上报的uevent事件。当接收到uevent事件时,会读取/sys/class/power_supply/battery/下的capacity、voltage_now、temp、health、status等11个属性的文件,通知给关注电池状态的上层应用。
在读取这些属性文件时共耗时100ms以上,而插入电池场景下读取文件仅耗时8ms。根本原因是由于内核电池驱动挂接的读取capacity属性的函数中,当电池未插上时会调用mdelay函数延迟100ms。内核的mdelay函数会让CPU进入忙等状态,因此用户态进程读取/sys/class/power_supply/battery/capacity文件需要100ms以上才能返回,并且期间一直占用CPU。
4 解决方案
电池内核驱动当电池未插上时,在读取/sys/class/power_supply/battery/capacity属性的处理函数中,将mdelay改为msleep避免一直占用CPU。
5 定位过程
1、使用top命令查看CPU占用率,发现power_host进程CPU占用达到17%。
执行top命令
Tasks: 267 total, 1 running, 266 sleeping, 0 stopped, 0 zombie
Mem: 911368K total, 487644K used, 423724K free, 880640 buffers
Swap: 524284K total, 44040192 used, 481276K free, 140064K cached
400%cpu 7%user 0%nice 46%sys 346%idle 0%iow 0%irq 0%sirq 0%host
PID USER PR NI VIRT RES SHR S[%CPU] %MEM TIME+ ARGS
659 power_host 20 0 7.9M 4.2M 3.6M S 17.0 0.4 0:02.61 power_host
....
2、进一步查看power_host进程的CPU占用,发现battery_thread和IPC_1_1298线程CPU占用率呈周期性跳跃状态,二个线程CPU共占用10%上下。
使用top -Hp 659 查看power_host进程CPU占用具体信息
Threads: 8 total, 0 running, 8 sleeping, 0 stopped, 0 zombie
Mem: 911368K total, 491324K used, 420044K free, 880640 buffers
Swap: 524284K total, 44040192 used, 481276K free, 141508K cached
400%cpu 65%user 0%nice 151%sys 184%idle 0%iow 0%irq 0%sirq 0%host
TID USER PR NI VIRT RES SHR S[%CPU] %MEM TIME+ THREAD PROCESS
741 power_host 20 0 8.0M 4.2M 3.6M S 7.0 0.4 0:27.07 battery_thread power_host
1298 power_host 20 0 8.0M 4.2M 3.6M S 3.3 0.4 0:12.34 IPC_3_1298 power_host
1296 power_host 0 -20 8.0M 4.2M 3.6M S 0.3 0.4 0:00.03 IPC_1_678 power_host
1755 power_host 0 -20 8.0M 4.2M 3.6M S 0.0 0.4 0:00.00 IPC_1_678 power_host
1293 power_host 20 0 8.0M 4.2M 3.6M S 0.0 0.4 0:00.00 IPC_2_1293 power_host
678 power_host 20 0 8.0M 4.2M 3.6M S 0.0 0.4 0:10.65 IPC_1_678 power_host
673 power_host 20 0 8.0M 4.2M 3.6M S 0.0 0.4 0:00.00 IPC_0_673 power_host
659 power_host 20 0 8.0M 4.2M 3.6M S 0.0 0.4 0:00.07 power_host power_host
3、查看代码,看看battery_thread线程主要是做什么的。
battery_thread线程实现的代码/drivers/peripheral/battery/interfaces/hdi_service/src/battery_thread.cpp。
// StartThread在BatteryInterfaceImpl::Init中调用
void BatteryThread::StartThread(void* service)
{
Init(service);
Run(service);
}
void BatteryThread::Run(void* service)
{
std::thread batteryThread(&BatteryThread::LoopingThreadEntry,
this, service);
// 创建battery_thread线程
pthread_setname_np(batteryThread.native_handle(), "battery_thread");
batteryThread.detach();
}
int32_t BatteryThread::Init([[maybe_unused]] void* service)
{
......
// 初始化uevent的事件接收
InitUevent();
return HDF_SUCCESS;
}
int32_t BatteryThread::InitUevent()
{
ueventFd_ = OpenUeventSocket();
if (ueventFd_ == INVALID_FD) {
BATTERY_HILOGE(COMP_HDI, "open uevent socket failed, fd is invalid");
return HDF_ERR_BAD_FD;
}
// 注册uenent事件的回调处理函数
fcntl(ueventFd_, F_SETFL, O_NONBLOCK);
callbacks_.insert(std::make_pair(ueventFd_,
&BatteryThread::UeventCallback));
if (RegisterCallback(ueventFd_, EVENT_UEVENT_FD)) {
BATTERY_HILOGE(COMP_HDI, "register Uevent event failed");
return HDF_ERR_BAD_FD;
}
return HDF_SUCCESS;
}
int32_t BatteryThread::OpenUeventSocket()
{
int32_t bufferSize = UEVENT_BUFF_SIZE;
struct sockaddr_nl address = {
.nl_family = AF_NETLINK,
.nl_pid = getpid(),
.nl_groups = 0xffffffff
};
// 创建接收uevent的socket
int32_t fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC,
NETLINK_KOBJECT_UEVENT);
......
return fd;
}
// 接收uevent事件并进行处理,主要处理函数UpdateBatteryInfo
void BatteryThread::UeventCallback(void* service)
{
char msg[UEVENT_MSG_LEN + UEVENT_RESERVED_SIZE] = { 0 };
ssize_t len = recv(ueventFd_, msg, UEVENT_MSG_LEN, 0);
if (len < 0 || len >= UEVENT_MSG_LEN) {
BATTERY_HILOGI(COMP_HDI, "recv return msg is invalid, len: %{public}zd", len);
return;
}
// msg separator
msg[len] = '\0';
msg[len + 1] = '\0';
if (!IsPowerSupplyEvent(msg)) {
return;
}
UpdateBatteryInfo(service);
}
void BatteryThread::UpdateBatteryInfo(void* service)
{
......
// UpdateInfoByReadSysFile读取/sys/class/power_supply/battery/
// 目录下的属性文件
provider_->UpdateInfoByReadSysFile(batteryInfo.get());
event.capacity = batteryInfo->capacity_;
.......
event.totalEnergy = batteryInfo->totalEnergy_;
// 调用框架层battery service注册的callback函数
// BatteryService::HandleBatteryCallbackEvent,
// 通知关注电池信息的模块更新电池信息
if (g_callback != nullptr) {
g_callback->Update(event);
} else {
BATTERY_HILOGI(FEATURE_BATT_INFO, "g_callback is nullptr");
}
}
battery_thread线程是power_host进程创建的线程,最主要的任务是接收内核驱动发送的uevent,并通知上层battery service电池信息的变化。而BatteryThread::UpdateBatteryInfo就是最核心的处理函数。
通过在BatteryThread::UpdateBatteryInfo中增加hilog调试发现,该函数在插电池和不插电池重启后所耗费的时间差异较大。不插电池重启后执行该函数大约花费103ms,而插上电池重启后只花费8ms,特别是调用ParseCapacity函数最为耗时。
ParseCapacity函数实际就是读取/sys/class/power_supply/battery/capacity文件中的值,为什么不同的场景下耗时差异这么大?
// 文件/drivers/peripheral/battery/interfaces/hdi_service/src/power_supply_provider.cpp
void PowerSupplyProvider::UpdateInfoByReadSysFile(struct BatterydInfo* info) const
{
ParseCapacity(&info->capacity_);
ParseVoltage(&info->voltage_);
ParseTemperature(&info->temperature_);
ParseHealthState(&info->healthState_);
ParseChargeState(&info->chargeState_);
ParseChargeCounter(&info->chargeCounter_);
ParseCurrentNow(&info->curNow_);
ParseCurrentAverage(&info->curAverage_);
ParseRemainEnergy(&info->remainEnergy_);
ParseTotalEnergy(&info->totalEnergy_);
ParsePresent(&info->present_);
......
}
int32_t PowerSupplyProvider::ParseCapacity(int32_t* capacity) const
{
char buf[MAX_BUFF_SIZE] = {0};
// 读取/sys/class/power_supply/battery/capacity文件
int32_t ret = ReadBatterySysfsToBuff(batterySysfsInfo_.capacityPath.c_str(), buf, sizeof(buf));
if (ret != HDF_SUCCESS) {
return ret;
}
int32_t value = ParseInt(buf);
*capacity = value;
return HDF_SUCCESS;
}
4、找到读取capacity文件耗时的原因。
/sys/目录是sysfs文件系统,一种虚拟文件系统,与proc文件系统有些类似。用于对系统设备进行管理,可以产生一个包括所有系统硬件的层级视图,向用户空间导出内核对象并且能对其进行读写操作。
/sys/class/power_supply/battery/目录下属性文件的读写由电池内核驱动实现,通过向linux内核注册power supply设备将读取电池属性文件的函数挂接到内核。
// 电池充放电驱动probe函数
static int battery_charger_probe(struct platform_device *pdev)
{
......
// battery_monitor_work用于周期性上报电池状态信息
INIT_DELAYED_WORK(&chip->battery_monitor_work,
battery_monitor_func);
......
chip->batt_psy_d.name = "battery";
chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
chip->batt_psy_d.properties = the_batt_power_props;
chip->batt_psy_d.num_properties =
ARRAY_SIZE(the_batt_power_props);
// 挂接power supply设备属性读、写等接口函数
chip->batt_psy_d.get_property = batt_power_get_property;
chip->batt_psy_d.set_property = batt_power_set_property;
chip->batt_psy_d.property_is_writeable =
batt_property_is_writeable;
chip->batt_psy_d.external_power_changed =
batt_external_power_changed;
batt_psy_cfg.drv_data = chip;
// 注册power supply设备
chip->batt_psy = devm_power_supply_register(chip->dev,
&chip->batt_psy_d,
&batt_psy_cfg);
......
// probe函数中开始延迟执行battery_monitor_work
schedule_delayed_work(&chip->battery_monitor_work,
msecs_to_jiffies(2000));
......
}
// 读取电池所有属性的入口函数
static int batt_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct the_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = get_prop_batt_status(chip);
break;
case POWER_SUPPLY_PROP_CAPACITY: // 读取capacity文件
val->intval = get_prop_capacity(chip);
break;
......
}
return 0;
}
static int get_prop_capacity(struct the_chip *chip)
{
......
// 电池没有插上时,会执行if分支,其中调用mdelay导致CPU忙等100ms,
// 用户态读取capacity要100+ms才能返回。
if (!get_prop_batt_present(chip) ||
(chip->battery_status_change != 0)) {
mdelay(100);
if (!get_prop_batt_present(chip)) {
last_soc = DEFAULT_CAPACITY;
return DEFAULT_CAPACITY;
}
}
......
}
5、充放电内核驱动程序每5s会调用battery_monitor_func,周期性向用户态发送uevent事件通知电池状态信息。
// 电池充放电驱动probe函数
static int battery_charger_probe(struct platform_device *pdev)
{
......
// battery_monitor_work用于周期性上报电池状态信息
INIT_DELAYED_WORK(&chip->battery_monitor_work,
battery_monitor_func);
......
// probe函数中延迟2s执行battery_monitor_work
schedule_delayed_work(&chip->battery_monitor_work,
msecs_to_jiffies(2000));
......
}
static void battery_monitor_func(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct the_chip *chip = container_of(dwork, struct the_chip,
battery_monitor_work);
......
// 查看电池状态等操作,并通知到用户态
user_action_func(chip);
// 5s后再次执行battery_monitor_work
schedule_delayed_work(&chip->battery_monitor_work,
msecs_to_jiffies(5000));
}
static int user_action_func(struct the_chip *chip)
{
......
// 调用power supply的接口通知用户态电池状态信息
power_supply_changed(chip->batt_psy);
......
}
6、从上述分析看,battery_thread线程周期性CPU占用高,是因为内核电池充放电管理的驱动程序每5秒钟通过uevent通知用户态电池状态信息,battery_thread接收到uevent后读取设备的属性文件。因为电池驱动挂接的get_prop_capacity函数在电池未插入的情况下会delay 100ms导致读取文件时间长并一直占用CPU,这样引起的CPU占用高的问题。
6 知识分享
- sysfs是基于内存的文件系统,用于向用户空间导出内核对象并且能对其进行读写。如果读写sysfs下的文件有异常,就需要分析设备驱动中向内核注册的读写等函数。
- 内核延时函数mdelay与msleep的区别:mdelay是忙等待函数,会占用CPU资源,延迟时间是准确的。msleep是休眠函数,不占用CPU资源,延迟时间通常高于给定值。
更多推荐
所有评论(0)