epoll是如何监控多个描述符及如何获得通知(1)

简介: 作者:gfree.wind@gmail.com博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net  微博:weibo.com/glinuxer QQ技术群:4367710   本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
微博:weibo.com/glinuxer
QQ技术群:4367710
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。

============================================================================================================================
有了宝宝以后,最近事情比较多,博客更新得远不如以前了。

前几天和人聊到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函数,这个函数用于插入新的监控描述符。

  1.     /* Initialize the poll table using the queue callback */
  2.     epq.epi = epi;
  3.     init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

  4.     /*
  5.      * Attach the item to the poll hooks and get current event bits.
  6.      * We can safely use the file* here because its usage count has
  7.      * been increased by the caller of this function. Note that after
  8.      * this operation completes, the poll callback can start hitting
  9.      * the new item.
  10.      */
  11.     revents = tfile->f_op->poll(tfile, &epq.pt);
这几行代码是关键的代码。这里的epq像一个粘合剂,把epoll和内核本身的poll机制黏在了一起。epq.epi = epi,这个epi对应了epoll一个监控描述符对象在epoll中的实例,然后init_poll_funcptr设置了epq的poll table的回调函数,完成了epq的初始化。然后调用该文件描述符对应的poll实现函数。

这里就要跳转到具体的poll函数了,以socket文件描述符为例,当该socket为UDP时候,对应的poll实现函数为udp_poll。

  1. unsigned int udp_poll(struct file *file, struct socket *sock, poll_table *wait)
  2. {
  3.     unsigned int mask = datagram_poll(file, sock, wait);
  4.     struct sock *sk = sock->sk;

  5.     /* Check for false positives due to checksum errors */
  6.     if ((mask & POLLRDNORM) && !(file->f_flags & O_NONBLOCK) &&
  7.      !(sk->sk_shutdown & RCV_SHUTDOWN) && !first_packet_length(sk))
  8.         mask &= ~(POLLIN | POLLRDNORM);

  9.     return mask;

  10. }
这个代码很简单。关键函数是datagram_poll

  1. unsigned int datagram_poll(struct file *file, struct socket *sock,
  2.              poll_table *wait)
  3. {
  4.     struct sock *sk = sock->sk;
  5.     unsigned int mask;

  6.     sock_poll_wait(file, sk_sleep(sk), wait);
  7.     mask = 0;
  8.     
  9.     //处理事件
  10.     ...... ......

  1.     return mask;
  2. }
sk_sleep(sk)就是sk上的wait队列。那么就需要进入sock_poll_wait

  1. static inline void sock_poll_wait(struct file *filp,
  2.         wait_queue_head_t *wait_address, poll_table *p)
  3. {
  4.     if (p && wait_address) {
  5.         poll_wait(filp, wait_address, p);
  6.         /*
  7.          * We need to be sure we are in sync with the
  8.          * socket flags modification.
  9.          *
  10.          * This memory barrier is paired in the wq_has_sleeper.
  11.         */
  12.         smp_mb();
  13.     }
  14. }
  1. static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
  2. {
  3.     if (p && wait_address)
  4.         p->qproc(filp, wait_address, p);
  5. }
看到这里,绕了一圈,又回到了起点。。。需要查看ep_ptable_queue_proc的实现
  1. static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
  2.                  poll_table *pt)
  3. {
  4.     struct epitem *epi = ep_item_from_epqueue(pt);
  5.     struct eppoll_entry *pwq;

  6.     if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
  7.         init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
  8.         pwq->whead = whead;
  9.         pwq->base = epi;
  10.         add_wait_queue(whead, &pwq->wait);
  11.         list_add_tail(&pwq->llink, &epi->pwqlist);
  12.         epi->nwait++;
  13.     } else {
  14.         /* We have to signal that an error occurred */
  15.         epi->nwait = -1;
  16.     }
  17. }
这里的代码很明确,获得epoll中的监控描述符的实例epi,然后创建一个epoll的等待节点,并将其放到参数whead的队列中。对应上面的例子中,即sock的等待队列中。这样,就将epoll和具体对应的描述联系起来。当对应的描述符执行唤醒操作时,就会利用上面的关联,唤醒epoll。

本想一篇博文就把这个流程说清楚的。结果没有成功,留到明天吧
(未完待续)



相关文章
|
4月前
|
API
epoll监听信号事件-signalfd
epoll监听信号事件-signalfd
27 0
|
4月前
epoll分析
epoll分析
|
监控
驱动开发:内核监控进程与线程回调
在前面的文章中`LyShark`一直在重复的实现对系统底层模块的枚举,今天我们将展开一个新的话题,内核监控,我们以`监控进程线程`创建为例,在`Win10`系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现,此类函数的原理是创建一个回调事件,当有进程或线程被创建或者注销时,系统会通过回调机制将该进程相关信息优先返回给我们自己的函数待处理结束后再转向系统层。
332 0
驱动开发:内核监控进程与线程回调
|
监控
驱动开发:内核注册并监控对象回调
在笔者上一篇文章`《驱动开发:内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止的目的。
405 0
驱动开发:内核注册并监控对象回调
|
消息中间件 测试技术 API
FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)
我们在前面单独介绍过FreeRTOS的任务通知和消息队列, 但是在FreeRTOS中任务间的通讯还有信号量,邮箱,事件组标志等可以使用 这篇文章就这些成员与消息队列和任务通知的关系进行说明分析
808 0
FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)
FreeRTOS事件组之事件组的创建(xEventGroupCreate())
FreeRTOS事件组之事件组的创建(xEventGroupCreate())
402 0
基于epoll封装的事件回调miniserver
epoll技术前两节已经阐述过了,目前主要做一下封装,很多epoll的服务器都是采用事件回调方式处理, 其实并没有什么复杂的,我慢慢给大家阐述下原理。 在networking.h和networking.cpp里,这两个文件主要实现了一些文件读写功能的回调函数 。
897 0
彻底学会使用epoll(一)——ET模式实现分析
注:之前写过两篇关于epoll实现的文章,但是感觉懂得了实现原理并不一定会使用,所以又决定写这一系列文章,希望能够对epoll有比较清楚的认识。是请大家转载务必注明出处,算是对我劳动成果的一点点尊重吧。
1754 0
彻底学会使用epoll(五)—— ET模式下的注意事项
彻底学会epoll(五)—— ET模式下的注意事项 ——lvyilong316 5.1 ET模式下的读写     经过前面几节分析,我们可以知道,当epoll工作在ET模式下时,对于读操作,如果read一次没有读尽buffer中的数据,那么下次将得不到读就绪的通知,造成buffer中已有的数据无机会读出,除非有新的数据再次到达。
1436 1
彻底学会使用epoll(四)——ET的写操作实例分析
首先,看程序四的例子。 l 程序四 点击(此处)折叠或打开 #include unistd.
1058 0