博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
============================================================================================================================
有了宝宝以后,最近事情比较多,博客更新得远不如以前了。
前几天和人聊到epoll,我本身不是做应用的,对epoll的了解也仅限于几篇博文。所以本文关于epoll的描述,准确度值得怀疑哦~欢迎大家指正。
我们起初聊的是TCP/IP协议栈的自下而上的流程。当我说到内核在选择了正确的socket以后,会唤醒在这个socket上等待的进程,通知他们有新数据包来了。这时,该朋友说到,这是同步模式,那epoll是如何实现的呢?这里先插一句,我认为该朋友的说法有问题。对于前者,可以说是同步模式,其实我觉得他更想强调的是阻塞模式。而无论是将其说成阻塞模式,还是同步模式,epoll都不是相反的。聊这个问题的时候,我当时对epoll的太少了,也就没有对epoll的实现发表什么看法。只是简单的聊了聊epoll的一些特点,以及与select的对比。
这几天找了点空余时间,带着这个问题,看了一些epoll的资料。
关于epoll本身的文章和资料已经有了很多,大多数都是关于epoll的应用,或者是将其与poll,select进行对比,也有部分是分析epoll的源码的,但是都是分析epoll的API在内核的实现。对于我这个简单的问题,网上貌似没有直接的答案。这两天,看了看代码,大致明白了epoll如何同时监控多个描述符及如何获得通知。
1. 无论是select还是epoll,都是基于poll的机制实现的。而poll是VFS要求的一个成员函数,每个具体文件系统的实现,都有对应的poll实现(socket也是一个虚拟的文件系统)。
2. 无论是select还是epoll,其实仍然是阻塞模式。只不过select和epoll在阻塞调用中,可以监控多个文件描述符,还可以设置一个超时。
select的实现代码相对于epoll,要简单很多,是以轮询的方式查询各个描述符。下面看看epoll是如何做到的?
首先看ep_insert函数,这个函数用于插入新的监控描述符。
-
/* Initialize the poll table using the queue callback */
-
epq.epi = epi;
-
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
-
-
/*
-
* Attach the item to the poll hooks and get current event bits.
-
* We can safely use the file* here because its usage count has
-
* been increased by the caller of this function. Note that after
-
* this operation completes, the poll callback can start hitting
-
* the new item.
-
*/
- revents = tfile->f_op->poll(tfile, &epq.pt);
这里就要跳转到具体的poll函数了,以socket文件描述符为例,当该socket为UDP时候,对应的poll实现函数为udp_poll。
-
unsigned int udp_poll(struct file *file, struct socket *sock, poll_table *wait)
-
{
-
unsigned int mask = datagram_poll(file, sock, wait);
-
struct sock *sk = sock->sk;
-
-
/* Check for false positives due to checksum errors */
-
if ((mask & POLLRDNORM) && !(file->f_flags & O_NONBLOCK) &&
-
!(sk->sk_shutdown & RCV_SHUTDOWN) && !first_packet_length(sk))
-
mask &= ~(POLLIN | POLLRDNORM);
-
-
return mask;
-
- }
-
unsigned int datagram_poll(struct file *file, struct socket *sock,
-
poll_table *wait)
-
{
-
struct sock *sk = sock->sk;
-
unsigned int mask;
-
-
sock_poll_wait(file, sk_sleep(sk), wait);
-
mask = 0;
- //处理事件
- ...... ......
-
return mask;
- }
-
static inline void sock_poll_wait(struct file *filp,
-
wait_queue_head_t *wait_address, poll_table *p)
-
{
-
if (p && wait_address) {
-
poll_wait(filp, wait_address, p);
-
/*
-
* We need to be sure we are in sync with the
-
* socket flags modification.
-
*
-
* This memory barrier is paired in the wq_has_sleeper.
-
*/
-
smp_mb();
-
}
- }
-
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
-
{
-
if (p && wait_address)
-
p->qproc(filp, wait_address, p);
- }
-
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;
-
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;
-
}
- }
本想一篇博文就把这个流程说清楚的。结果没有成功,留到明天吧
(未完待续)