muduo EPollPoller源码学习
一 Channel怎么update到epollfd
EPollPoller::update(),这个方法的层层调用关系:
假设acceptor或者TcpConnection对象刚刚创建,第一次调用了自己的channel成员的enableReading();channel因此更新表示关注事件的events_,然后update();
Channel::update()转而执行EventLoop::updateChannel();
EventLoop::updateChannel()转而执行EpollPoller::updateChannel();
EpollPoller::updateChannel()转而执行EpollPoller::update(int operation, Channel *channel)
在上面的一层层调用中,由于对应的channel刚刚创建没加入poller中,所以首先到了EpollPoller::updateChannel里面会先把channel的fd,指针插入当前poller的channel表里,更新channel的index状态为kAdded,表示已注册,紧接着以EPOLL_CTL_ADD作为operation的实参调用update()。在update里面,既然是enableReading()那么根据channel的events_,终于在这里把EPOLLIN|EPOLLPRI事件,channel的fd,channel指针正式注册进epollfd_。
void EpollPoller::update(int operation, Channel *channel) { epoll_event event; bzero(&event, sizeof event); int fd = channel->fd(); event.events = channel->events(); event.data.fd = fd; event.data.ptr = channel; if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) { if (operation == EPOLL_CTL_DEL) { LOG_ERROR << "epoll_ctl del error:" << errno; } else { LOG_FATAL << "epoll_ctl add/mod error:" << errno; } } }
二 EpollPoller对epoll_wait的封装
muduo的EPollPoller封装了epoll的相关方法,包括epoll的动态扩容,其中poll用于调用epoll_wait(),当事件到来,poll可以通过epoll_wait()更新已发生事件的channel列表activeChannels,这个activeChannels在EPollPoller中被封装为vector<Channel*>的数组。
//EPollPoller::poll()中调用epoll_wait的语句 int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs); //man page中epoll_wait的函数原型 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
&*events_.begin()是传出参数,是epoll_event类型的vector数组,通过数组大小来轮询检查事件
以下为epoll_event在内核中的定义
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ } __EPOLL_PACKED;
可以看到epoll_event是一个结构体,内部的信息记录了感兴趣的事件和发生待处理的事件,这是一个用于传出的参数。当事件来了,epoll_wait()把发生事件的fd和指向对应channel的指针包装进来,而后通过fillActiveChannels方法把上述events_数组的相关信息逐个填入activeChannels,当loop()函数里poll()返回时,所有已发生事件都在activeChannels里,loop()中依次对这些事件调用相关回调函数就好了。
Timestamp EpollPoller::poll(int timeoutMs, ChannelList *activeChannels) { LOG_DEBUG << "func=" << __FUNCTION__ << "=> fd total count:" << channels_.size(); int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs); int saveErrno = errno; Timestamp now(Timestamp::now()); if (numEvents > 0) { LOG_DEBUG << "events happened" << numEvents; fillActiveChannels(numEvents, activeChannels); if (numEvents == events_.size()) { events_.resize(events_.size() * 2); // events_做成vector,所以可以动态扩容 } } else if (numEvents == 0) { LOG_DEBUG << "func=" << __FUNCTION__ << "timeout!"; } else { if (saveErrno != EINTR) { errno = saveErrno; LOG_ERROR << "EPollPoller::poll() error:" << errno; } } return now; }