Linux内核笔记:epoll实现原理(二)
在通过epoll_ctl(2)向epoll中添加被监视文件描述符时,会将ep_poll_callback()作为回调函数添加被监视文件的等待队列中。下面分析ep_poll_callback()函数
1004 static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
1005 {
1006 int pwake = 0;
1007 unsigned long flags;
1008 struct epitem *epi = ep_item_from_wait(wait);
1009 struct eventpoll *ep = epi->ep;
1010 int ewake = 0;
1008行首先调用ep_item_from_wait()来获取到与被监视文件描述符相关联的结构体struct epitem,获取方法就是利用container_of宏。
1009行再根据struct epitem的ep字段获取到代表epoll对象实例的结构体struct eventpoll。
1012         if ((unsigned long)key & POLLFREE) {
1013                 ep_pwq_from_wait(wait)->whead = NULL;
1014                 /*
1015                  * whead = NULL above can race with ep_remove_wait_queue()
1016                  * which can do another remove_wait_queue() after us, so we
1017                  * can't use __remove_wait_queue(). whead->lock is held by
1018                  * the caller.
1019                  */
1020                 list_del_init(&wait->task_list);
1021         }
判断返回的事件掩码里是否设置了标志位POLLFREE(什么时候会设置该标志?),如果是则将当前等待对象从文件描述符的等待队列中删除(疑问:注释是什么意思?为什么不需要加锁?)。
接下来对epoll的实例加锁:
1023 spin_lock_irqsave(&ep->lock, flags);
接下来判断epitem中的事件掩码是不是并没有包括任何poll(2)事件,如果是的话,则解锁后直接返回:
1025 /*
1026 * If the event mask does not contain any poll(2) event, we consider the
1027 * descriptor to be disabled. This condition is likely the effect of the
1028 * EPOLLONESHOT bit that disables the descriptor when an event is received,
1029 * until the next EPOLL_CTL_MOD will be issued.
1030 */
1031 if (!(epi->event.events & ~EP_PRIVATE_BITS))
1032 goto out_unlock;
什么时候会出现上述情况呢?注释里也说了,就是在设置了EPOLLONESHOT标志的时候。对EPOLLONESHOT标志的处理是在epoll_wait()的返回过程,调用ep_send_events_proc()的时候,如果设置了EPOLLONESHOT标志则将EP_PRIVATE_BITS以外的标志位全部清0:
1552 if (epi->event.events & EPOLLONESHOT)
1553 epi->event.events &= EP_PRIVATE_BITS;
接下来判断返回的事件里是否有用户真正感兴趣的事件,没有则解锁后返回,否则继续。
1034 /*
1035 * Check the events coming with the callback. At this stage, not
1036 * every device reports the events in the "key" parameter of the
1037 * callback. We need to be able to handle both cases here, hence the
1038 * test for "key" != NULL before the event match test.
1039 */
1040 if (key && !((unsigned long) key & epi->event.events))
1041 goto out_unlock;
如果此时就绪链表rdllist没有被其他进程访问,则直接将当前文件描述符添加到rdllist链表中,否则的话添加到ovflist链表中。ovflist默认值是EP_UNACTIVE_PTR,epoll_wait()遍历rdllist之前会把ovflist设置为NULL,遍历完再恢复为EP_UNACTIVE_PTR,因此通过判断ovflist的值是不是EP_UNACTIVE_PTR可知此时rdllist是不是正在被访问。
1049         if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
1050                 if (epi->next == EP_UNACTIVE_PTR) {
1051                         epi->next = ep->ovflist;
1052                         ep->ovflist = epi;
1053                         if (epi->ws) {
1054                                 /*
1055                                  * Activate ep->ws since epi->ws may get
1056                                  * deactivated at any time.
1057                                  */
1058                                 __pm_stay_awake(ep->ws);
1059                         }
1060
1061                 }
1062                 goto out_unlock;
1063         }
1064
1065         /* If this file is already in the ready list we exit soon */
1066         if (!ep_is_linked(&epi->rdllink)) {
1067                 list_add_tail(&epi->rdllink, &ep->rdllist);
1068                 ep_pm_stay_awake_rcu(epi);
1069         }
如果是描述符是添加到ovflist链表中,说明此时已经有ep_wait()准备返回了,因此不用再唤醒epoll实例的等待队列,因此1062行直接跳到解锁处;否则的话,则唤醒因为调用epoll_wait()而等待在epoll实例等待队列上的进程(这里最多只会唤醒一个进程):
1075         if (waitqueue_active(&ep->wq)) {
1076                 if ((epi->event.events & EPOLLEXCLUSIVE) &&
1077                                         !((unsigned long)key & POLLFREE)) {
1078                         switch ((unsigned long)key & EPOLLINOUT_BITS) {
1079                         case POLLIN:
1080                                 if (epi->event.events & POLLIN)
1081                                         ewake = 1;
1082                                 break;
1083                         case POLLOUT:
1084                                 if (epi->event.events & POLLOUT)
1085                                         ewake = 1;
1086                                 break;
1087                         case 0:
1088                                 ewake = 1;
1089                                 break;
1090                         }
1091                 }
1092                 wake_up_locked(&ep->wq);
1093         }
如果epoll实例的poll队列非空,也会唤醒等待在poll队列上的进程,不过是在解锁后才会进行唤醒操作。
1094 if (waitqueue_active(&ep->poll_wait))
1095 pwake++;
最后解锁并返回:
1097 out_unlock:
1098 spin_unlock_irqrestore(&ep->lock, flags);
1099
1100 /* We have to call this outside the lock */
1101 if (pwake)
1102 ep_poll_safewake(&ep->poll_wait);
1103
1104 if (epi->event.events & EPOLLEXCLUSIVE)
1105 return ewake;
1106
1107 return 1;
注意到ep_poll_callback()的返回值和EPOLLEXCLUSIVE标志有关,该标志是用来处理这种情况:当多个进程中的不同epoll实例在监视同一个文件描述符时,如果该文件描述符上有事件发生,则所有的epoll实例所在进程都将被唤醒,这样有可能造成“惊群”(thundering herd)。关于EPOLLEXCLUSIVE可以看这里。
Linux内核笔记:epoll实现原理(二)的更多相关文章
- Linux内核笔记--内存管理之用户态进程内存分配
		内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ... 
