epoll源码剖析
程序员文章站
2022-06-14 11:22:27
...
epoll多路实现模型相比selecte、poll避免了出现文件描述符越多性能越差的情况,并且省去了大量内核/用户空间的拷贝和轮询所有文件描述符的系统消耗。为了理解epoll实现高并发,本文从源码了解epoll的实现机制。
epoll的用法:
int epoll_create(int size);//创建一个epoll fd
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);//添加事件
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);//监控文件描述符
epoll_create()
创建一系列数据结构并建立的联系。
int fd;//文件描述符
struct inode* inode;//内核用该结构在内部表示一个文件
struct file* file;//代表一个打开的文件
struct eventpoll;//epoll的描述符
源码摘录
sys_epoll_create(int size);
>> int error, fd;
struct inode *inode;
struct file *file;
error = ep_getfd(&fd, &inode, &file);
>> file = get_empty_filp();//分配一个文件索引节点并初始化
inode = ep_eventpoll_inode();//从eventpoll文件系统分配一个inode
dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);
d_add(dentry, inode);
>> d_instantiate(entry, inode);
>> list_add(&entry->d_alias, &inode->i_dentry);//file.inode指向inode
fd_install(fd, file);
>> files->fd[fd] = file;//将文件索引点添加到进程中
error = ep_file_init(file);//设置文件内部数据结构(“struct eventpoll”)
>> memset(ep, 0, sizeof(*ep));//初始化epoll描述符
rwlock_init(&ep->lock);
init_rwsem(&ep->sem);
init_waitqueue_head(&ep->wq);
init_waitqueue_head(&ep->poll_wait);
INIT_LIST_HEAD(&ep->rdllist);
ep->rbr = RB_ROOT;
file->private_data = ep;//file.private_data指向epoll 描述符
return fd;//epfd
创建struct file、strcut inode 、struct eventpoll等结构体并初始化,建立file与inode的联系,task_struct与file之间的联系,file与eventpolld之间的联系。
epoll_ctl()
插入、删除、更改epoll描述符中的内核文件描述符,下面以插入为例:
源码摘录:
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
>> int error;
struct file *file, *tfile;
struct eventpoll *ep;
struct epitem *epi;//当前节点
file = fget(epfd);//根据进程文件描述符获得文件对象的地址。并增加其引用计数。
tfile = fget(fd);
ep = file->private_data;
epi = ep_find(ep, tfile, fd);//在eventpoll.rbr中查找fd
ep_insert(ep, &epds, tfile, fd);//给fd注册事件
>> struct epitem *epi;
struct ep_pqueue epq;
/*struct ep_pqueue {
poll_table pt;//poll_queue_proc qproc;
struct epitem *epi;
};
*/
epi->ep = ep;
epi->event = *event;
epq.epi = epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
>> pt->qproc = qproc;
/*
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) //监听的fd生改变时,指定的调用函数会被调用
unsigned int (*poll) (struct file *, struct poll_table_struct *);
*/
revents = tfile->f_op->poll(tfile, &epq.pt);//回调函数
ep_rbtree_insert(ep, epi);//添加当前项到eventpoll.rbr
/* If the file is already "ready" we drop it inside the ready list */
if ((revents & event->events) && !EP_IS_LINKED(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);//如果当前事件就绪,把他放到就绪队列
/* Notify waiting tasks that events are available */
if (waitqueue_active(&ep->wq))
wake_up(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
将用户空间的epoll_event拷贝到内核,只拷贝一次;
在eventpoll->rbr中查找fd是否存在;
设置回调函数并且插入节点。
epoll_wait()
向用户空间拷贝就绪描述符。
源码摘录:
sys_epoll_wait()
struct file *file;
struct eventpoll *ep;
file = fget(epfd);
ep = file->private_data;
ep_epoll(ep,events,maxevents,timeout);
>> if (list_empty(&ep->rdllist)) {//如果就绪队列为空
init_waitqueue_entry(&wait, current);//初始化等待队列
add_wait_queue(&ep->wq, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (!list_empty(&ep->rdllist) || !jtimeout)//如果超时
break;
if (signal_pending(current)) {//如果有信号产生
res = -EINTR;
break;
}
write_unlock_irqrestore(&ep->lock, flags);
jtimeout = schedule_timeout(jtimeout);//如果此时回调函数被调用,直接被唤醒
write_lock_irqsave(&ep->lock, flags);
}
>> if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {//收集就绪的描述符
eventcnt = ep_send_events(ep, &txlist, events);
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
epi->revents = revents & epi->event.events;//只返回相关的fd
if (!res && eavail &&!(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)//向用户空间拷贝
如果是LT,无论是否状态发生改变都会重新插入就绪队列,下次调用返回值为0。
参考资料:
下一篇: 韭菜海带会相克吗?