1. 概览
在进入 second stage init 讲解之前,先来看看它事件监听及处理的机制 – Epoll 类,它实际上是对 epoll 的封装,使他变得更加适合再 init 中来跟踪事件以及分发触发方法等。整个类只有 4 个方法,在如此小巧的条件下实现了:事件监听的注册、卸载、跟踪以及收集事件的处理方法。这也意味着用户在注册后,在等待到事件后可以直接调用返回的处理方法列表。
2.整体使用骨架
second init 中就是使用 Epoll 来跟踪事件并处理的,下面看下 Epoll 的使用流程。
//system\core\init\init.cpp
SecondStageMain//code 1Epoll epoll;epoll.Open();//code 2InstallInitNotifier(&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 3while (true) {auto pending_functions = epoll.Wait(epoll_timeout);for (const auto& function : *pending_functions) {(*function)();}}
2.1 Epoll 的实例化 – code1
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 {};
}
2.2 注册监听对象及处理方法 – code2
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)//aInfo info;info.events = events;info.handler = std::make_shared<decltype(handler)>(std::move(handler));auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(info));//bepoll_event ev;ev.events = events;ev.data.ptr = reinterpret_cast<void*>(&it->second);//cepoll_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 实现监听。
2.3 等待事件并返回处理方法 – code3
可见 Wait 方法会被循环调用不做退出的,这部分的概念和 Looper 还是很相似的,一个线程中只存在一个 Epoll中,并且 Epoll 的回调方法也是该线程执行的。下面就来看看它的实现
Result<std::vector<std::shared_ptr<Epoll::Handler>>> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout)//aconst 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));//bstd::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);}//creturn 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)();
}
3. 总结
可见使用了 Epoll 后,只要简单的几个步骤即可,作为框架使用还是相当方便的,其中隐藏了 epoll 的数据构造,用户只要提供 监听对象和对于的触发方法即可。不过值得注意的是,这是单线程的,如果注册太多的监听对象,或者某一个处理方法中耗时过长,还是相当影响其他监听事件的处理的。