在进入 second stage init 讲解之前,先来看看它事件监听及处理的机制 – Epoll 类,它实际上是对 epoll 的封装,使他变得更加适合再 init 中来跟踪事件以及分发触发方法等。整个类只有 4 个方法,在如此小巧的条件下实现了:事件监听的注册、卸载、跟踪以及收集事件的处理方法。这也意味着用户在注册后,在等待到事件后可以直接调用返回的处理方法列表。
second init 中就是使用 Epoll 来跟踪事件并处理的,下面看下 Epoll 的使用流程。
//system\core\init\init.cpp
SecondStageMain
//code 1
Epoll epoll;
epoll.Open();
//code 2
InstallInitNotifier(&epoll);
auto clear_eventfd = [] {
uint64_t counter;
TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter)));
};
epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd)
//code 3
while (true) {
auto pending_functions = epoll.Wait(epoll_timeout);
for (const auto& function : *pending_functions) {
(*function)();
}
}
Epoll 构造方法使用的是空的并没做什么,再 Open 中也是很简单的申请了一个 epoll 的句柄,并记录在 epoll_fd_类变量中。
//system\core\init\epoll.cpp
Result<void> Epoll::Open() {
if (epoll_fd_ >= 0) return {};
epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
if (epoll_fd_ == -1) {
return ErrnoError() << "epoll_create1 failed";
}
return {};
}
Epoll 的监听对象只能是 fd 也就是文件句柄,它的回调方法原型如下,
//system\core\init\epoll.h
typedef std::function<void()> Handler;
准备好监听对象和它的处理方法后就可以调用 RegisterHandler 注册到 Epoll 中了,来看看它的定义
Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events)
//a
Info info;
info.events = events;
info.handler = std::make_shared<decltype(handler)>(std::move(handler));
auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(info));
//b
epoll_event ev;
ev.events = events;
ev.data.ptr = reinterpret_cast<void*>(&it->second);
//c
epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev)
可见注册分为如下几个步骤
a) 构建 Info 信息,其中包括它对 fd 中关心的事件类型,比如默认值 EPOLLIN,如果来这个事件就意味着 fd 句柄有数据可以读取了。还有用来处理 fd 数据的回调方法 handler。最后将 Info 和 fd 传入到 map epoll_handlers_中去,供后续使用。
b) 设置 epoll_event 事件,该数据结构则是使用 epoll 的的标准构造方法,其中记录的 events 则是供 kernel 使用的和上面提到的 info.events值一致。并将 Info 实例作为它的私有数据。
c) 最后调用 epoll_ctl 将 fd 添加到 epoll_fd_中去,如此系统才会对该 fd 实现监听。
可见 Wait 方法会被循环调用不做退出的,这部分的概念和 Looper 还是很相似的,一个线程中只存在一个 Epoll中,并且 Epoll 的回调方法也是该线程执行的。下面就来看看它的实现
Result<std::vector<std::shared_ptr<Epoll::Handler>>> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout)
//a
const auto max_events = epoll_handlers_.size();
epoll_event ev[max_events];
auto num_events = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_, ev, max_events, timeout_ms));
//b
std::vector<std::shared_ptr<Handler>> pending_functions;
for (int i = 0; i < num_events; ++i) {
auto& info = *reinterpret_cast<Info*>(ev[i].data.ptr);
if ((info.events & (EPOLLIN | EPOLLPRI)) == (EPOLLIN | EPOLLPRI) &&
(ev[i].events & EPOLLIN) != ev[i].events) {
// This handler wants to know about exception events, and just got one.
// Log something informational.
LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
}
pending_functions.emplace_back(info.handler);
}
//c
return pending_functions;
a) 监听 epoll_fd_ 也就是 epoll句柄,只要挂在它上面的任何一个 fd 来事件了,epoll_wait 就会返回。当然超时的话也会返回。
b) 循环遍历,看看是哪一个 epoll_event 来数据了,可以查看它的 epoll_event.events 成员变量。对于没有注册的事件则会报 Error 级别的log,但不会退出线程。最后再收集下需要处理的 fd 的回调方法,存入 pending_functions 向量中。
c) 返回 pending_functions 向量,线程只要循环调用它内部的回调方法即可。
for (const auto& function : *pending_functions) {
(*function)();
}
可见使用了 Epoll 后,只要简单的几个步骤即可,作为框架使用还是相当方便的,其中隐藏了 epoll 的数据构造,用户只要提供 监听对象和对于的触发方法即可。不过值得注意的是,这是单线程的,如果注册太多的监听对象,或者某一个处理方法中耗时过长,还是相当影响其他监听事件的处理的。