C++中epoll用法总结

epoll能显著的提高程序的CPU利用率。

本文主要是用于优化uevent监听,降低CPU使用率,uevent监听文章如下:

C++层uevent获取-CSDN博客

本文中代码是源码中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 结构体数组的指针,用于接收发生事件的文件描述符和事件类型。
  • maxeventsevents 数组的大小,即最多可以接收多少个事件。
  • 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++)
            ;
    }
}
相关推荐
Eastsea.Chen1 小时前
MTK Android12 user版本MtkLogger
android·framework
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
长亭外的少年8 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
Yang.999 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王9 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_9 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀10 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++