可扩展的事件复用技术:epoll和kqueue

高级 IO 模型之 kqueue 和 epoll 简介 任何一个程序都离不开 IO,有些是很明显的 IO,比如文件的读写,也有一些是不明显的 IO,比如网络数据的传输等。那么这些 IO 都有那些模式呢?我们在使用中应该如何选择呢?高级的 IO 模型 kqueue 和 epoll 是怎么工作的呢?一起来看看吧。 block IO 和 nonblocking IO 大家先来了解一下 IO 模型中最简单的两个模型:阻塞 IO 和非阻塞 IO。 比如我们有多个线程要从一个 Socket server 中读取数据,那么这个读取过程其实可以分成两... 阅读详情

通常来说我喜欢Linux更甚于BSD系统,但是我真的想在Linux上拥有BSD的kqueue功能。

什么是事件复用技术

假设你有一个简单的web服务器,并且那里已经打开了两个socket连接。当服务器从两个连接那里都收到Http请求的时候,它应该返回一个Http响应给客户端。但是你没法知道那个客户端先发送的消息和什么时候发送的。BSD套接字接口的阻塞行为意味着,如果你在一个连接上调用recv()函数,你就没办法去响应另外一个连接上的请求。这时你就需要I/O复用技术。 I/O复用技术的一个直接方式是让每个连接都拥有一个进程/线程,这样连接上的阻塞行为就不会相互影响。这样,你就把所有繁琐的调度/复用问题交给了操作系统内核。这样的多线程架构伴随着的是高昂资源消耗。维护大量的线程对内核来说没有什么必要。每个连接上的独立栈不仅要增加内存痕迹,同时也降低了CPU本地缓存能力。 那么我们如何不使用线程-连接模式来实现I/O复用技术呢?你可以通过一个简单的忙等轮询来实现,即在每个连接上进行非阻塞的套接字操作,但这种行为过于的浪费。我们所要知道的只不过是哪个套接字已经就绪。因此系统内核为应用与内核之间提供了一个单独的通道,这个通道在你的套接字变为就绪时会发出通知。这就是基于准备就绪模式下的select()/poll()工作模式。

概况: select()

select()和poll()的工作方式非常类似。让我们先快速看一下select()函数

select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *timeout)

调用select()函数,你的应用程序需要提供三个兴趣集:r,w和e。每一个集合都是一个文件描述符的位图。例如,如果你关注从文件描述符6里面读取数据,那么r集合里面的第6个字节位就设成1。这个调用会被阻塞直到兴趣集中有更多的文件描述符就绪,因此你可以操纵这些文件描述符而不会被阻塞。在返回后,系统内核会覆写整个位图来指明哪些文件描述符已经就绪。 从扩展性角度,我们可以找到4个问题:

  1. 这些位图的大小是固定的(FD_SETSIZE, 通常是1024),尽管也有一些方法可以绕过这个限制。
  2. 由于位图是由内核来覆写的,用户应用程序在每一次调用时需要重填兴趣集。
  3. 每一次调用时,用户应用程序和内核都需要扫描整个位图,用于指出哪些文件描述符属于兴趣集,哪些属于结果集。这对于结果集来说特别的低效,因为他们看起来非常的稀疏(如在一个给定的时间内,只有很少的文件描述符会发生变化)。
  4. 内核必须为每一次调用去迭代整个兴趣集,以便找到哪些文件描述符已经就绪。假如没有一个就绪,内核就会迭代的为每个套接字链接设置一个内部事件。

概况: poll()

poll()的设计意图就是解决这些问题。

poll(struct pollfd *fds, int nfds, int timeout)

struct pollfd {
    int fd;
    short events;
    short revents;
}

poll()的实现不依赖于位图,而是用文件描述符数组(这样第一个问题就解决了)。通过对兴趣事件与结果事件采取分离字段,第二个问题也得以解决,因为用户程序可以维护并重用这个数组。如果poll函数能够拆分该数组而不是字段,那么第三个问题也就引刃而解。第四个问题是继承而来的而且是不可避免,因为poll()和select()都是无状态的,内核不会在内部维护兴趣集状态。

为什么与扩展性有关?

如果你的网络服务器需要维护一个相对较小的连接数(如100个),并且连接率也比较低(如每秒100个), 那么poll()和select()就足够了。也许你根本不需要为事件驱动编程而苦恼,只要多进程/多线程架构就可以了。如果性能不是你关注的重点,那么灵活性与容易开发才是关键。Apache web服务器就是一个典型的例子。

