epoll源码分析(全)

linux-2.6.24.3源代码

当系统启动时,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的调用顺序为:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值