`
wzk
  • 浏览: 897 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

epoll 和 kqueue 的原理

阅读更多
源引:http://www.zhihu.com/question/20122137

首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。
不管是文件,还是套接字,还是管道,我们都可以把他们看作流。
之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据;通过write,我们可以往流写入数据。现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办?
阻塞。阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。
非阻塞忙轮询。接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”
很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。
大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。

为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的),当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。
假设有一个管道,进程A为管道的写入方,B为管道的读出方。
假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。
但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。
假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”
也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。
这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(注都是说的内核缓冲区,且这四个术语都是我生造的,仅为解释其原理而造)。这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。

然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。
于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论):
while true {
for i in stream[]; {
if i has data
read until unavailable
}
}
我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。这里要补充一点,阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(后文介绍的select以及epoll)处理甚至直接忽略。

为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。代码长这样:
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。再次
说了这么多,终于能好好解释epoll了
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(k),k为产生I/O事件的流的个数,也有认为O(1)的[更新 1])
在讨论epoll的实现细节之前,先把epoll的相关操作列出[更新 2]:
epoll_create 创建一个epoll对象,一般epollfd = epoll_create()
epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件
比如
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//有缓冲区内有数据时epoll_wait返回
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//缓冲区可写入时epoll_wait返回
epoll_wait(epollfd,...)等待直到注册的事件发生
(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。
一个epoll模式的代码大概的样子是:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till unavailable
}
}
限于篇幅,我只说这么多,以揭示原理性的东西,至于epoll的使用细节,请参考man和google,实现细节,请参阅linux kernel source。
======================================
[更新1]: 原文为O(1),但实际上O(k)更为准确
[更新2]: 原文所列第二点说法让人产生EPOLLIN/EPOLLOUT等同于“缓冲区非空”和“缓冲区非满”的事件,但并非如此,详细可以Google关于epoll的边缘触发和水平触发。
分享到:
评论

相关推荐

    IO复用:select,poll,epoll,kqueue的详细例子

    本文将深入探讨四种常见的IO复用机制:`select`、`poll`、`epoll`和`kqueue`,并结合源码分析如何在实际项目中应用它们。我们将通过分析`service.cpp`(服务端)和`client.cpp`(客户端)来理解这些机制的工作原理。...

    epoll-kqueue-c:IO多路复用脚本,可轻松在Mac和Linux上使用

    理解`epoll` 和 `kqueue` 的工作原理和使用方法对于开发高并发、高性能的网络服务至关重要。在实际应用中,它们常被用于服务器端编程,例如Web服务器、数据库服务器等。通过对`epoll-kqueue-c-main`的分析,我们可以...

    bsd kqueue

    相比于传统的`select()`和`poll()`,Kqueue具有更好的可扩展性和效率,尤其适用于需要同时监控大量描述符的情况。 #### 三、Kqueue的特点 1. **高效性**:Kqueue通过优化内部数据结构和算法设计,显著提高了在大...

    epoll 使用golang实现

    首先,我们来理解`epoll`的工作原理。`epoll`基于`IO多路复用`技术,它提供了一个接口,允许程序注册一组文件描述符(如套接字),然后等待这些描述符上的事件发生。当有事件发生时,`epoll_wait`函数会返回就绪的...

    c-event-machine:基于 Linux epoll 或 FreeBSD kqueue 的简单低级事件机

    2. **FreeBSD Kqueue**: Kqueue是FreeBSD操作系统中的一种事件通知机制,与Epoll类似,但具有更强的通用性和灵活性。Kqueue不仅可以用于文件描述符,还可以监控信号、进程、线程等。在c-event-machine中,kqueue被...

    C++开源协程库libco-原理与应用

    开源协程库libco——原理及应用 2016年11月26日 导论 C++来编写高性能的网络服务器程序...epoll/kqueue直接码起的时候尤其如此。即便使用libevent,libev golang近几年能够大规模流行起来呢?因为简单。这方面最突出的

    开源协程库libco

    描述中的“C++开源协程库libco-原理与应用”暗示了本文将深入探讨libco的工作原理和使用方法。libco提供了一种可以高效处理I/O操作的并发模型,其核心机制是基于Linux系统调用如epoll/kqueue,这表明它特别适合于...

    Redis深度历险:核心原理和应用实践.zip

    - **网络模型**:Redis使用单线程模型处理客户端请求,通过I/O多路复用(epoll/kqueue)实现高效的网络通信。 - **事务与Lua脚本**:Redis支持事务和内嵌的Lua脚本,保证操作的原子性,简化复杂的操作序列。 2. *...

    Lighttped 1.4.x Brief Analysis 原码分析,Chaoslawful著

    2. 高性能:它采用了多路复用(Multiplexing)技术,如epoll和kqueue,以提高并发处理能力。 3. 安全性:Lighttpd 提供了多种安全功能,如FastCGI、SSL/TLS支持,防止DDoS攻击等。 4. 扩展性:通过模块化设计,可以...

    libevent参考手册(中文版)+libevent源码深度剖析

    libevent提供了多种后端机制,如poll、epoll、kqueue等,以适应不同的操作系统。这些后端可以根据系统的特性自动选择最优的事件通知机制,确保最佳的性能。例如,在Linux系统中,epoll通常提供最好的性能,因为它...

    The C10K PROBLEM解决方案 流程图

    未来,随着网络技术和硬件性能的不断提升,解决C10K问题的方法也将不断演进,但核心原理和思路——即通过优化I/O处理机制和合理设计应用架构,以最小的资源消耗实现最大的并发处理能力——将始终是关键所在。

    LibEvent中文帮助文档_zhouyongyi

    这个库在处理大量并发连接时表现出色,因为它使用了非阻塞I/O和事件多路复用技术,如epoll、kqueue、poll或select。 《LibEvent中文帮助文档_zhouyongyi》是针对LibEvent的中文指南,由zhouyongyi提供,对于中文...

    libevent源码深度剖析.zip_libevent 原理_libevent原理_libevent源码_libevent编程_

    libevent的原理和源码分析对于深入理解网络编程的底层机制至关重要。 首先,libevent的核心是其事件模型。它基于事件驱动编程模型,这种模型能够高效地处理大量并发连接。当有事件发生时,libevent会调用相应的回调...

    Libevent学习手册(高清)+libevent源码深度剖析(很适合初学者)

    Libevent 的核心功能是它提供了一种抽象层,将底层的事件机制(如epoll、kqueue、select等)隐藏起来,让开发者无需关注具体的操作系统细节就能实现高性能的事件驱动编程。这种事件模型在构建高并发服务器、网络通信...

    腾讯开源协程库libco-原理及应用.pdf

    2. 同步与异步编程的对比:传统的事件驱动编程,如使用epoll/kqueue的I/O多路复用技术,提供的是非阻塞、异步编程接口,这要求开发者有与传统同步编程不同的思维模式。而go语言则利用协程实现了类似同步阻塞的网络...

    Memcached 原理和使用详解

    **Memcached原理** Memcached是一种高性能的分布式内存缓存...总的来说,Memcached作为一个简单而高效的分布式缓存系统,在现代Web应用中有着广泛的应用,通过理解和掌握其原理和使用技巧,可以有效地提升应用的性能。

    C++开源协程库libco

    C++作为一种高性能编程语言,在网络...通过对libco库的深入学习,开发者可以更好地理解和掌握C++协程编程的原理和实践方法,进而在开发高性能的网络服务器程序时能够利用协程库的优势,简化开发流程,提升程序性能。

    Memcached_原理和使用详解

    **Memcached原理和使用详解** Memcached是一款由LiveJournal的开发团队设计的高性能分布式内存缓存系统。它的主要目标是减少数据库的访问次数,通过在内存中缓存数据查询结果来提升动态Web应用的速度和可扩展性。...

    linux网络编程.zip

    6. **06libevent**: Libevent是一个跨平台的事件通知库,支持epoll、kqueue等多种事件模型。它简化了网络编程中事件驱动的实现,这部分可能介绍如何使用libevent处理网络事件,并创建高并发服务器。 7. **day07web...

Global site tag (gtag.js) - Google Analytics