但是,如果你的服务器程序是网络资源敏感的(如1000个并发连接数或者一个较高的连接率),那么你就要真的在意性能问题了。这种情况通常被称为c10k问题。你的网络服务器将很难执行任何有用的东西,除了在这样的高负荷下浪费宝贵的CPU周期。

假设这里有10000并发连接。一般来说,只有少量的文件描述符被使用,如10个已经读就绪。那么每次poll()/select()被调用,就有9990个文件描述符被毫无意义的拷贝和扫描。

正如更早时候提到过的,这个问题是由于select()/poll()接口的无状态产生的。Banga et al的论文(发布于USENIX ATC 1999)提供了一个新的建议:状态相关兴趣集。通过在内核内部维护兴趣集的状态,来取代每次调用都要提供整个兴趣集这样的方式。在decalre_interest()调用之上,内核持续的更新兴趣集。用户程序通过调用get_next_event()函数来分发事件。

灵感通常来自于研究成果,Linux和Free BSD都有它们自己的实现, 分别是epoll和kqueue。但这又意味着缺少了可移植性,一个基于epoll的程序是无法跑在Free BSD系统上的。有一种说法是kqueue技术上比epoll更优,所以看起来epoll也没有存在的理由了。

Linux中的epoll

epoll接口由3个调用组成:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll_ctl()和epoll_ctl()本质上是分别对应到declare_interest()和get_next_event() 函数的。epoll_create()创建一个类似于文件描述符的上下文,这个上下文其实暗指进程的上下文。 从内部机制来说,epoll在Linux内核中的实现并非非常不同于select()/poll()的实现。唯一不同的地方就是是否状态相关。因为本质上来说它们的设计目标是一样的(基于套接字/管道的事件复用技术)。查看Linux分支树种的源代码文件fs/select.c(对应select和poll)和fs/eventpoll.c(对应epoll)可以得到更多的信息。 你也可以从这里找到Linus Torvalds对于epoll的早期一些想法。

Free BSD中的Kqueue

如epoll那样,kqueue同样支持每个进程中有多个上下文(兴趣集)。kqueue()函数行为有点类似于epoll_create()。但是,kevent()却集成了epoll_ctl()(用于调整兴趣集)和epoll_wait()(获取事件) 的角色。

int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges, 
           struct kevent *eventlist, int nevents, const struct timespec *timeout);

事实上,kqueue从易于编程角度来看相比epoll要更复杂一些。这是因为kqueue设计更抽象一些,目的更宽泛。让我们来看一下kevent结构体:

struct kevent {
     uintptr_t       ident;          /* 事件标识 */
     int16_t         filter;         /* 事件过滤器 */
     uint16_t        flags;          /* 通用标记 */
     uint32_t        fflags;         /* 特定过滤器标记 */
     intptr_t        data;           /* 特定过滤器数据 */
     void            *udata;         /* 不透明的用户数据标识 */
 };

这些字段的细节已经超出了本文的范围,但你可能已经注意到了这里没有显式的文件描述符字段。这是因为kqueue设计的目的并非是为了替代基于套接字事件复用技术的select()/poll(),而是提供一般化的机制来处理多种操作系统事件。

过滤器字段指明了内核事件类型。如果它是EVFILT_READ或EVFILT_WRITE,kqueue就与epoll是一样的。这种情况下,ident字段表现为一个文件描述符。ident字段也可能表现为其他类型事件的标识,如进程号和信号数目,这取决于过滤器类型。更多的细节可以从man手册和这篇文档里找到。

epoll和kqueue的比较

性能

从性能角度讲,epoll存在一个设计上的缺陷;它不能在单次系统调用中多次更新兴趣集。当你的兴趣集中有100个文件描述符需要更新状态时,你不得不调用100次epoll_ctl()函数。性能降级在过渡的系统调用时表现的非常明显,这篇文章有做解释。我猜这是Banga et al原来工作的遗留,正如declare_interest()只支持一次调用一次更新那样。相对的,你可以在一次的kevent调用中指定进行多次兴趣集更新。

非文件类型支持

