linux内核epoll实现分析

2023-11-03

为了更好的分享体验,博客搬迁至极客驿站 ,欢迎查阅

epoll与select/poll的区别

     select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的操作。

     select的本质是采用32个整数的32位,即32*32= 1024来标识,fd值为1-1024。当fd的值超过1024限制时,就必须修改FD_SETSIZE的大小。这个时候就可以标识32*max值范围的fd。
     poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
     epoll还是poll的一种优化,返回后不需要对所有的fd进行遍历,在内核中维持了fd的列表。select和poll是将这个内核列表维持在用户态,然后传递到内核中。与poll/select不同,epoll不再是一个单独的系统调用,而是由epoll_create/epoll_ctl/epoll_wait三个系统调用组成,后面将会看到这样做的好处。epoll在2.6以后的内核才支持。

select/poll的几大缺点:
1、每次调用select/poll,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
2、同时每次调用select/poll都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
3、针对select支持的文件描述符数量太小了,默认是1024

为什么epoll相比select/poll更高效

     传统的poll函数相当于每次调用都重起炉灶,从用户空间完整读入ufds,完成后再次完全拷贝到用户空间,另外每次poll都需要对所有设备做至少做一次加入和删除等待队列操作,这些都是低效的原因。

     epoll的解决方案中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。select, poll和epoll都是使用waitqueue调用callback函数去wakeup你的异步等待线程的,如果设置了timeout的话就起一个hrtimer,select和poll的callback函数并没有做什么事情,但epoll的waitqueue callback函数把当前的有效fd加到ready list,然后唤醒异步等待进程,所以epoll函数返回的就是这个ready list, ready list中包含所有有效的fd,这样一来kernel不用去遍历所有的fd,用户空间程序也不用遍历所有的fd,而只是遍历返回有效fd链表。

为什么要实现eventpollfs

1、可以在内核里维护一些信息,这些信息在多次epoll_wait间是保持的,比如所有受监控的文件描述符
2、epoll本身也可以被poll/epoll

加入的epoll的FD需要满足什么条件

理论上只要是一个文件就可以加入epoll,Linux本身的一个设计思想也是一切皆文件。file结构也体现了对epoll的支持。

struct file {
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
#define f_dentry	f_path.dentry //与文件相关的目录项对象
#define f_vfsmnt	f_path.mnt    //含有该文件的已安装文件系统
	<span style="color:#ff0000;">const struct file_operations	*f_op; //文件操作表指针</span>
	spinlock_t		f_lock;  /* f_ep_links, f_flags, no IRQ */
	atomic_long_t		f_count; //文件对象的引用计数器
	unsigned int 		f_flags; //当打开文件时所指定的标志
	fmode_t			f_mode;      //进程访问模式
	loff_t			f_pos;       //当前的文件偏移量
	struct fown_struct	f_owner; //通过信号进行I/O事件通知的数据
	const struct cred	*f_cred;
	struct file_ra_state	f_ra; //文件预读状态

	u64			f_version; //版本号,每次使用后自动递增
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;//指向特定文件系统或设备驱动程序所需要数据的指针

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	<span style="color:#ff0000;">struct list_head	f_ep_links;//文件的事件轮询等待着链表头</span>
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;//指向文件地址空间对象的指针
#ifdef CONFIG_DEBUG_WRITECOUNT
	unsigned long f_mnt_write_state;
#endif
};

但是加入文件能不能poll出事件,这就要符合两点要求:

1、文件对应的file_operations必须实现poll操作

2、在对应的文件描述符等待队列上注册回调函数用于唤醒等待进程

epoll关键数据结构关系如下:

epoll是如何poll出事件的

以tcp socket为例分析一下,是如何poll出事件的

当通过系统调用epoll_ctl将FD加入epoll时会执行ep_insert函数

/*
 * Must be called with "mtx" held.
 */
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
		     struct file *tfile, int fd)
{
	int error, revents, pwake = 0;
	unsigned long flags;
	struct epitem *epi;
	struct ep_pqueue epq;

	if (unlikely(atomic_read(&ep->user->epoll_watches) >=
		     max_user_watches))
		return -ENOSPC;
	if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
		return -ENOMEM;

