epoll源码分析(三)

epoll源码分析(三)


epoll_wait函数

SYSCALL_DEFINE4(epoll_wait,…)

  1. 判断最大值合法性
  2. 获取匿名文件的文件指针, 取得文件信息
  3. 调用ep_poll阻塞自己, 等待有消息的到来
// epoll_wait()函数的调用
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *,events,
 int, maxevents, int, timeout)
{
    int error;
    struct file *file;
    struct eventpoll *ep;
    ...
    // 判断最大值没有超过能承受的范围   
    if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
        return -EINVAL;
    // 获取epoll_create()函数创建的匿名文件的文件描述符
    file = fget(epfd);
    if (!file)
        goto error_return;
    ...

    error = -EINVAL;
    // 判断类型是不是epoll类型
    if (!is_file_epoll(file))
        goto error_fput;
    // 获取文件中保存的信息
    ep = file->private_data;
    // 等待就绪队列不为空, 也就是等待有要接受的消息到来
    error = ep_poll(ep, events, maxevents, timeout);
    ...
}

epoll_wait()中最重要的函数其实是ep_poll, 它会完成等待消息到来, 执行, 没有消息到来就阻塞自己. 可以看出来这是epoll_wait的核心.

ep_poll

  1. 先设置好时间长度, 如果传过来的已经设置好的时间, 就设置为传入 的时间, 没有的话就默认一个初始时间.
  2. 初始化就绪的等待队列
  3. 对有没有进程准备好, 有消息传来或者对时间是否到达进行判断, 如果满足就退出循环, 执行进程.
  4. 如果没有满足就执行进程调度, 让出cpu, 让其他的进程执行, 等待一定的时间后返回该进程继续进行判断, 有准备好的执行, 没有继续让出cpu.
  5. 调用ep_send_evnets将就绪链表中的数据返回给用户空间.
// 设置进程的为可中断, 一直等待有事件的到来. 没哟到来就被调度
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout)
{
    int res, eavail;
    unsigned long flags;
    long jtimeout;
    wait_queue_t wait;
    ...

    // 设置运行的时间节拍
    jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ?
        MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000;

    if (list_empty(&ep->rdllist)) 
    {
        // 初始化 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;
            ...
            // 在时间节拍到达之前, 让出 cpu 调用其他进程, 时间节拍到达时, 重新通过中断回调该进程
            jtimeout = schedule_timeout(jtimeout);
            /*
            // 可用于在阻塞态的进程让出cpu, 一定时间后再回调查看
            extern inline void schedule_timeout(int timeout)
            {
                current->timeout = jiffies + timeout;
                current->state = TASK_INTERRUPTIBLE;
                schedule();
                current->timeout = 0;
            }
            */
            spin_lock_irqsave(&ep->lock, flags);
        }
        __remove_wait_queue(&ep->wq, &wait);

        // 将进程状态设置为运行态
        set_current_state(TASK_RUNNING);
    }
    eavail = !list_empty(&ep->rdllist);
    ...
    // 没有被中断, 有就绪队列, 并向用户空间发送成功, 并调用ep_send_event()返回给用户空间就绪的epoll事件
    if (!res && eavail &&
        !(res = ep_send_events(ep, events, maxevents)) && jtimeout)
        goto retry;

    return res;
}

ep_poll 等待消息到来, 当消息来到后执行ep_send_events函数, 实现将链表从内核态复制到用户态.

ep_send_events

  1. 将就绪队列的链表复制到另一个新的链表中, 并且清空就绪队列.
  2. 将新保存的链表调用__put_uer, 将就绪队列从内核空间复制到内核空间.
  3. 判断是否是下降沿有效
  4. 如果在将队列返回给用户空间的时侯又有消息到来的时侯, 需要重新将就绪消息放入ovflist 中, 同时增加唤醒的次数.
// 向用户空间发送就绪事件队列
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);
    ...
    // 将 rdllist 就绪队列的链表放入到 txlist 链表中, 此时 rdllist 就为空链表了
    list_splice(&ep->rdllist, &txlist);
    INIT_LIST_HEAD(&ep->rdllist);
    // 清空ovflist, 原因是后面执行将链表复制到用户空间的时侯还有消息到来, 就保存在ovflist中
    ep->ovflist = NULL;
    spin_unlock_irqrestore(&ep->lock, flags);

    // 将就绪的队列放入传回到用户空间
    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) {
            if (__put_user(revents, &events[eventcnt].events) ||__put_user(epi->event.data, &events[eventcnt].data))
                goto errxit;
            if (epi->event.events & EPOLLONESHOT) 
                epi->event.events &= EP_PRIVATE_BITS;
            // 唤醒消息的个数
            eventcnt++;
        }
        // 是否是下降沿有效
        if (!(epi->event.events & EPOLLET) && (revents & epi->event.events))
            list_add_tail(&epi->rdllink, &ep->rdllist);
    }
    error = 0;

errxit:
    spin_lock_irqsave(&ep->lock, flags);
    // 在发送给用户空间的时侯又有数据传来时, 将事件放入到 ovflist 队列中. 在将数据加入到就绪队列事件中
    for (nepi = ep->ovflist; (epi = nepi) != NULL; nepi = epi->next, epi->next = EP_UNACTIVE_PTR)
    {
        if (!ep_is_linked(&epi->rdllink) && (epi->event.events & ~EP_PRIVATE_BITS))
            list_add_tail(&epi->rdllink, &ep->rdllist);
    }
    ep->ovflist = EP_UNACTIVE_PTR;
    ... 
        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
        // 如果 poll 队列不为空, 则唤醒的次数++
        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }
    ...
    // 返回累计的值
    return eventcnt == 0 ? error: eventcnt;
}

这就是关于epoll_wait函数的具体实现, 自己描述的可能不是特别的清楚, 但是整体的思路还是写的比较的清楚了. 最后, 再来小小的总结一下epoll_wait的思路.

总结

调用SYSCALL_DEFINE(epoll_wait, …)函数, 获得匿名文件的文件指针, 在通过调用ep_poll函数, 进行时间片的设置, 在时间片结束后就绪队列为空, 就退出等待; 如果时间设置的是负数, ep_poll函数会调用schedule_timeout, 执行进程调度, 当设置的时间片结束后又继续回到ep_poll进程 查看就绪队列是否为空, 为空的话就继续cinching调度, 此时wait又变成阻塞态; 当就绪队列准备好后, 就退出进程调度, 执行ep_send_events函数, 主要是为了将就绪队列的链表从内核空间发送给用户空间.

ep_send_events函数, 先将就绪链表rdllist复制到另一个新的链表, 重新将就绪链表清零, 然后程序调用__put_user将新链表的数据发送给用户空间, 同时, 发送的个数吉拉路下来, 以便函数的返回 , 但是, 如果在发送的时候又有就绪信号到来, 就将来的就绪信号保存在ovflist链表中, 最后又重新数据拷贝给rdllist中, 再重复执行ep_send_events函数.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值