另一个问题,在我看了更重要一些,同样也是epoll的一个限制。它的设计目的是为了提高select()/poll()的性能,epoll只能基于文件描述符工作。这有什么问题吗? 一个常见的说法是“在unix中,所有东西都是文件”。大部分情况都是对的,但并不总是这样。例如时钟就不是,信号也不是,信号量也不是,包括进程也不是。(在Linux中)网络设备也不是文件。在类Unix系统中有好多事物都不是文件。你无法对这些事物采用select()/poll()/epoll()的事件复用技术。典型的网络服务器管理很多类型的资源,除了套接字外。你可能想通过一个单一的接口来管理它们,但是你做不到。为了避免这个问题,Linux提供了很多补充性质的系统调用,如signalfd(),eventfd()和timerfd_create()来转换非文件类型到文件描述符,这样你就可以使用epoll了。但是看起来不那么的优雅...你真的想让用一个单独的系统调用来处理每一种资源类型吗? 在kqueue中,多才多艺的kevent结构体支持多种非文件事件。例如,你的程序可以获得一个子进程退出事件通知(通过设置filter = EVFILT_PROC, ident = pid, 和fflags = NOTE_EXIT)。即便有些资源或事件不被当前版本的内核支持,它们也会在将来的内核中被支持,同时还不用修改任何API接口。

磁盘文件支持

最后一个问题是epoll并不支持所有的文件描述符;select()/poll()/epoll()不能工作在常规的磁盘文件上。这是因为epoll有一个强烈基于准备就绪模型的假设前提。你监视的是准备就绪的套接字,因此套接字上的顺序IO调用不会发生阻塞。但是磁盘文件并不符合这种模型,因为它们总是处于就绪状态。 磁盘I/O只有在数据没有被缓存到内存时会发生阻塞,而不是因为客户端没发送消息。磁盘文件的模型是完成通知模型。在这样的模型里,你只是产生I/O操纵,然后等待完成通知。kqueue支持这种方式,通过设置EVFILT_AIO 过滤器类型来关联到 POSIX AIO功能上,诸如aio_read()。在Linux中,你只能祈祷因为缓存命中率高而磁盘发生不阻塞(这种情况在通常的网络服务器上是个彩蛋),或者通过分离线程来使得磁盘I/O阻塞不会影响网络套接字的处理(如FLASH架构)。


高级I/O模型:kqueue和epoll编程 通过使用kqueue和epoll,我们可以有效地管理并发I/O事件,提高程序的性能和响应能力。本文通过示例代码介绍了kqueue和epoll的使用方法,希望对你理解和应用这两种高级I/O模型有所帮助。在网络编程中,I/O多路复用是一种高效的技术,可以同时监视多个I/O事件而不阻塞程序的执行。当读事件发生时,我们可以执行相应的读取操作。通过epoll,我们可以监视大量文件描述符上的读写事件,并在事件发生时得到通知。通过kqueue,我们可以同时监视多个文件描述符上的读写事件,并在事件发生时得到通知。 阅读详情

相关推荐

GitHub漏洞挖掘与CVE提交实战指南:从发现到编号的完整链路

GitHub开源项目是现代软件供应链的核心节点,其安全漏洞直接影响全球数百万系统的机密性、完整性与可用性。理解漏洞本质需回归CIA三元组原理,而真实可利用性依赖对协议解析层、配置加载区和依赖传递链等高危模块的精准识别。技术价值在于将模糊的‘可疑逻辑’转化为符合MITRE CVE标准的可验证事件,支撑SDL流程、威胁情报建设与供应链风险管理。典型应用场景包括甲方安全工程师推动供应商修复、独立研究者参与负责任披露,以及DevSecOps团队构建自动化风险坐标系。本文基于27次CVE提交经验,聚焦GitHub代码

weixin_33646989的博客 651

高级IO模型之kqueue和epoll

文章目录简介block IO和nonblocking IOIO多路复用和selectpollepollkqueueepoll和kqueue的优势 简介 任何一个程序都离不开IO,有些是很明显的IO,比如文件的读写,也有一些是不明显的IO,比如网络数据的传输等。那么这些IO都有那些模式呢?我们在使用中应该如何选择呢?高级的IO模型kqueue和epoll是怎么工作的呢?一起来看看吧。 block IO和nonblocking IO 大家先来了解一下IO模型中最简单的两个模型:阻塞IO和非阻塞IO。 比如我们有

程序那些事 5702

S32DS使用完整指南:S32K CAN通信模块配置步骤

深入讲解S32DS使用过程中S32K芯片的CAN通信模块配置步骤,涵盖关键设置与常见问题。掌握s32ds使用技巧,提升开发效率,实现稳定可靠的车载网络通信。

weixin_42592399的博客 981

深入分析 Kqueue & Kevent 原理及应用

转载者注:此篇转载内容是两篇博文的汇总,Kqueue用例部分增加csdn 《春夜喜雨》的demo用例分析,转者认为更容易让读者理解和认知。 常见IO复用技术对比 Wiki上的解释: kqueue 是一种可扩展的事件通知接口。2000 年 7 月发布的 FreeBSD 4.1 中首次引入了 kqueue,随后也被 NetBSD、OpenBSD、macOS 等操作系统支持。 kqueue 在内核与用户空间之间充当输入输出事件的管线。因此在事件循环的迭代中,进行一次 kevent(2) 系统调用不仅可以接收未决

