Linux epoll 实现(一)

        本章主要介绍Linux epoll 的实现方式,但在此之前,我们首先要知道一个 socket 的创建流程,以及当数据就绪时 socket 如何被唤醒。当我们了解这些之后,再去理解 epoll 的实现,会更加深刻。

注:本章内容主要来源公众号《开发内功修炼》

socket 的创建

进程的结构

在这里,只介绍socket 相关的部分。

每个用户进程都会维护一份进程打开的文件列表。当创建一个 socket 时,即创建一个对应的socket 文件,并且在文件列表中维护。

struct socket

创建一个 socket ,即实例化一个 struct socket,这里我们只关心以下几个字段:

sk_receive_queue

接收队列,用于接收网络数据

sk_wq

等待队列,用于存放等待 socket 数据的对象

void(*sk_data_ready)(...)

数据就绪处理函数

其中 sk_data_ready 会默认设置成 sock_def_readable(),该函数用于唤起等待队列中的进程。

 socket 等待接收数据

        当 socket 创建完成,进⼊系统调⽤后,⽤户进程就进⼊到了内核态,通过执⾏⼀系列的内核协议层函数,然后到 socket 对象的接收队列中查看是否有数据,没有的话就把⾃⼰添加到 socket 对应的等待队列⾥,最后让出 CPU。这里我们主要关注等待项:

private

会被设置成当前进程的文件描述符

func

被设置 autoremove_wake_function,用于唤醒 private 的进程

         当数据到达网卡之后,会通过软中断接收数据,并将数据保存到 socket 的接收队列中,并执行 socket 中 sk_data_ready 设置的数据就绪数量函数(这里是 sock_def_readable())。 sock_def_readable 主要是获取 socket 等待队列中等待项,并唤起对应的进程。

小结

以上就是一个 socket 对象创建和接收数据的流程,总结来说:

  1. socket 创建时,会设置对应的数据就绪处理函数,当数据就绪时调用。
  2. 当 socket 接收队列没有数据或者数据不足够时,会将新建一个等待项,插入到等待队列,休眠,让出 CPU。
  3. 当 socket 接收到足够数据,会通过数据就绪处理函数,唤起等待队列中,等待项的进程。

接下来,将说明 epoll 基于上述的实现

EPOLL

这里不会具体介绍 EPOLL 的细节(具体细节将在下章介绍),只说明 epoll 对上述流程的改造。

1.epoll 相关的函数是如下三个:

epoll_create

创建一个 epoll 对象

epoll_ctl

向 epoll 对象中添加要管理的连接

epoll_wait

等待其管理的连接上的 IO 事件

2.首先在用户进程调用 epoll_create 时,内核会创建一个 struct eventpoll 的内核对象。并同样把它关联到当前进程的已打开文件列表中。

eventpoll 在文件列表:

 eventpoll 结构:

3.在创建 socket 除了struct socket 正常创建流程外,同时会通过 epoll_ctl 将 socket 添加的 eventpoll 中进行管理。在 epoll_ctl 执行的时候,内核为每一个 socket 上都添加了一个等待队列项。这里与前面的不同点是:

    (1)之前等待项的 private 指向 当前进程描述符,但在 epoll 中,private 指向 null。
    (2)之前等待项的 func 设置成  autoremove_wake_function,但在 epoll 中被设置成 ep_poll_callback。
    (3)会新增一个 base 参数,用于指向 epitem。

4.通过epoll_wait 获取 rdllist中就绪的 epitem,如果没有,创建一个 eventpoll 等待项,插入到eventpoll 的等待队列中,然后把自己阻塞掉,这里等待项主要是包括:

    (1)private 指向当前进程
    (2)func 设置成 default_wake_function 用于唤起进程。

5.数据就绪时,默认调用sock_def_readable,调用socket 等待队列中的等待项,在这里会调用 ep_poll_callback,在 ep_poll_callback 根据等待任务队列项上的额外的 base 指针可以找到 epitem, 进而也可以找到 eventpoll对象。首先它做的第一件事就是把自己的 epitem 添加到 epoll 的就绪队列中。接着它又会查看 eventpoll 对象上的等待队列里是否有等待项(epoll_wait 执行的时候会设置)。如果有等待项,那就查找到等待项里设置的回调函数。

6.唤起等待项中对应的进程,从 epoll_wait 函数继续执行,获取rdllist 中的 epitem,然后执行相关操作。

总结

        对比常规的 socket 和 epoll 的流程可以发现,其实 epoll 的本质是改变了  sock_def_readable 的 func 行为。 在常规的 socket 中, func 用于唤起进程,而在 epoll 中,被设置成了 ep_poll_callback 以执行 epoll 的相关行为。至于具体 epoll 介绍,将在下一章说明。

参考

  1. 你管这破玩意叫 IO 多路复用?
  2. 图解 | 深入揭秘 epoll 是如何实现 IO 多路复用的!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值