epoll能显著的提高程序的CPU利用率。
本文主要是用于优化uevent监听,降低CPU使用率,uevent监听文章如下:
本文中代码是源码中healthd中的ueventfd的epoll监听代码
1. epoll_create1
epoll_create1
是 Linux 中用于创建一个新的 epoll 实例的系统调用。在使用 epoll 多路复用 I/O 模型时,首先需要创建一个 epoll 实例,以便将文件描述符添加到 epoll 集合中,以便监视其状态变化。
epoll_create1
的函数原型如下:
#include <sys/epoll.h>
int epoll_create1(int flags);
flags
参数是一个整数,用来指定 epoll 实例的行为。目前只定义了一个标志位EPOLL_CLOEXEC
,用于在执行exec
函数时关闭 epoll 实例。
在调用 epoll_create1
函数后,会返回一个新的 epoll 实例的文件描述符。如果调用成功,则返回一个非负整数作为 epoll 实例的文件描述符;如果失败,则返回 -1,并设置相应的错误码(通过 errno
变量获取)
在使用 epoll 实例后,通常会用 epoll_ctl
函数向 epoll 实例中添加、修改或删除需要监听的文件描述符,然后通过 epoll_wait
函数等待文件描述符的状态变化,从而实现高效的 I/O 事件监听和处理。
epoll_create1
是 epoll I/O 多路复用机制中用于创建 epoll 实例的关键函数之一。
cpp
int HealthLoop::InitInternal() {
epollfd_.reset(epoll_create1(EPOLL_CLOEXEC));
if (epollfd_ == -1) {
KLOG_ERROR(LOG_TAG, "epoll_create1 failed; errno=%d\n", errno);
return -1;
}
// Call subclass's init for any additional init steps.
// Note that healthd_config_ is initialized before wakealarm_fd_; see
// AdjustUeventWakealarmPeriods().
Init(&healthd_config_);
WakeAlarmInit();
UeventInit();
return 0;
}
2. epoll_wait
epoll_wait
是 Linux 中用于等待文件描述符上的 I/O 事件发生的系统调用。它会阻塞当前线程,直到指定的 epoll 实例中的文件描述符有事件发生或达到超时时间。
epoll_wait
的函数原型如下:
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epfd
是 epoll 实例的文件描述符,即通过epoll_create1
创建的返回值。events
是一个指向struct epoll_event
结构体数组的指针,用于接收发生事件的文件描述符和事件类型。maxevents
是events
数组的大小,即最多可以接收多少个事件。timeout
是等待的超时时间(以毫秒为单位),当设置为负数时将永久阻塞,直到有事件发生。
调用 epoll_wait
后,会阻塞当前线程,等待文件描述符上的事件发生。如果有事件发生,epoll_wait
将填充 events
数组,并返回实际发生事件的文件描述符数量。如果超时时间到达或发生错误,epoll_wait
将返回 0 或 -1,并设置相应的错误码(通过 errno
变量获取)。
在使用 epoll_wait
之前,需要使用 epoll_ctl
函数将待监视的文件描述符添加到 epoll 实例中。
epoll_wait
是 epoll I/O 多路复用机制中用于等待文件描述符上的事件发生的函数。它能够高效地监视多个文件描述符并处理相应的 I/O 事件。
cpp
void HealthLoop::MainLoop(void) {
int nevents = 0;
while (1) {
reject_event_register_ = true;
size_t eventct = event_handlers_.size();
struct epoll_event events[eventct];
int timeout = awake_poll_interval_;
nevents = epoll_wait(epollfd_, events, eventct, -1);
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) {
auto* event_handler = reinterpret_cast<EventHandler*>(events[n].data.ptr);
event_handler->func(event_handler->object, events[n].events);
}
}
}
return;
}
3. epoll_ctl
epoll_ctl
是 Linux 中用于控制 epoll 实例的系统调用,它可以向 epoll 实例中添加、修改或删除需要监视的文件描述符。
epoll_ctl
的函数原型如下:
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd
是 epoll 实例的文件描述符,即通过epoll_create1
创建的返回值。op
是操作类型,可以是EPOLL_CTL_ADD
(添加文件描述符)、EPOLL_CTL_MOD
(修改文件描述符)或EPOLL_CTL_DEL
(删除文件描述符)。fd
是需要添加、修改或删除的文件描述符。需要监听是否发生变化的文件描述符event
是一个指向struct epoll_event
结构体的指针,用于指定事件类型和其他属性。
调用 epoll_ctl
后,根据 op
的不同,可以实现向 epoll 实例中添加、修改或删除需要监视的文件描述符。如果调用成功,则返回 0;如果失败,则返回 -1,并设置相应的错误码(通过 errno
变量获取)。
通过使用 epoll_ctl
,可以动态地向 epoll 实例中添加或删除需要监视的文件描述符,从而实现高效的 I/O 事件监听和处理。
epoll_ctl
是 epoll I/O 多路复用机制中用于控制 epoll 实例的函数,是实现高效事件驱动编程的关键之一。
cpp
// Register an epoll event. When there is an event, |func| will be
// called with |this| as the first argument and |epevents| as the second.
// This may be called in a different thread from where StartLoop is called
// (for obvious reasons; StartLoop never ends).
// Once the loop is started, event handlers are no longer allowed to be
// registered.
using BoundFunction = std::function<void(HealthLoop*, uint32_t /* epevents */)>;
struct EventHandler {
HealthLoop* object = nullptr;
int fd;
BoundFunction func;
};
int HealthLoop::RegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) {
CHECK(!reject_event_register_);
auto* event_handler =
event_handlers_
.emplace_back(std::make_unique<EventHandler>(EventHandler{this, fd, func}))
.get();
struct epoll_event ev;
ev.events = EPOLLIN;
if (wakeup == EVENT_WAKEUP_FD) ev.events |= EPOLLWAKEUP;
ev.data.ptr = reinterpret_cast<void*>(event_handler);
if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &ev) == -1) {
KLOG_ERROR(LOG_TAG, "epoll_ctl failed; errno=%d\n", errno);
return -1;
}
return 0;
}
void HealthLoop::UeventInit(void) {
uevent_fd_.reset(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);
//注册uevent_fd_的epoll监听
if (RegisterEvent(uevent_fd_, &HealthLoop::UeventEvent, EVENT_WAKEUP_FD))
KLOG_ERROR(LOG_TAG, "register for uevent events failed\n");
}
经过以上步骤后,就可以实现当uevent_fd_中有uevent事件到来,nevents 就会大于0,则会触发HealthLoop::UeventEvent方法,进而开始处理uevent事件,本方法大大提升CPU使用率,将CPU占用率从98%降到了1%。
cpp
// TODO(b/140330870): Use BPF instead.
#define UEVENT_MSG_LEN 2048
void HealthLoop::UeventEvent(uint32_t /*epevents*/) {
// No need to lock because uevent_fd_ is guaranteed to be initialized.
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)) {
ScheduleBatteryUpdate();
break;
}
/* advance to after the next \0 */
while (*cp++)
;
}
}