当系统启动时,epoll进行初始化:
-
static int __init eventpoll_init(void)
-
{
-
mutex_init(&pmutex);
-
ep_poll_safewake_init(&psw);
-
epi_cache = kmem_cache_create(“eventpoll_epi”,
-
sizeof(struct epitem),
-
0,
-
SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,
-
NULL);
-
pwq_cache = kmem_cache_create(“eventpoll_pwq”,
-
sizeof(struct eppoll_entry),
-
0,
-
EPI_SLAB_DEBUG|SLAB_PANIC,
-
NULL);
-
-
return 0;
-
}
上面的代码实现一些数据结构的初始化,通过fs/eventpoll.c中的注释可以看出,有三种类型的锁机制使用场景:
1.epmutex(mutex):用户关闭文件描述符,但是没有调用EPOLL_CTL_DEL
2.ep->mtx(mutex):用户态与内核态的转换可能会睡眠
3.ep->lock(spinlock):内核态与具体设备中断过程中的转换,poll回调
接下来就是使用slab分配器动态分配内存,第一个结构为当系统中添加一个fd时,就创建一epitem结构体,内核管理的基本数据结构:
-
struct epitem
-
{
-
struct rb_node rbn; //用于主结构管理的红黑树
-
struct list_head rdllink; //事件就绪队列
-
struct epitem *next; //用于主结构体中的链表
-
struct epoll_filefd ffd; //每个fd生成的一个结构
-
int nwait; //
-
struct list_head pwqlist; //poll等待队列
-
struct eventpoll *ep; //该项属于哪个主结构体
-
struct list_head fllink; //链接fd对应的file链表
-
struct epoll_event event; //注册的感兴趣的事件,也就是用户空间的epoll_event
-
}
而每个epoll
fd对应的主要数据结构为:
-
struct eventpoll {
-
spin_lock_t lock; //对本数据结构的访问
-
struct mutex mtx; //防止使用时被删除
-
wait_queue_head_t wq; //sys_epoll_wait() 使用的等待队列
-
wait_queue_head_t poll_wait;//file->poll()使用的等待队列
-
struct list_head rdllist; //事件满足条件的链表
-
struct rb_root rbr; //用于管理所有fd的红黑树
-
struct epitem *ovflist; //将事件到达的fd进行链接起来发送至用户空间
-
}
该结构主要在epoll_create时进行创建:
-
//原来使用的是hash表,所以有size,现在改为红黑树,故不使用.
-
long sys_epoll_create(int size)
-
{
-
int error,fd = -1;
-
struct eventpoll *ep;
-
struct inode *inode;
-
struct file *file;
-
-
….
-
error = -EINVAL;
-
//分配空间
-
if(size <= 0 || (error = ep_alloc(&ep)!=0))
-
goto errror_return;
-
//创建一个struct file结构,由于没有任何文件系统,为匿名文件,
-
//并将主结构体放入file->private项中进行保存
-
error = anon_inode_getfd(&fd,&inode,&file,”[eventpoll]”,
-
&eventpoll_fops,ep);
-
if(error)
-
goto error_free;
-
return fd;
-
...
-
}
上面注册的操作eventpoll_fops定义如下:
-
static const struct file_operations eventpoll_fops = {
-
.release = ep_eventpoll_release;
-
.poll = ep_eventpoll_poll,
-
}
这样说来,内核中维护了一棵红黑树,大致的结构如下:
上面的原型是epoll的fd所维护的主结构,下面是每一个具体的fd结构.
以后每一个fd加入到epoll中,就会创建一个struct
epitem结构,并插入至红黑树中。
接着是epoll_ctl函数原型:
-
asmlinkage long sys_epoll_ctl(int epfd,int op,int fd,struct epoll_event __user *event)
-
{
-
int error;
-
struct file *file,*tfile;
-
struct eventpoll *ep;
-
struct epoll_event epds;
-
-
error = -FAULT;
-
//判断行参的合法性
-
if(ep_op_has_event(op) && copy_from_user(&epds,event,sizeof(struct epoll_event)))
-
goto error_return;
-
error = -EBADF;
-
file = fget (epfd);
-
if(!file) goto error_return;
-
-
tfile = fget(fd);
-
if(!tfile) goto error_fput;
-
-
error = -EPERM;
-
//不能没有poll驱动
-
if(!tfile->f_op || !tfile->f_op->poll)
-
goto error_tgt_fput;
-
-
error =-EINVAL;
-
//防止自己监听自己
-
if(file == tfile || !is_file_poll(file))
-
goto error_tgt_fput;
-
//在create时存入进去的,现在将其拿出来
-
ep = file->private->data;
-
-
mutex_lock(&ep->mtx);
-
//防止重复添加
-
epi = epi_find(ep,tfile,fd);
-
error = -EINVAL;
-
-
switch(op)
-
{
-
….....
-
case EPOLL_CTL_ADD:
-
if(!epi)
-
{
-
epds.events |=EPOLLERR | POLLHUP;
-
error = ep_insert(ep,&epds,tfile,fd);
-
} else
-
error = -EEXIST;
-
break;
-
…....
-
}
-
return error;
-
}
下面就是插入代码:
-
static int ep_insert(struct eventpoll *ep,struct epoll_event *event,
-
struct file *tfile,int fd)
-
{
-
int error ,revents,pwake = 0;
-
unsigned long flags ;
-
struct epitem *epi;
-
/*
-
struct ep_queue{
-
poll_table pt;
-
struct epitem *epi;
-
}
-
*/
-
struct ep_pqueue epq;
-
-
//分配一个epitem结构体来保存每个加入的fd
-
error = -ENOMEM;
-
if(!(epi = kmem_cache_alloc(epi_cache,GFP_KERNEL)))
-
goto error_return;
-
//初始化该结构体
-
ep_rb_initnode(&epi->rbn);
-
INIT_LIST_HEAD(&epi->rdllink);
-
INIT_LIST_HEAD(&epi->fllink);
-
INIT_LIST_HEAD(&epi->pwqlist);
-
epi->ep = ep;
-
ep_set_ffd(&epi->ffd,tfile,fd);
-
epi->event = *event;
-
epi->nwait = 0;
-
epi->next = EP_UNACTIVE_PTR;
-
-
epq.epi = epi;
-
//安装poll回调函数
-
init_poll_funcptr(&epq.pt,ep_ptable_queue_proc);
-
//调用poll函数来获取当前事件位,其实是利用它来调用注册函数ep_ptable_queue_proc
-
revents = tfile->f_op->poll(tfile,&epq.pt);
-
if(epi->nwait < 0)
-
goto error_unregister;
-
spin_lock(&tfile->f_ep_lock);
-
list_add_tail(&epi->fllink,&tfile->f_ep_lilnks);
-
spin_unlock(&tfile->f_ep_lock);
-
-
ep_rbtree_insert(ep,epi);
-
spin_lock_irqsave(&ep->lock,flags);
-
if((revents & event->events) && !ep_is_linked(&epi->rdllink))
-
{
-
list_add_tail(&epi->rdllink,&ep->rdllist);
-
if(waitqueue_active(&ep->wq))
-
__wake_up_locked(&ep->wq,TAKS_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE);
-
if(waitqueue_active(&ep->poll_wait))
-
pwake++;
-
}
-
-
spin_unlock_irqrestore(&ep->lock,flags);
-
if(pwake)
-
ep_poll_safewake(&psw,&ep->poll_wait);
-
…....
-
return 0;
-
…...
-
-
}
-
//当poll醒来时就回调用该函数
-
static void ep_ptable_queue_proc(struct file *file,wait_queue_head_t *whead,
-
poll_table *pt)
-
{
-
//从注册时的结构中struct ep_pqueue中获取项epi
-
struct epitem *epi = ep_item_from_epqueue(pt);
-
/*//epitem的私有项,通过pwqlist来进行链接
-
*struct eppoll_entry
-
{
-
struct list_head llink;
-
void *base;
-
wait_queue_t wait;
-
wait_queue_head_t *whead;
-
}
-
*/
-
struct eppoll_entry *pwq;//struct epitem的私有项,为每一个fd保存内核poll
-
//为每一个等待的结构分配一项
-
if(epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache,
-
GFP_KERNEL)))
-
{
-
//醒来就调用ep_poll_callback,这里才是真正意义上的poll醒来时的回调函数
-
init_waitqueue_func_entry(&pwq->wait,ep_poll_callback);
-
pwq->whead = whead;
-
pwq->base = epi;
-
//加入到该驱动的等待队列
-
add_wait_queue(whead,&pwq->wait);
-
//将等待链接也放入到epitem链表中去
-
list_add_tail(&pwq->llink,&epi->pwqlist);
-
epi->nwait ++;
-
} else {
-
epi->nwait = -1;
-
}
-
}
-
//当poll监听的事件到达时,就会调用下面的函数
-
static int ep_poll_callback(wait_queue_t *wait,unsigned mode,int sync,void *key)
-
{
-
int pwake = 0;
-
unsigned long flags;
-
struct epitem *epi = ep_item_from_wait(wait);
-
struct eventpoll *ep = epi->ep;
-
-
spin_lock_irqsave(&ep->lock,flags);
-
//判断注册的感兴趣事件
-
//#define EP_PRIVATE_BITS (EPOLLONESHOT | EPOLLET)
-
//有非EPOLLONESHONT或EPOLLET事件
-
if(!(epi->event.events & ~EP_PRIVATE_BITS))
-
goto out_unlock;
-
-
if(unlikely(ep->ovflist != EP_UNACTIVE_PTR))
-
{
-
if(epi->next == EP_UNACTIVE_PTR) {
-
epi->next = ep->ovflist;
-
ep->ovflist = epi;
-
}
-
goto out_unlock;
-
}
-
if(ep_is_linked(&epi->rdllink))
-
goto is_linked;
-
//关键是这一句,将该fd加入到epoll监听的就绪链表中
-
list_add_tail(&epi->rdllink,&ep->rdllist);
-
is_linked:
-
if(waitqueue_active(&ep->wq))
-
__wake_up_locked(&ep->wq,TASK_UNINTERRUPTIBLE
-
| TASK_INTERRUPTIBLE);
-
if(waitqueue_active(&ep->poll_wait))
-
pwake++;
-
out_unlock:
-
spin_unlock_irqrestore(&ep->lock,flags);
-
-
if(pwake)
-
ep_poll_safewake(&psw,&ep->poll_wait);
-
return 1;
-
}
这里采用了两级回调方式,流程如下:
目前为止,整个数据结构就可以描述如下:
epoll_wait系统实现如下:
-
asmlinkage long sys_epoll_wait(int epfd,struct epoll_event __user *events,
-
int maxevents,int timeout)
-
{
-
int error;
-
struct file *file;
-
struct eventpoll *ep;
-
//#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event))
-
//178956970(1.7亿)
-
if(maxevents <=0 || maxevents > EP_MAX_EVETNS)
-
return -EINVAL;
-
//判断返回事件数组是否合法
-
if(!access_ok(VERIFY_WRITE,events,
-
maxevents * sizeof(struct epoll_event)))
-
{
-
error = -EFAULT;
-
goto error_return;
-
}
-
-
error = -EBADF;
-
file = fget(epfd);
-
-
if(!file)
-
goto error_return;
-
error = -EINVAL;
-
if(!is_file_epoll(file))
-
goto error_fput;
-
//将epoll注册时设置的数据结构取出来,开始进行判断
-
ep = file->private_data;
-
error = ep_poll(ep,events,maxevents,timeout);
-
….......
-
}
现在又转入了ep_poll函数中:
-
static int ep_poll(struct eventpoll *ep,struct epoll_event __user *events,
-
int maxevents,long timeout)
-
{
-
int res,avail;
-
unsigned long flags;
-
long jtimeout;
-
wait_queue_t wait;
-
-
//注册的0ms按0.999 Jiffies处理,并非真正的0s,HZ=100,
-
//jiffies/HZ 为s
-
jtimeout = (timeout<0 || timeout >= EP_MAX_MSTIMEO)?
-
MAX_SCHEDULE_TIMEOUT:(timeout*HZ+999)/1000;
-
-
retry:
-
spin_lock_irqsave(&ep->lock,flags);
-
-
res = 0;
-
//事件就绪队列为空,就监听poll
-
if(list_empty(&ep->rdllist))
-
{
-
//让当前进程挂在等待队列wait上,并将该等待队列加入到ep->wq(epoll_wait的 专属队列中),
-
init_waitqueue_entry(&wait,current);
-
wait.flags |= WQ_FLAG_EXCLUSIVE;
-
__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;
-
}
-
spin_unlock_irqrestore(&ep->lock,flags);
-
//进程进入睡眠状态直到规定的睡眠事件醒来或者
-
注册的fd对应的poll驱动函数唤醒该 进程
-
jtimeout = schedule_timeout(jtimeout);
-
spin_lock_irqrestore(&ep->lock,flags);
-
}
-
//poll驱动唤醒了该进程,现在就将对应的poll从等待队列中清除出去,并设置为运行状态
-
__remove_wait_queue(&ep->wq,&wait);
-
set_current_state(TASK_RUNNING);
-
}
-
eavail = !list_empty(&ep->rdllist);
-
spin_unlock_irqrestore(&ep->lock,flags);
-
//没有被中断,有就绪事件,并且向用户空间发送成功,就返回
-
if(!res && eavail && !(res = ep_send_events(ep,events,maxevents))
-
&&jtimeout)
-
goto retry;
-
-
return res;
-
}
ep_send_events函数向用户空间发送就绪事件:
-
static int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events,int maxevents)
-
{
-
int eventcnt,error = -EFAULT,pwake = 0;
-
unsigned int revents;
-
unsigned long flags;
-
struct epitem *epi,*nepi;
-
struct list_head txlist;
-
-
INIT_LIST_HEAD(&txlist);
-
mutex_lock(&ep->mtx);
-
-
spin_lock_irqsave(&ep->lock,flags);
-
//将ep->rdllist链表加入到txlist链表中去,这样的话rdllist链表就为空了
-
list_splice(&ep->rdllist,&txlist);
-
INIT_LIST_HEAD(&ep->rdllist);
-
ep->ovflist = NULL;
-
spin_unlock_irqrestore(&ep->lock,flags);
-
//将rdllist链表中的每一项都发送至用户空间
-
for(eventcnt = 0; !list_empty(&txlist) && eventcnt < maxevents;) {
-
-
epi = list_first_entry(&txlist,struct epitem,rdllink);
-
list_del_init(&epi->rdllink);
-
//立刻返回当前文件的就绪事件
-
revents = epi->ffd.file->f_op->poll(epi->ffd.file,NULL);
-
revents &= epi->event.events;
-
-
if(revents) {
-
//将就绪事件的poll_event发送至用户空间
-
if(__put_user(revents,&events[eventcnt.].events) ||
-
__put_user(epi->event.data,&events[eventcnt].data))
-
-
goto errxit;
-
//#define EP_PRIVATE_BITS (EPOLLONESHOT | EPOLLET)
-
if(epi->event.events & EPOLLONESHOT)
-
epi->event.events &= EP_PRIVATE_BITS;
-
eventcnt++;
-
}
-
//非边缘触发,且事件就绪时,就将epi->rdllink加入到rdllist链表中,实际上就是将没有标记为ET模式的fd又放回到rdllist中,这样下次就绪时又能将其发送至用户空间了
-
if(!(epi->event.events & EPOLLET) && (revents &
-
epi->event.events))
-
list_add_tail(&epi->rdllink,&ep->rdllist);
-
}
-
error = 0;
-
errixt:
-
spin_lock_irqsave(&ep->lock,flags);
-
//在执行上面的代码期间,又有可能有就绪事件,这样的话就进入了ovflist队列,这样有需要再一次确认一次
-
for(nepi = ep->ovflist;(epi = nepi)!= NULL;
-
nepi = epi->next;epi->next = EP_UNACTIVE_PTR) {
-
//链表为空且没有ET事件发生,#define EP_PRIVATE_BITS (EPOLLONESHOT | EPOLLET),这里也和上面的一样
-
if(!ep_is_linked(&epi->rdllink) && (epi->event.events &
-
~EP_PRIVATE_BITS))
-
//又将rdllink其加入到rdllist中
-
list_add_tail(&epi->rdllink,&ep->rdllist);
-
}
-
//#define EP_UNACTIVE_PTR ((void*) -1L)
-
ep->ovflist = EP_UNACTIVE_PTR;
-
list_spice(&txlist,&ep->rdllist);//现在又将txlist链表加入到rdllist链表中去
-
if(!list_empty(&ep->rdllist))
-
{
-
//等待的队列不为空
-
if(waitqueue_active(&ep->wq))
-
-
__wake_up_locked(&ep->wq,TASK_UNINTERRUPTIBLE |
-
TASK_INTERRUPTIBLE);
-
//如果poll队列不为空,则唤醒的次数加1
-
if(waitqueue_active(&ep->poll_wait))
-
pwake++;
-
}
-
spin_unlock_irqrestore(&ep->lock,flags);
-
mutex_unlock(&ep->mtx);
-
if(pwake)
-
ep_poll_safewake(&psw,&ep->poll_wait);
-
return eventcnt == 0?error:eventcnt;
-
}
这样epoll_wait的调用顺序为:
参考资料:
linux-2.6.24.3源代码
阅读(614) | 评论(0) | 转发(0) |