Linux 内核理解epoll

Linux 下的五种 I/O 模型


image.png

总的来说,阻塞 IO 就是 JDK 里的 BIO 编程,IO 复用就是 JDK 里的 NIO 编程,Linux 下异
步 IO 的实现建立在 epoll 之上,是个伪异步实现,而且相比 IO 复用,没有体现出性能优势,
使用不广。非阻塞 IO 使用轮询模式,会不断检测是否有数据到达,大量的占用 CPU 的时间,
是绝不被推荐的模型。信号驱动 IO 需要在网络通信时额外安装信号处理函数,使用也不广
泛。


image.png

阻塞 IO 模型


image.png

I/O 复用模型
比较上面两张图,IO 复用需要使用两个系统调用(select 和 recvfrom),而 blocking IO 只
调用了一个系统调用(recvfrom)。但是,用 select 的优势在于它可以同时处理多个 connection。
所以,如果处理的连接数不是很高的话,使用 select/epoll 的 web server 不一定比使用
multi-threading + blocking IO 的 web server 性能更好,可能延迟还更大。select/epoll 的优势
并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

Linux 下的 IO 复用编程

select,poll,epoll 都是 IO 多路复用的机制。I/O 多路复用就是通过一种机制,一个进
程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序
进行相应的读写操作。但 select,poll,epoll 本质上都是同步 I/O,因为他们都需要在读写事
件就绪后自己负责进行读写,并等待读写完成。

select int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval
*timeout);

select 函数监视的文件描述符分 3 类,分别是 writefds、readfds、和 exceptfds。调用后
select 函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有 except),或者超时
(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。当 select 函数返回后,
可以 通过遍历 fdset,来找到就绪的描述符。
select 目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select 的
一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,
可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
poll

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

不同与 select 使用三个位图来表示三个 fdset 的方式,poll 使用一个 pollfd 的指针实现。
pollfd 结构包含了要监视的 event 和发生的 event,不再使用 select“参数-值”传递的方
式。同时,pollfd 并没有最大数量限制(但是数量过大后性能也是会下降)。 和 select 函数
一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。
epoll
epoll 是在 2.6 内核中提出的,是之前的 select 和 poll 的增强版本。相对于 select 和 poll
来说,可以看到 epoll 做了更细致的分解,包含了三个方法,使用上更加灵活。

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);//等待感兴趣的事件跟nio的选择器功能相似

select、poll、epoll 的比较
select,poll,epoll 都是 操作系统实现 IO 多路复用的机制。 我们知道,I/O 多路复用
就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),
能够通知程序进行相应的读写操作。那么这三种机制有什么区别呢

1、支持一个进程所能打开的最大连接数
select 底层基于数组(连接数有限),默认1024个

单个进程所能打开的最大连接数有 FD_SETSIZE 宏定义,其大小是 32
个整数的大小(在 32 位的机器上,大小就是 32*32,同理 64 位机器上
FD_SETSIZE 为 32*64),当然我们可以对进行修改,然后重新编译内核,
但是性能可能会受到影响

poll 底层基于链表

poll 本质上和 select 没有区别,但是它没有最大连接数的限制,原因
是它是基于链表来存储的

epoll

虽然连接数基本上只受限于机器的内存大小

2、FD 剧增后带来的 IO 效率问题
select

因为每次调用时都会对连接进行线性遍历,所以随着 FD 的增加会造
成遍历速度慢的“线性下降性能问题”。

poll 同select一样

epoll 回调函数实现

因为 epoll 内核中实现是根据每个 fd 上的 callback 函数来实现的,只
有活跃的 socket 才会主动调用 callback,所以在活跃 socket 较少的情况下,
使用 epoll 没有前面两者的线性下降的性能问题,但是所有 socket 都很活
跃的情况下,可能会有性能问题

3、 消息传递方式
select/poll 内核需要将消息传递到用户空间,都需要内核拷贝动作

epoll

epoll 通过内核和用户空间共享一块内存来实现的。

==============================================
从网卡接收数据说起
网卡收到网线传来的数据;经过硬件电路的传输;最终将数据写入到内存中的某个地址
上。这个过程涉及到 DMA 传输、IO 通路选择等硬件有关的知识,但我们只需知道:网卡会
把接收到的数据写入内存。操作系统就可以去读取它们。

如何知道接收了数据?
CPU 和操作系统如何知道网络上有数据要接收?使用中断机制。

进程阻塞
cpu调度工作队列上的进程,当cpu时间片切换后将进程的引用指向socket的等待队列列表中

image.png

同时监视多个 socket 的简单方法


image.png

epoll 的原理和流程
当某个进程调用 epoll_create 方法时,内核会创建一个 eventpoll 对象(也就是程序中
epfd 所代表的对象)。eventpoll 对象也是文件系统中的一员,和 socket 一样,它也会有等
待队列。
创建 epoll 对象后,可以用 epoll_ctl 添加或删除所要监听的 socket。以添加 socket 为例,
如下图,如果通过 epoll_ctl 添加 sock1、sock2 和 sock3 的监视,内核会将 eventpoll 添加到
这三个 socket 的等待队列中。


image.png

当 socket 收到数据后,中断程序会操作 eventpoll 对象,而不是直接操作进程。中断程
序会给 eventpoll 的“就绪列表”添加 socket 引用。如下图展示的是 sock2 和 sock3 收到数
据后,中断程序让 rdlist 引用这两个 socket。


image.png

eventpoll 对象相当于是 socket 和进程之间的中介,socket 的数据接收并不直接影响进
程,而是通过改变 eventpoll 的就绪列表来改变进程状态。
当程序执行到 epoll_wait 时,如果 rdlist 已经引用了 socket,那么 epoll_wait 直接返回,
如果 rdlist 为空,阻塞进程。
假设计算机中正在运行进程 A 和进程 B,在某时刻进程 A 运行到了 epoll_wait 语句。如
下图所示,内核会将进程 A 放入 eventpoll 的等待队列中,阻塞进程。


image.png

当 socket 接收到数据,中断程序一方面修改 rdlist,另一方面唤醒 eventpoll 等待队列中
的进程,进程 A 再次进入运行状态。也因为 rdlist 的存在,进程 A 可以知道哪些 socket 发生
了变化。

数据结构
epoll 使用双向链表来实现就绪队列(rdlist等待队列)
红黑树存储 epoll_ctl 传入的soket

总结

当某一进程调用 epoll_create 方法时,Linux 内核会创建一个 eventpoll 结构体,在内核
cache 里建了个红黑树用于存储以后 epoll_ctl 传来的 socket 外,还会再建立一个 rdllist 双向
链表,用于存储准备就绪的事件,当 epoll_wait 调用时,仅仅观察这个 rdllist 双向链表里有
没有数据即可。有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数据
也返回。
同时,所有添加到 epoll 中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是
说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做 ep_poll_callback,
它会把这样的事件放到上面的 rdllist 双向链表中。
当调用 epoll_wait 检查是否有发生事件的连接时,只是检查 eventpoll 对象中的 rdllist
双向链表是否有 epitem 元素而已,如果 rdllist 链表不为空,则这里的事件复制到用户态内
存(使用共享内存提高效率)中,同时将事件数量返回给用户。因此 epoll_waitx 效率非常
高,可以轻易地处理百万级别的并发连接。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容