首页 > 其他 > 详细

Epoll的基本原理

时间:2021-03-30 22:15:25      阅读:54      评论:0      收藏:0      [点我收藏+]

计算机的硬件结构图

技术分享图片

网卡处理数据包的流程

技术分享图片

计算机执行程序时,会有优先级的需求。比如,当计算机收到中断电信号时,它应立即去保存数据,保存数据的程序具有较高的优先级。

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

现在可以回答本节提出的问题了:当网卡把数据写入到内存后,网卡向cpu发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据

进程阻塞为什么不占用cpu资源

阻塞是进程调度的关键一环,指的是进程在等某事件(如接受网络数据)发生之前的
等待状态,rec、select和epoll都是阻塞方法。下面是一段基础的网络编程代码。

# -*- coding: UTF-8 -*-
# 文件名:server.py

import socket               # 导入 socket 模块

s = socket.socket()         # 创建 socket 对象
host = socket.gethostname() # 获取本地主机名
port = 8081                # 设置端口
s.bind((host, port))        # 绑定端口

s.listen(5)                 # 等待客户端连接
while True:
    c,addr = s.accept()     # 建立客户端连接
    print ‘连接地址:‘, addr
    c.send(‘hello world‘)
    c.close()                # 关闭连接

技术分享图片

内核状态分为运行中、等待中、停止、僵尸。运行中的进程正在CPU中执行或者等待CPU分配时间片。等待中的进程在等某个事件的发生,比如网卡接收到新的数据发送中断到CPU。等待中的进程在所等待的事件没有到来之前不会被执行,因此也不会占用CPU的资源。

如上图所示,进程A包含了网络编程创建server并执行监听的过程。执行recv时,操作系统会将进程A从工作队列移动到该socket的等待队列中。操作系统中只剩下进程B和进程C继续执行。A被阻塞住等待事件的发生。如果有新的数据包到达网卡,会发生中断。socket接收到数据之后,操作系统把A进程放回工作队列继续执行,处理数据。

内核接受网络数据的过程

技术分享图片

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

服务端需要管理多个客户端连接,而recv只能监视单个socket,这种矛盾下,人们开始寻找监视多个socket的方法。epoll的要义是高效的监视多个socket。从历史发展角度看,必然先出现一种不太高效的方法,人们再加以改进。只有先理解了不太高效的方法,才能够理解epoll的本质。
假如能够预先传入一个socket列表,如果列表中的socket都没有数据,挂起进程,直到有一个socket收到数据,唤醒进程。这种方法很直接,也是select的设计思想。
为方便理解,我们先复习select的用法。在如下的代码中,先准备一个数组(下面代码中的fds),让fds存放着所有需要监视的socket。然后调用select,如果fds中的所有socket都没有数据,select会阻塞,直到有一个socket接收到数据,select返回,唤醒进程。用户可以遍历fds,通过FD_ISSET判断具体哪个socket收到数据,然后做出处理。
技术分享图片

select的流程

技术分享图片

  • select的实现思路很直接。假如程序同时监视如下图的sock1、sock2和sock3三个socket,那么在调用select之后,操作系统把进程A分别加入这三个socket的等待队列中。
  • 当任何一个socket收到数据后,中断程序将唤起进程。下图展示了sock2接收到了数据的处理流程。
  • 所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面。遍历所有的socket判断是不是有事件。

select遇到的问题

其一,每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。

其二,进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。
那么,有没有减少遍历的方法?有没有保存就绪socket的方法?这两个问题便是epoll技术要解决的。

Epoll的设计思路

功能分离

select低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。如下图所示,每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程。显而易见的,效率就能得到提升。

就绪列表

select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。

epoll的原理和流程

创建epoll对象

如下图所示,当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。技术分享图片

维护监视队列

创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。当socket收到数据后,中断程序会操作eventpoll对象,而不是直接操作进程。

Epoll的基本原理

原文:https://www.cnblogs.com/zhouqi0505/p/14597793.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!