Linux下监听sysfs电池事件问题:libudev与inotify均无法捕获容量变更事件
问题根因
你的两版代码逻辑本身没有致命错误,收不到事件是由Linux内核的底层行为和你对参考代码的理解偏差导致的:
- libudev收不到容量变更事件:Linux power_supply子系统默认仅在「外接电源插拔、充放电状态切换、电池故障」这类离散突变场景下才会发送uevent通知,电池容量每1%的渐变属于常规状态更新,内核不会主动触发uevent上报,因此udev monitor自然捕获不到对应事件。
- inotify实现不生效:首先你监听的
IN_ACCESS是文件被读取时才会触发的事件,内核更新电池容量参数不会主动读取uevent文件,自然不会触发事件;其次sysfs是虚拟文件系统,仅少数特殊节点支持inotify事件上报,uevent不在支持列表内。你参考的状态栏代码中的inotify仅用于监听电源设备插拔事件,其内部默认内置了30~60秒的定时轮询逻辑来更新电池容量,你只提取了inotify部分的逻辑,遗漏了核心的轮询分支。
适配嵌入式场景的最优实现方案
你需要的低功耗事件驱动方案可以用timerfd + poll/epoll组合实现,完全符合你不使用sleep()的要求,功耗开销极低:
- 初始化udev monitor监听power_supply子系统事件,用于处理外接电源插拔这类需要立刻响应的事件
- 用
timerfd_create创建定时器,设置符合你业务需求的容量检测间隔(太阳能设备如果对容量实时性要求不高,可设置为1~5分钟的间隔,进一步降低功耗) - 将udev的fd和timerfd同时加入poll监听列表
- 事件触发时判断事件来源:
- 来自udev的事件:立刻读取电池全量状态更新
- 来自timerfd的事件:仅读取容量节点更新数值即可
示例代码
#include <libudev.h> #include <poll.h> #include <stdio.h> #include <sys/timerfd.h> #include <unistd.h> #include <stdint.h> #define BAT_CAP_PATH "/sys/class/power_supply/BAT0/capacity" static int read_bat_cap(void) { int cap = 0; FILE *f = fopen(BAT_CAP_PATH, "r"); if (!f) return -1; fscanf(f, "%d", &cap); fclose(f); return cap; } int main(void) { struct udev *udev = udev_new(); if (!udev) return 1; struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "power_supply", NULL); udev_monitor_enable_receiving(mon); int udev_fd = udev_monitor_get_fd(mon); // 创建定时器,每30秒触发一次 int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); struct itimerspec ts = { .it_interval = {.tv_sec = 30, .tv_nsec = 0}, .it_value = {.tv_sec = 1, .tv_nsec = 0} // 首次1秒后触发 }; timerfd_settime(timer_fd, 0, &ts, NULL); struct pollfd fds[] = { {.fd = udev_fd, .events = POLLIN}, {.fd = timer_fd, .events = POLLIN} }; while (1) { if (poll(fds, 2, -1) < 0) break; if (fds[0].revents & POLLIN) { // 处理电源插拔事件,立刻更新全量状态 struct udev_device *dev = udev_monitor_receive_device(mon); if (dev) { printf("电源状态变更,当前容量:%d%%\n", read_bat_cap()); udev_device_unref(dev); } } if (fds[1].revents & POLLIN) { // 定时更新容量 uint64_t expire_cnt; read(timer_fd, &expire_cnt, sizeof(expire_cnt)); printf("定时刷新,当前容量:%d%%\n", read_bat_cap()); } } close(timer_fd); udev_unref(udev); return 0; }
这种方案既避免了阻塞的sleep()调用,也没有无效的空轮询,整体开销和纯事件驱动方案一致,完全适合嵌入式太阳能设备的低功耗需求。
内容的提问来源于stack exchange,提问作者pan
相关产品推荐
相关产品推荐

