select/poll/epoll分析:区别与联系

IO模型中一个重要的多路处理模型。

背景:

程序需要处理多路IO时,靠阻塞的同步IO或者非阻塞的轮询都不是太好的选择。

因为阻塞IO只能处理单路IO比较有效,而非阻塞的轮询无论是否有IO到来都会形成开销。

因此需要一种事件推动的模型,能对多路IO的就绪状态进行监听。类型于硬件中断驱动机制。

select/poll/epoll便于用于这个目的。

比较:

 特点问题点
select用数组的方式指定监听的多路IO有最大监听数量的限制,最大1024
poll用链表来指定监听的多路IO

1.解决了select监听数量限制

2.用户需要遍历所有的IO,才能找到就绪的IO,开销是O(n).

3.每次拷贝要监听的IO数据开销。

4.内核也要遍历所有的IO,才能找到就绪的IO,开销是O(n)

epoll监听IO集合单独指定,返回就绪的IO集合

1.去掉了不必要的多次拷贝要监听的IO数据开销。

2.解决了遍历所有的IO的0(n)开销。有就绪的IO就单独通过回调函数把自己加入就绪IO集合中。

从上表看起来,一个比一个要好。epoll似乎是最优美的,没有任何冗余的操作与不必要的限制。

具体内核代码分析:

fs/select.c
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
...

for (;;) {

...
    for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {  遍历所有的IO
    if (f_op && f_op->poll) {
                        wait_key_set(wait, in, out,
                                 bit, busy_flag);
                        mask = (*f_op->poll)(f.file, wait);
                    }    
    }
    if (retval || timed_out || signal_pending(current))
            break;
        if (table.error) {
            retval = table.error;
            break;
        }
    if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
                       to, slack))
            timed_out = 1;

}

}

static int do_poll(unsigned int nfds,  struct poll_list *list,
           struct poll_wqueues *wait, struct timespec *end_time)
{

...

for (;;) {
...        for (walk = list; walk != NULL; walk = walk->next) { 遍历所有的IO
          struct pollfd * pfd, * pfd_end;

            pfd = walk->entries;
            pfd_end = pfd + walk->len;
            for (; pfd != pfd_end; pfd++) {            
                if (do_pollfd(pfd, pt, &can_busy_loop,
                          busy_flag)) {
                    count++;
                    pt->_qproc = NULL;
                    /* found something, stop busy polling */
                    busy_flag = 0;
                    can_busy_loop = false;
                }
            }
        }
      

pt->_qproc = NULL;
        if (!count) {
            count = wait->error;
            if (signal_pending(current))
                count = -EINTR;
        }
        if (count || timed_out)
            break;

        /* only if found POLL_BUSY_LOOP sockets && not out of time */
        if (can_busy_loop && !need_resched()) {
            if (!busy_end) {
                busy_end = busy_loop_end_time();
                continue;
            }
            if (!busy_loop_timeout(busy_end))
                continue;
        }
        busy_flag = 0;       
        if (end_time && !to) {
            expire = timespec_to_ktime(*end_time);
            to = &expire;
        }

        if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
            timed_out = 1;
    }

}

fs/eventpoll.c

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
           int maxevents, long timeout)

{

fetch_events:
    spin_lock_irqsave(&ep->lock, flags);

    if (!ep_events_available(ep)) { //判断是否有就绪IO

      for (;;) {
   
            set_current_state(TASK_INTERRUPTIBLE);
            if (ep_events_available(ep) || timed_out) //判断是否有就绪IO
                break;
            if (signal_pending(current)) {
                res = -EINTR;
                break;
            }

            spin_unlock_irqrestore(&ep->lock, flags);
            if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
                timed_out = 1;

            spin_lock_irqsave(&ep->lock, flags);
        }

}

static inline int ep_events_available(struct eventpoll *ep)
{
    return !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR; //判断是否有就绪IO,是看rdlist是否为空或者有异常
}

在add 新的epoll时会将wakeup的默认处理回调设置为自定义的ep_poll_callback

static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
                 poll_table *pt)
{
    struct epitem *epi = ep_item_from_epqueue(pt);
    struct eppoll_entry *pwq;

    if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
        init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
        pwq->whead = whead;
        pwq->base = epi;
        if (epi->event.events & EPOLLEXCLUSIVE)
            add_wait_queue_exclusive(whead, &pwq->wait);
        else
            add_wait_queue(whead, &pwq->wait);
        list_add_tail(&pwq->llink, &epi->pwqlist);
        epi->nwait++;
    } else {
        /* We have to signal that an error occurred */
        epi->nwait = -1;
    }
}

接下来,肯定不会猜错,会在回调中把就绪IO加入rdlist.

static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
...

    /* If this file is already in the ready list we exit soon */
    if (!ep_is_linked(&epi->rdllink)) {
        list_add_tail(&epi->rdllink, &ep->rdllist);
        ep_pm_stay_awake_rcu(epi);
    }

...

}

select/poll几乎是一样的,只是接口方式有些不一样,运行原理上相同,而epoll在运行原理上是不同的。

关于驱动:

驱动实现就比较简单了,因为它只是框架的一部分,就是把调用  poll_wait()把自己放入队列,并返回事件信息,然后在事情发生时调用wakeup().

样例:

static unsigned int bt_bmc_poll(struct file *file, poll_table *wait)
{
    struct bt_bmc *bt_bmc = file_bt_bmc(file);
    unsigned int mask = 0;
    u8 ctrl;

    poll_wait(file, &bt_bmc->queue, wait);

    ctrl = bt_inb(bt_bmc, BT_CTRL);

    if (ctrl & BT_CTRL_H2B_ATN)
        mask |= POLLIN;

    if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))
        mask |= POLLOUT;

    return mask;
}

static irqreturn_t bt_bmc_irq(int irq, void *arg)
{
    struct bt_bmc *bt_bmc = arg;
    u32 reg;

    reg = ioread32(bt_bmc->base + BT_CR2);
    reg &= BT_CR2_IRQ_H2B | BT_CR2_IRQ_HBUSY;
    if (!reg)
        return IRQ_NONE;

    /* ack pending IRQs */
    iowrite32(reg, bt_bmc->base + BT_CR2);

    wake_up(&bt_bmc->queue);
    return IRQ_HANDLED;
}

 

总结:

1.所以说epoll真的是event事件驱动,O(1)的效率。select/poll是遍历找到事件在哪里。

2. 但是此处在性能上又有类似中断与polling的特点。在大量事件产生时,interrupt的处理流程开销必然不变,同时对表的操作要加lock机制上又有开销,造成单个事件处理开销并要比select/poll要高。在数量起来后,反而在性能上epoll不一定比select/poll好。
因此:看情况来选择,而不是epoll万能适用。对于少量的多路IO其实都还是可以的,不用太纠结哪个一定好。对于大数量的多路,而事件不太多的情况倒是最适用的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值