第一次把Epoll讲这么详细的文章

从事服务端开发,少不了要接触网络编程。epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,nginx、Redis、Skynet 和大部分游戏服务器都使用到这一多路复用技术。

epoll 很重要,但是 epoll 与 select 的区别是什么呢?epoll 高效的原因是什么?

网上虽然也有不少讲解 epoll 的文章,但要么是过于浅显,或者陷入源码解析,很少能有通俗易懂的。笔者于是决定编写此文,让缺乏专业背景知识的读者也能够明白 epoll 的原理。

文章核心思想是:要让读者清晰明白 epoll 为什么性能好。

本文会从网卡接收数据的流程讲起,串联起 CPU 中断、操作系统进程调度等知识;再一步步分析阻塞接收数据、select 到 epoll 的进化过程;最后探究 epoll 的实现细节。

一、从网卡接收数据说起

下边是一个典型的计算机结构图,计算机由 CPU、存储器(内存)与网络接口等部件组成,了解 epoll 本质的第一步,要从硬件的角度看计算机怎样接收网络数据。

 

计算机结构图(图片来源:Linux内核完全注释之微型计算机组成结构)

下图展示了网卡接收数据的过程。

  • 在 ① 阶段,网卡收到网线传来的数据;
  • 经过 ② 阶段的硬件电路的传输;
  • 最终 ③ 阶段将数据写入到内存中的某个地址上。

这个过程涉及到 DMA 传输、IO 通路选择等硬件有关的知识,但我们只需知道:网卡会把接收到的数据写入内存


 
网卡接收数据的过程

通过硬件传输,网卡接收的数据存放到内存中,操作系统就可以去读取它们。

二、如何知道接收了数据?

了解 epoll 本质的第二步,要从 CPU 的角度来看数据接收。理解这个问题,要先了解一个概念——中断。

计算机执行程序时,会有优先级的需求。比如,当计算机收到断电信号时,它应立即去保存数据,保存数据的程序具有较高的优先级(电容可以保存少许电量,供 CPU 运行很短的一小段时间)。

一般而言,由硬件产生的信号需要 CPU 立马做出回应,不然数据可能就丢失了,所以它的优先级很高。CPU 理应中断掉正在执行的程序,去做出响应;当 CPU 完成对硬件的响应后,再重新执行用户程序。中断的过程如下图,它和函数调用差不多,只不过函数调用是事先定好位置,而中断的位置由“信号”决定。


 
中断程序调用

以键盘为例,当用户按下键盘某个按键时,键盘会给 CPU 的中断引脚发出一个高电平,CPU 能够捕获这个信号,然后执行键盘中断程序。下图展示了各种硬件通过中断与 CPU 交互的过程。


CPU 中断(图片来源:net.pku.edu.cn)

现在可以回答“如何知道接收了数据?”这个问题了:当网卡把数据写入到内存后,网卡向 CPU 发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据。

三、进程阻塞为什么不占用 CPU 资源?

了解 epoll 本质的第三步,要从操作系统进程调度的角度来看数据接收。阻塞是进程调度的关键一环࿰

好的,接下来我会详细介绍 epollepoll 是 Linux 内核提供的一种 I/O 事件通知机制,与 select 和 poll 相比,它具有更高的性能和更强的扩展性。 epoll 的特点如下: 1. 支持边缘触发和水平触发两种模式。 2. 采用基于事件驱动的方式,可以同时监听大量的文件描述符。 3. 支持添加、修改和删除事件,可以动态地改变监听的文件描述符。 4. 可以通过 epoll_wait 函数等待事件的发生,避免了轮询的效率问题。 epoll 的使用流程如下: 1. 调用 epoll_create 函数创建一个 epoll 实例。 2. 调用 epoll_ctl 函数向 epoll 实例添加、修改或删除事件。 3. 调用 epoll_wait 函数等待事件的发生。 4. 处理已经发生的事件。 下面是一个简单的使用 epoll 的示例: ```c #include <sys/epoll.h> int main() { int fd = socket(AF_INET, SOCK_STREAM, 0); struct epoll_event event, events[10]; int epfd, nfds; epfd = epoll_create(1); event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); while(1) { nfds = epoll_wait(epfd, events, 10, -1); for(int i = 0; i < nfds; i++) { if(events[i].data.fd == fd) { // 处理文件描述符 fd 的事件 } } } return 0; } ``` 在这个示例中,我们创建了一个 epoll 实例,并向其中添加了一个文件描述符,然后使用 epoll_wait 函数等待事件的发生,并处理已经发生的事件。 需要注意的是,epoll 的事件驱动模型并不是线程安全的,因此在多线程环境下需要进行同步处理。 希望这份介绍对你有所帮助。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值