weixin_38387929的博客 4475

CA(载波聚合)

文章目录CA基本概念1. CA分类2. Pcell、Scell以及Serving CellPcell(Primary Cell)Scell(Secondary Cell)3. Pcell、Scell部署场景场景1场景2场景3场景44. CA的协议控制4.1 Camping/Connetion Establishment:4.2 CC Management/SCell Addition/Release:4.3 Handover/Measurements:4.4 SCell激活 CA基本概念 CA也就是我们中文

你又来看我了,一起学习吧 4万+

Unity 数字孪生实战:PiXYZ Plugin工业模型高效处理指南

本文是Unity数字孪生实战指南,重点介绍了如何使用PiXYZ Plugin高效处理工业CAD模型。针对工业模型导入Unity时面临的格式壁垒、数据爆炸和资产混乱等核心痛点,文章提供了从环境安装、基础导入到智能减面、资产清理的完整解决方案,并分享了预设自动化、运行时导入等高级技巧与性能优化策略,帮助开发者快速打通工业设计与实时交互流程。

weixin_29009501的博客 257

掌握kqueue与epoll的7个关键点,让C++网络程序性能飙升

掌握C++实现高性能网络库(基于epoll/kqueue)的7个关键技术,解决高并发IO瓶颈。适用于服务器开发、实时通信等场景,通过事件驱动、非阻塞IO与资源优化提升性能,显著降低延迟。核心方法清晰解析,值得收藏。

FuncWander的博客 666

基于GEC6818开发板的C/C++贪吃蛇小游戏

f.开始游戏(进入游戏,并没有马上开游戏,需要点击播放才能开始)2.相关宏,全局变量(可以根据自己需要进行修改)k.显示文字前的相关初始化。k.自定义font.h文件。1.需要的主要头文件。l.在开发板上显示字1。m.在开发板上显示字2。a.显示固定大小图片。h.线程1:获得坐标。

2301_77273301的博客 2132

nanomsg异步I/O模型终极指南:epoll与kqueue性能对比分析

在现代网络编程中,**异步I/O模型**是实现高性能应用的关键技术。nanomsg作为一个轻量级、高性能的消息传递库,在其异步I/O实现中巧妙地利用了不同操作系统的原生机制,特别是Linux的**epoll**和BSD系统的**kqueue**。本文将深入探讨这两种异步I/O模型的实现原理、性能特点以及在nanomsg中的具体应用。🚀 ## 异步I/O模型基础概念 **异步I/O**是一种非

gitblog_00396的博客 839

Mit6.S081-实验1-Xv6 and Unix utilities

Mit6.S081-实验1-Xv6 and Unix utilities前言一、Boot xv61,实验目的2,操作流程1)切换到xv6-labs-2020代码库的lab1分支2)启动xv63)测试xv64)过程分析5)其他操作二、在xv6中添加一个自己编写的程序1,源码准备2,编译配置3,测试添加程序4,过程分析三、xv6中shell简析 前言 一、Boot xv6 1,实验目的 利用qemu启动xv6 2,操作流程 1)切换到xv6-labs-2020代码库的lab1分支 git checkout ut

code a better life~ 36万+

C# 通俗易懂的介绍基础知识(七)——栈Stack(从日常生活开始讲解)

C# 通俗易懂的介绍基础知识(七)——栈Stack(从日常生活开始讲解) 大家好,我是菌菌巧乐兹,励志用最简单的语言来讲解程序知识点。

菌菌兹的博客 1669

为什么顶尖公司都在用epoll/kqueue?C++网络库性能跃迁的关键路径

掌握C++实现高性能网络库(基于epoll/kqueue),突破高并发服务性能瓶颈。适用于即时通信、分布式系统等场景,通过事件驱动与非阻塞I/O显著提升吞吐量与响应速度。核心方法清晰,架构设计高效,值得收藏。

PoliSeed的博客 833

傅里叶红外(FT-IR):Origin数据处理、光谱绘图、曲线优化、图形美化

双击 X 轴,选择“轴线和刻度线”,设置粗细为“2”(较常用的粗细度),主刻度和次刻度为“朝内”,然后在此对话框内选择左轴,设置粗细为“2”,主刻度和次刻度为“无”,上轴和右轴同左轴。(4)在第二个图层基础上新建第三个图层,针对第二组数据重复上述中(1)和(2)的操作,再添加第三组数据的折线图,并重复上述中(3)的操作。FTIR 光谱上的峰只有相对的强弱,因此只分析一个光谱上的一个峰的强度大小并无意义,一般分析两个光谱在同一位置的峰强相对大小,或者分析同一光谱不同位置的峰强相对大小并与另一光谱进行比较。

