Kanon EPoller
Kanon(六): Epoller(epoll wrapper)
谈谈Epoller的实现细节。
UpdateChannel
根据man epoll可以知道对内核的事件表的操作是通过epoll_ctl()和其参数指定的:
EPOLL_ADDEPOLL_MODEPOLL_DEL
因此封装成:1
2
3void UpdateChannel(Channel* ch);
// Forward to UpdateEpollEvent()
void UpdateEpollEvent(int op, Channel* ch);
对于epoll_ctl(),它只需要fd,因此Channel本身是不兼容的,它需要额外存储,为了避免多余的空间开销,epoll它并不是返回fd和触发事件,而是返回epoll_data_t数据和触发事件,具体来说,是这样的:
1 | typedef union epoll_data { |
可以看出,struct epoll_data有个void*成员,所以可以存储任何类型的数据,兼容用户自定义数据结构,到时候只需要转换回来就行了。
相比,如果struct epoll_event的第二个数据成员是fd,那就十分不灵活,不利于扩展。
这样对于UpdateEpollEvent也就没有疑问了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void Epoller::UpdateEpollEvent(int op, Channel* ch) noexcept {
int fd = ch->GetFd();
struct epoll_event ev;
ev.events = ch->GetEvents(); // interested
if (!ch->IsNoneEvent() && is_et_mode_) {
ev.evnets |= EPOLLET;
}
ev.data.ptr = static_cast<void*>(ch);
if (::epoll_ctl(epoll_fd_, op, fd, &ev)) {
// error handling
}
}
接下来实现UpdateChannel()又遇到一个问题:我怎么知道Channel是否添加过?故而Channel有一个成员记录它的状态,在Epoller中分为三种状态:1
2
3
4enum EventStatus {
kNew = -1,
kAdded = 1,
};
所以具体逻辑为:
- 如果状态为
kNew,调用UpdateEpollEvent(EPOLL_CTL_ADD, ch),状态置为KAdded - 否则表示当前有新事件注册或旧事件注销,调用
UpdateEpollEvent(EPOLL_MOD, ch)
PS: 当前所有事件都不关心了应不应该调用
UpdateEpollEvent(EPOLL_CTL_DEL, ch)?
这么做的好处是:
- 在之后没有新事件注册时,可以节省一次系统调用
- 带来了心理负担,得考虑先
EnableXXX()再DisableXXX(),不然会引发下面提到的坏处。坏处是:
- 可能之后还会注册新事件,先注销再注册,红黑树需要进行旋转和重染色。
- 不符合
UpdateChannel()的语义,与RemoveChannel()语义重复
由于弊大于利,故即使当前没有感兴趣的事件,也不注销内核事件表中的事件
1 | void Epoller::UpdateChannel(Channel* ch) { |
RemoveChannel
删除API比较简单,因为只需要针对KAdded的Channel进行删除罢了。1
2
3
4
5
6
7
8
9
10
11
12
13void Epoller::RemoveChannel(Channel* ch) {
AssertInThread();
int fd = ch->GetFd();
auto index = ch->GetIndex();
if (index == kAdded) {
UpdateEpollEvent(EPOLL_CTL_DEL, ch);
// set status to kNew, then we can add it again
ch->SetIndex(kNew);
}
}
Poll
Poll说白了就是epoll_wait()的wrapper,只不过需要考虑转换为Channel,与库兼容。
前面也说到了需要struct epoll_data[]去接受就绪事件,用std::vector表示可以通过resize()来进行动态扩展,如果返回的就绪事件数与容器大小相等,那么进行扩容,因为下次可能会有更多的事件就绪,并且该容器不考虑主动缩容,避免导致多余的扩容->缩容->扩容->缩容->…。
顺便一提,初始大小是16。
1 | TimeStamp Epoller::Poll(int ms, ChannelVec& active_channels) { |
ET模式的支持
kanon是比较粗糙地在UpdateChannel()中给关注事件加上EPOLL_ET从而启动ET模式。
1 | /** |
EventLoop的相关API是转发给Epoller的







