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;
}