	/* Item initialization follow here ... */
	INIT_LIST_HEAD(&epi->rdllink);
	INIT_LIST_HEAD(&epi->fllink);
	INIT_LIST_HEAD(&epi->pwqlist);
	epi->ep = ep;
	ep_set_ffd(&epi->ffd, tfile, fd);
	epi->event = *event;
	epi->nwait = 0;
	epi->next = EP_UNACTIVE_PTR;

	/* Initialize the poll table using the queue callback */
	epq.epi = epi;
	<span style="color:#ff0000;">init_poll_funcptr(&epq.pt, ep_ptable_queue_proc)</span>;

	/*
	 * 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.
	 */
	<span style="color:#ff0000;">revents = tfile->f_op->poll(tfile, &epq.pt)</span>;

	/*
	 * We have to check if something went wrong during the poll wait queue
	 * install process. Namely an allocation for a wait queue failed due
	 * high memory pressure.
	 */
	error = -ENOMEM;
	if (epi->nwait < 0)
		goto error_unregister;

	/* Add the current item to the list of active epoll hook for this file */
	spin_lock(&tfile->f_lock);
	list_add_tail(&epi->fllink, &tfile->f_ep_links);
	spin_unlock(&tfile->f_lock);

	/*
	 * Add the current item to the RB tree. All RB tree operations are
	 * protected by "mtx", and ep_insert() is called with "mtx" held.
	 */
	ep_rbtree_insert(ep, epi);

	/* We have to drop the new item inside our item list to keep track of it */
	spin_lock_irqsave(&ep->lock, flags);

	/* If the file is already "ready" we drop it inside the ready list */
	if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
		list_add_tail(&epi->rdllink, &ep->rdllist);

		/* Notify waiting tasks that events are available */
		if (waitqueue_active(&ep->wq))
			wake_up_locked(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}
<span style="white-space:pre">	</span>...
	return error;
}

首先初始ep_pqueue这样一个结构将等待队列回调函数注册,然后通过poll函数执行注册的回调函数将等待队列节点加入对应的等待队列

/*
 * This is the callback that is used to add our wait queue to the
 * target file wakeup lists.
 */
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, <span style="color:#ff0000;">ep_poll_callback</span>);
		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;
	}
}

ep_poll_callback为当对应的描述符状态发生改变或是有对应事件发生时执行的回调函数,以tcp为了,当状态发生改变时会有如下调用流程

sock_def_wakeup(sock_init_data对sock初始化)--->wake_up_interruptible_all-->__wake_up--->curr->func(对于加入epoll的文件描述符而言即ep_poll_callback)

/*
 * This is the callback that is passed to the wait queue wakeup
 * machanism. It is called by the stored file descriptors when they
 * have events to report.
 */
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int pwake = 0;
	unsigned long flags;
	struct epitem *epi = ep_item_from_wait(wait);
	struct eventpoll *ep = epi->ep;

	spin_lock_irqsave(&ep->lock, flags);

	/*
	 * If the event mask does not contain any poll(2) event, we consider the
	 * descriptor to be disabled. This condition is likely the effect of the
	 * EPOLLONESHOT bit that disables the descriptor when an event is received,
	 * until the next EPOLL_CTL_MOD will be issued.
	 */
	if (!(epi->event.events & ~EP_PRIVATE_BITS))
		goto out_unlock;

	/*
	 * Check the events coming with the callback. At this stage, not
	 * every device reports the events in the "key" parameter of the
	 * callback. We need to be able to handle both cases here, hence the
	 * test for "key" != NULL before the event match test.
	 */
	if (key && !((unsigned long) key & epi->event.events))
		goto out_unlock;

	/*
	 * If we are trasfering events to userspace, we can hold no locks
	 * (because we're accessing user memory, and because of linux f_op->poll()
	 * semantics). All the events that happens during that period of time are
	 * chained in ep->ovflist and requeued later on.
	 */
	if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) {
		if (epi->next == EP_UNACTIVE_PTR) {
			epi->next = ep->ovflist;
			ep->ovflist = epi;
		}
		goto out_unlock;
	}

	/* 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);

	/*
	 * Wake up ( if active ) both the eventpoll wait list and the ->poll()
	 * wait list.
	 */
	if (waitqueue_active(&ep->wq))
		<span style="color:#ff0000;">wake_up_locked(&ep->wq)</span>;
	if (waitqueue_active(&ep->poll_wait))
		pwake++;