- 【转载】linux内核笔记之进程地址空间
		原文:linux内核笔记之进程地址空间 进程的地址空间由允许进程使用的全部线性地址组成,在32位系统中为0~3GB,每个进程看到的线性地址集合是不同的. 内核通过线性区的资源(数据结构)来表示线性地址 ... 
- 【转载】linux内核笔记之高端内存映射
		原文:linux内核笔记之高端内存映射 在32位的系统上,内核使用第3GB~第4GB的线性地址空间,共1GB大小.内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的1 ... 
- Linux内核[CVE-2016-5195] (dirty COW)原理分析
		[原创]Linux内核[CVE-2016-5195] (dirty COW)原理分析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com https://bbs.pediy.com/ ... 
- Linux内核笔记:epoll实现原理(一)
		一.说明 针对的内核版本为4.4.10. 本文只是我自己看源码的简单笔记,如果想了解epoll的实现,强烈推荐下面的文章: The Implementation of epoll(1) The Imp ... 
- Linux学习笔记-epoll
		#include <sys/epoll.h> epoll是Linux内核的一个系统调用,一种可扩展的I/O事件通知机制,最早在Linux内核2.5.44版本引入. 它的功能是监视多文件描述 ... 
- LINUX内核笔记:自旋锁
		目录 自旋锁作用与基本使用方法? 在SMP和UP上的不同表现? 自旋锁与上下文 使用spin_lock()后为什么不能睡眠? 强调:锁什么? 参考 1.自旋锁作用与基本使用方法? 与其他锁一样,自 ... 
- linux内核笔记-内核同步
		linux内核就相当于不断对请求进行响应的服务器,这些请求可能来自CPU,可能来自发出中断的外部设备.我们将内核看作两种请求的侍者. (1)老板提出请求,侍者如果空闲,为老板服务.(系统调用或异常) ... 
- Linux内核启动流程分析(二)【转】
		转自:http://blog.chinaunix.net/uid-25909619-id-3380544.html S3C2410 Linux 2.6.35.7启动分析(第二阶段) 接着上面的分析,第 ... 
- Linux内核同步 - RCU synchronize原理分析
		RCU(Read-Copy Update)是Linux内核比较成熟的新型读写锁,具有较高的读写并发性能,常常用在需要互斥的性能关键路径.在kernel中,rcu有tiny rcu和tree rcu两种 ... 
随机推荐
- Linux清除文件内容的几种方法
			# 清空或删除大文件内容的五种方法: # 法一:通过重定向到 Null 来清空文件内容 $ >test.sh # 法二:使用 ‘true' 命令重定向来清空文件 $ true > test ... 
- 功率 dbm 和 mw 的换算
			射频知识; 功率/电平(dBm):放大器的输出能力,一般单位为w.mw.dBm.dBm是取1mw作基准值,以分贝表示的绝对功率电平. 换算公式:电平(dBm)=10lgw5W → 10lg5000 ... 
- openvpn用户管理、linux客户端配置及企业常用真实案例解析
			1.给企业用户分配VPN账户的流程: 添加拨号需要密码的用户 # source vars NOTE: If you run ./clean-all, I will be doing a rm -rf ... 
- centos6.5环境基于corosync+cman+rgmanager实现RHCS及iscsi+gfs2+clvm的文件系统集群
			centos6.5环境基于corosync+cman+rgmanager实现RHCS及iscsi+gfs2+clvm文件系统集群 一.环境准备 服务器列表: ansible server : 192. ... 
- git命令行提交并且同步到远程代码库
			远程代码库以github为例 1.打开 git bash 2.进入项目目录 cd /e/myGitProjects/test 3.提交到本地git仓库 git add -Agit commit -m ... 
- annoy ANN算法 调参
			search_k serach_k越大,越准确,但是要在时间和准确率之间取个trade off During the query it will inspect up to search_k node ... 
- vue2之对象属性的监听
			对象属性监听的两种方法: 1.普通的watch data() { return { frontPoints: 0 } }, watch: { frontPoints(newValue, oldValu ... 
- ubuntu 查看进程信息
			查看进程信息 ps ps -aux 查看所有进程,每行一个程序 top 显示当前运行程序 kill 98 (98为PID号,) kill -9 98 (强制杀死98) ps -e Linux如何查看端 ... 
- 欧拉函数,打表求欧拉函数poj3090
			欧拉函数 φ(n) 定义:[1,N]中与N互质的数的个数 //互质与欧拉函数 /* 求欧拉函数 按欧拉函数计算公式,只要分解质因数即可 */ int phi(int n){ int ans=n; ;i ... 
- pytest二:setup和teardown
			用例运行级别 模块级(setup_module/teardown_module)开始于模块始末,全局的 函数级(setup_function/teardown_function)只对函数用例生效(不在 ... 