qiushireader的博客 3933

使用 ARIMA/SARIMA 构建电力负荷时间序列预测模型:ML-For-Beginners 实战指南

本文以 ML-For-Beginners 课程仓库(GitHub_Trending/ml/ML-For-Beginners)第 7 单元第 2 课《Tidsserieprognoser med ARIMA》(基于 ARIMA 的时间序列预测)为骨架,系统讲解 ARIMA 模型的核心概念、基于 `statsmodels` 的完整建模流程,以及使用 walk-forward 验证与 MAPE 评估预测

gitblog_00678的博客 508

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

C++网络库性能提升10倍的秘密:深度剖析epoll与kqueue底层机制

掌握C++实现高性能网络库(基于 epoll/kqueue)的核心技术,显著提升服务器并发能力。深入解析epoll与kqueue底层机制,涵盖高并发、低延迟场景下的事件驱动设计与跨平台优化策略。性能提升10倍的实战经验总结,值得收藏。

SimProceed的博客 699

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Redis源码探究系列—epoll、kqueue 在 Redis 中的封装实现(下)

kqueue与epoll设计理念相似但API差异较大,Redis通过统一的抽象层屏蔽了这些差异,使得上层代码可以无缝运行在不同操作系统上。

Deepincode的博客 380

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

polling:跨平台I/O事件通知的终极解决方案,支持epoll、kqueue与IOCP

polling 是一个功能强大的跨平台 I/O 事件通知库,为开发者提供了统一的接口来操作 epoll、kqueue、event ports 和 IOCP 等多种系统级 I/O 事件通知机制,让开发者能够轻松编写高效、可移植的异步 I/O 应用程序。 ## 为什么选择 polling? 在现代操作系统中,不同的平台有着各自独特的 I/O 事件通知机制。比如 Linux 系统使用 epoll,m

gitblog_01149的博客 301

sc/sc网络编程宝典:Epoll、Kqueue和WSAPoll完全对比

在现代C语言网络编程中,I/O多路复用技术是构建高性能服务器的核心。sc/sc作为专注于C语言通用库和数据结构的项目,提供了丰富的网络编程组件。本文将深入对比三大主流I/O多路复用机制:Linux的Epoll、FreeBSD的Kqueue和Windows的WSAPoll,帮助开发者选择最适合的方案。 ## 一、I/O多路复用技术基础 I/O多路复用允许程序同时监视多个文件描述符,当其中一个或多

gitblog_00294的博客 842

C++高并发服务器开发秘籍(基于epoll/kqueue的极致优化)

掌握C++高并发服务器开发核心技巧,本文深入解析C++实现高性能网络库(基于epoll/kqueue)的底层原理与优化策略,涵盖事件驱动、非阻塞I/O及线程模型设计,适用于高负载网络服务开发,性能极致提升,值得收藏。

LearnPlex的博客 925

【C++高性能网络库设计精髓】:从epoll/kqueue原理到百万并发实战

掌握C++实现高性能网络库(基于epoll/kqueue)的核心技术,解决高并发服务设计难题。适用于即时通信、大规模服务器等场景,通过事件驱动与非阻塞I/O提升性能,支持百万级并发连接。架构清晰、扩展性强,值得收藏。

PixelFlow的博客 800

Rustix事件驱动编程终极指南:epoll、kqueue、port的跨平台解决方案

在当今高性能网络编程领域,事件驱动编程已成为构建高效、可扩展应用程序的核心技术。如果你正在寻找一个安全、高效且跨平台的Rust系统编程解决方案,那么rustix正是你需要的工具。rustix是一个提供安全Rust绑定的POSIX/Unix/Linux/Winsock系统调用库,它通过内存安全和I/O安全的封装,让开发者能够轻松使用epoll、kqueue和port等事件驱动API,构建跨平台的高性

gitblog_00231的博客 346

如何用C++构建千万级并发系统?epoll/kqueue核心技巧全公开

掌握C++实现高性能网络库(基于 epoll/kqueue)的核心技术,轻松应对千万级并发挑战。适用于高并发服务器、实时通信系统等场景,通过事件驱动、非阻塞I/O提升性能。源码级解析关键设计模式与优化技巧,值得收藏。

QuickCode的博客 566
上一篇: [转载]Linux TCP/IP 协议栈源码分析
下一篇: openwrt hotplug
wdsfup
博客等级 码龄20年 31粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值