out_unlock:
	spin_unlock_irqrestore(&ep->lock, flags);

	/* We have to call this outside the lock */
	if (pwake)
		ep_poll_safewake(&ep->poll_wait);

	return 1;
}

ep_poll_callback所做的动作是将已ready的epitem加入到ep->rdlist中,然后唤醒等待对应描述符的进程。也即系统调用epoll_wait函数所执行的内容,wait函数会判断rdlist是否为空,如果不为空则跳出循环,扫描rdlist将以发生的event发送到用户态空间

目前linux系统中,pipefd,timerfd,signalfd,eventfd等这些都是可以加入epoll的,另外epoll本身也可以作为一个FD加入epoll。

libevent

libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部对select、epoll、kqueue等系统调用管理事件机制进行封装。详见http://libevent.org/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux内核epoll实现分析 的相关文章

  • Linux-epoll机制

    主要接口 epoll create epoll ctl epoll wait epoll create 头文件 include
  • linux内核中GPIO的使用(一)--IO内存

    一 相关概念 使用IO内存将物理地址映射为虚拟地址 再通过对虚拟地址的操作来控制硬件 所谓的IO内存是指一种编址方式 不同cpu平台使用的编址方式不同 一种是 IO内存 方式 也叫统一编址方式 是指内存和外设的地址是在同一个地址空间上的 如
  • select,poll,epoll优缺点及比较

    在之前我已经分析了这三个函数 请看我之前的文章 IO多路复用之select函数详解 IO多路复用之poll函数详解 IO多路复用之epoll函数详解 这篇文章只总结优缺点 以便面试时回答 select优点 1 select 的可移植性更好
  • 新手玩转Linux Kernel漏洞之Null Pointer Dereference

    新手玩转Linux Kernel漏洞之Null Pointer Dereference 前言 这是我内核漏洞的入门篇 不是很复杂 希望能给徘徊在门外的小伙伴一点启发 漏洞描述 A NULL pointer dereference occur
  • 基于升序链表的定时器及其简单应用

    Linux网络编程笔记 定时器 基于升序链表的定时器 这其实就是一个结点为 class util timer public util timer prev NULL next NULL 构造函数 public time t expire 任
  • 基于libevent, libuv和android Looper不断演进socket编程

    最近在做websocket porting的工作中 需要实现最底层socket读和写 基于同步读 libevent libuv和android Looper都写了一套 从中体会不少 1 同步阻塞读写 最开始采用同步阻塞读写 主要是为了快速实
  • 装上后这 14 个插件后,PyCharm 真的是无敌的存在

    来源 Python编程时光 作者 写代码的明哥 Key Promoter X 如果让我给新手推荐一个 PyCharm 必装插件 那一定是 Key Promoter X 它就相当于一个快捷键管理大师 它时刻地在 教导你 当下你的这个操作 应该
  • linux源代码.tar.xz解压

    刚开始学习linux内核 在linux内核官网https www kernel org 下载 我下载的版本是 linux 2 6 34 14 tar xz 由于我的linux中没有安装 xz的解压缩软件 需要下载 http download
  • gcov 和 perf 使用的基本套路备忘 ubuntu

    一 源代码 cat helloSeven c include
  • mac下面有epoll?

    没有的 但是mac下面有kqueue 跟epoll原理是差不多的 这个是没办法的 如果实在需要 就用Ubuntu吧 这个也可以无缝迁移 更多资源 更多文章由小白技术社提供 是我啦
  • 基于epoll实现简单的web服务器

    1 简介 epoll 是 Linux 平台下特有的一种 I O 复用模型实现 于 2002 年在 Linux kernel 2 5 44 中被引入 在 epoll 之前 Unix Linux 平台下的 I O 复用模型包含 select 和
  • 面试题创作0001,请解释mmap的细节

    1 请列举Linux的几种ICP工具 2 重解释共享内存的实现原理 3 两个进程A和B共享到的同一页物理内存 如果被A进程勾进CPU的Cache 那么B进程访问这段内存数据时 将会从内存中访问 还是从Cache中访问呢 可以X86为例 或其
  • pthread信号

    信号是典型的异步事件 内核在某个信号出现时有三种处理方式 忽略信号 除了SIGKILL和SIGSTOP信号不能忽略外 其他大部分信号都可以被忽略 捕捉信号 也就是在信号发生时调用一个用户函数 注意不能捕捉SIGKILL和SIGSTOP 执行
  • 工作队列(workqueue)

    转载于 http blog csdn net angle birds article details 8448070 项目需要 在驱动模块里用内核计时器timer list实现了一个状态机 郁闷的是 运行时总报错 Scheduling wh
  • EPOLLRDHUP 不可靠

    我正在通过客户端 服务器 TCP 连接使用非阻塞读 写epoll wait 问题是 我无法使用以下方法可靠地检测 对等关闭连接 事件EPOLLRDHUP旗帜 经常会发生标志未设置的情况 客户端使用close 大多数时候 服务器从epoll
  • 具有边缘触发事件的 epoll

    的手册页epoll有一个边缘触发的示例代码 如下所示 for nfds epoll wait epollfd events MAX EVENTS 1 if nfds 1 perror epoll pwait exit EXIT FAILUR
  • 当 fd 关闭时,我会收到 epoll 的通知吗?

    我目前正在构建一些使用的东西epoll 它工作得很好 但是当文件描述符被删除时最好有一个通知epoll当底层的fd关闭了 有没有办法获得通知epoll一旦fd关闭了 不 这是一个Zig https ziglang org 程序来演示 con
  • 提升 Asio 单线程性能

    我正在实现需要维护大量 100K 或更多 长期连接的自定义服务器 服务器只是在套接字之间传递消息 并且不进行任何认真的数据处理 消息很小 但每秒都会接收 发送许多消息 减少延迟是目标之一 我意识到使用多核不会提高性能 因此我决定通过调用在单
  • Linux 上的 Boost Asio 不使用 Epoll

    我的印象是 boost asio 默认情况下会使用 epoll 设置而不是 select 实现 但在运行一些测试后 看起来我的设置正在使用 select 操作系统 RHEL 4内核 2 6海湾合作委员会 3 4 6 我编写了一个小测试程序来
  • 如何将 boost::asio 与 Linux GPIO 结合使用

    我有一个单线程 Linux 应用程序 使用 boost asio 进行异步输入 输出 现在我需要扩展此应用程序以读取 GPIO 输入 sys class gpio gpioXX value 可以在边沿触发的 GPIO 输入上使用 boost

随机推荐

  • c++中调用复制构造函数的三种情况

    普通构造函数是在对象创建时被调用 而复制构造函数在以下三种情况下会被调用 1 当用类的一个对象去初始化该类的另一个对象时 例如 Point a 1 2 Point b a 调用复制构造函数 Point c a 同上 2 如果函数的形参是类的
  • weex dom.scrollToElement 滚动问题

    使用weex 的dom scrollToElement 兼容问题 1 使用for生成的ref 在初始化获取ref节点时候需要有100ms延迟 2 dom scrollToElement 传入的 ref参数 需要使用this refs ref
  • DNS中的正向解析与反向解析 及 nslookup命令使用

    DNS中的正向解析与反向解析 Jackxin Xu IT技术专栏 博客频道 CSDN NET http blog csdn net jackxinxu2100 article details 8145318 正向解析 通过域名查找ip 反向
  • 黑盒测试方法之因果图和判定表——三

    前面文章 黑盒测试方法之因果图和判定表 一 主要讲述判定表驱动法的相关理论内容 黑盒测试方法之因果图和判定表 二 主要讲述因果图相关理论内容 4 因果图加判定表设计测试用例实例 这里以一个 软件评测师教程 上面的例子为例 来说明和演示因果图
  • GAN(初步学习)

    GAN的原理介绍 GAN的主要灵感来源于博弈论中零和博弈的思想 应用到深度学习神经网络上来说 就是 通过生成网络G Generator 和判别网络D Discriminator 不断博弈 进而使G学习到数据的分布 如果用到图片生成上 则训练
  • Zotero 相关学习链接

    参考链接 https www zotero org support zh start https github com l0o0 translators CN https www zhihu com question 21518558 ht
  • ROS串口通信(1)环境搭建

    ROS串口通信 1 环境搭建 引言 1 ubuntu串口驱动安装和使用 1 1 安装 1 2 使用 1 3 Ubuntu 查看串口 设置串口权限 2 Ubuntu下的串口助手cutecom 引言 无疑 串口的调试需要联合串口助手调试更加方便
  • 软件测试2019:第一次作业

    就是利用测试工具按照测试方案和流程对产品进行功能和性能测试 甚至根据需要编写不同的测试工具 设计和维护测试系统 对测试方案可能出现的问题进行分析和评估 执行测试用例后 需要跟踪故障 以确保开发的产品适合需求 使用人工或者自动手段来运行或测试
  • 三分钟拥有自己的 chat-gpt (开发到上线)

    三分钟拥有自己的 chat gpt 开发到上线 首先你需要有一个 laf 账号 如果你还不知道 laf 是什么 点击这里三分钟学会 然后你还需要有一个 chat gpt 的账号并且生成一个 apiKey 这一步可以问 Google 云函数
  • Centos 7 阿里yum源及epel源配置

    1 下载阿里yum配置文件 wget O etc yum repos d CentOS Base repo http mirrors aliyun com repo Centos 7 repo 2 下载阿里epel配置文件 wget O e
  • ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26‘ not found

    在运行程序的时候报错 import cv2 ImportError usr lib x86 64 linux gnu libstdc so 6 version GLIBCXX 3 4 26 not found required by hom
  • 【STM32内部架构理解】

    STM32和GD32F10X内部架构 整体架构 模块架构 总线矩阵 最开始学stm32开始对架构各部分不是很了解看架构图基本上走马观花 然后陷入对各个外设的投入中去 比如GPIO ADC CAN等 但是对整体架构的掌握对后面编程很多细节的理
  • 列表首屏毫秒级加载与自动滚动定位方案

    引用自 摸鱼wiki 场景
  • 利用libuv编写异步多线程的addon实例

    转载自 http snoopyxdy blog 163 com blog static 601174402013422103614385 最近cnode上很多TX在问关于node的异步回调以及单线程的事情 今天看了libuv的一些api和d
  • 微信小程序启动自动检测版本更新,检测到新版本则提示更新

    UpdateManager 对象 用来管理更新 可通过 wx getUpdateManager 接口获取实例 在app js中的示例代码 onShow 获取小程序更新机制的兼容 由于更新的功能基础库要1 9 90以上版本才支持 所以此处要做
  • 垃圾分类资料汇总

    目录 一 前言 二 垃圾分类话题简介 三 当前存在的一些有用参考资源 四 当前存在的垃圾分类小程序或者APP 五 当前规模比较大的产品 六 个人想法 参考资料 注意事项 一 前言 自从上海实行了垃圾分类之后 垃圾分类这个话题就成为了一个热点
  • 蓄水池抽样算法(Reservoir Sampling)

    蓄水池抽样算法 Reservoir Sampling 问题描述 问题分析 代码实现 数学证明 问题描述 给定一个数据流 数据流长度N很大 且长度不可预知 问如何在仅遍历一次数据的情况下 如何等概率 抽取m个样本 问题分析 首先明确概念 等概
  • qt5.2音乐播放器的播放功能

    qt5 2并没有了phonon 模块 在播放音频视频的时候可以使用QMediaplayer来实现 创建媒体 player new QMediaPlayer this 创建播放列表 并在列表里添加两首歌曲 mediaList new QMed
  • 禁止弹窗中蒙层底部页面跟随滚动的几种方法汇总

    场景概述 众所周知 弹窗是一种常见的交互方式 而蒙层是弹窗必不可少的元素 用于隔断页面与弹窗区块 暂时阻断页面的交互 但是 在蒙层元素中滑动的时候 滑到内容的尽头时 再继续滑动 蒙层底部的页面会开始滚动 显然这不是我们想要的效果 因此需要阻
  • linux内核epoll实现分析

    为了更好的分享体验 博客搬迁至极客驿站 欢迎查阅 epoll与select poll的区别 select poll epoll都是IO多路复用的机制 I O多路复用就通过一种机制 可以监视多个描述符 一旦某个描述符就绪 能够通知程序进行相应