内容目录

前言早期的UnixTCP/IP诞生后终端复用套接字章节回顾结论引用

前言

最近我一直在思考 Linux 中的多路复用,即 epoll(7)[1]系统调用。我很好奇 epoll与Windows等操作系统的iocp 或 macOS等操作系统的kqueue相比,是更好还是更差呢?我想知道基于批处理 epoll_ctl 调用是否性能更佳。在我们开始认真讨论之前,我们不妨往前追溯下,先了解一些背景信息。首要问题是----文件描述符多路复用是 Unix 设计理念的偏差还是温和扩展?

要回答这些问题,我们必须首先讨论 epoll 的前身:select(2)[2]系统调用。正好趁此机会对Unix系统做一次考古!

在 1960 年代中期,分时系统仍是最近的发明。与以前的批处理系统相比,时间共享确实是革命性的。它大大减少了程序编写和结果输出之间的时间浪费。批处理意味着经常要等待数小时才能看到并判断程序的运行结果。

早期的Unix

1970 年开发出了 Unix 的第一个版本。需要强调的是:Unix 不是凭空创造出来的----它试图解决批处理系统存在的问题。其目的是创造一个更好的、多用户、分时的环境,以加快最常见的任务处理。“常见任务”主要是:执行需要大量 CPU 计算和大量磁盘访问的程序。

在当时,一个程序被执行时,它会在以下几种事件上“停止”(阻塞):

  • 等待 CPU

  • 等待磁盘 I/O

  • 等待用户输入(等待 shell 命令)或控制台(打印数据太快)

在Linux 进程状态中,上述“停止”表示为:R、D、S 三种进程状态码。

1进程状态代码
2    R 运行或可运行状态(在运行队列上)
3    D 不可中断的睡眠状态(通常是IO)
4    S 可中断的睡眠状态(等待事件完成)
5    Z 已失效/僵尸状态,进程结束但尚未消亡(未被父进程回收)
6    T 停止,要么是由于作业控制信号,要么是因为
7       它正在被调试追踪
8    [...]

一个进程的生命周期以 R“运行”状态开始,以其父进程从 Z“僵尸”状态回收它后结束。下图是其状态流转


早期 Unix 中的进程真的不能做得更多。有一个pipe(2)[匿名管道],又抽象出一个叫命名管道的技术,但仅此而已。上一个匿名管道的stdin(标准输入)可以作为下一个管道的stdout(标准输出)或stderr(标准错误输出),如下图所示

让我们仔细看看 pipe(2)[3]。论文“UNIX 分时系统:回顾”[4]的作者Ritchie于 1978 年在其论文中写到:

在没有通用的进程间消息工具,甚至没有信号量等有限通信方案的情况下。事实证明,上述管道机制足以实现密切相关的协作进程之间所需的任何通信。[…] 但是管道在与守护进程(用来为多个用户服务)进行通信时没有任何用处。

在这里,Ritchie 似乎已经确认同步管道足以作为基本的进程间通信设施。

可能已经足够了!在操作系统BSD中,进程被限制最多有 20 个文件描述符。每个用户被限制为 20 个并发进程。这些系统真的很简陋。它们也不需要 IPC(进程间通信) 或复杂的 I/O。

例如,在早期的 Unix 中,没有文件描述符多路复用的概念。一个很好的例子是cu命令[5],全称是Call Unix 。其手册页讲到:

当与远程系统建立连接时,cu命令 forks成两个进程。一个从端口读取并写入终端,而另一个从终端读取并写入端口。

这是有道理的,因为所有的 I/O 都被阻塞了,让操作系统同时使用read方法来读取、write方法来写入的唯一途径是使用两个进程。

附带说明一下,如果您是 Golang 程序员,这可能听起来很熟悉。在 Golang 中,读写调用通常是阻塞的,所以当你想同时读写时,你不得不使用两个协程。

TCP/IP诞生后

这一切都在 1983 年随着 4.2BSD 的发布而改变。此版本引入了 TCP/IP 堆栈的早期实现,最重要的---- BSD 套接字 API。

尽管今天我们认为 BSD 套接字 API 存在是理所当然的,但其API的设计是正确的吗?STREAMS 框架是 System V Revision 3 上的比较完善的API 设计。

1在计算机网络中,STREAMS 是 Unix System V 中用于实现字符设备驱动程序、网络协议和进程间通信的本地框架。 在这个框架中,流是在程序和设备驱动程序之间(或在一对程序之间)传递消息的协程链。
2STREAMS 的设计是一种模块化架构,用于在内核和设备驱动程序之间实现全双工 I/O。 它最常用于开发终端 I/O(线路规程)和网络子系统。

随后 BSD 套接字 API 出现 select() 这个系统调用。为什么它的出现是有必要的呢?

我一直认为编写网络服务器的“正确”Unix 方法是为每个连接创建一个工作进程。在 TCP/IP 服务器的情况下,这代表着 accept-and-fork 模型:

1// 端口绑定2sd = bind();3while (1) {4    // 接受连接请求5    cd = accept(sd);6    // fork函数返回两个值,对于子进程,返回0; 父进程,返回子进程ID.7    if (fork() == 0) {8        close(sd);9        // TODO:worker进程处理套接字“cd“相关逻辑.
10        exit(0);
11    }
12    // TODO:回到“accept”循环,避免泄漏套接字“cd”相关逻辑。
13    close(cd);
14}

虽然这个模型可能足以编写基本的网络服务 ,但对于复杂程序来说还不够。

终端复用

1983 年左右,贝尔实验室的Rob Pike 正在开发 Blit----第八代Unix实验版(Research Unix 8th Edition)的一个可编程位图图形终端。

Blit 显然做了终端多路复用。它允许用户通过单个串行链路与多个终端连接后进行交互。

我向 Pike 先生询问了 select 的历史:

正如你所说的那样,Accept-and-fork 使得多个客户端无法在服务器上共享状态。这不仅仅是关于网络, Blit也受其影响。

虽然运行两个同步进程来为 cu (早期的Unix一章中有说到)供电就足够了,但不足以为 Blit 供电。Blit 确实需要某种套接字多路复用工具才能顺利工作。

有人可能会尝试扩展 cu 模型并将文件描述符和多路复用器组合在一起,方法是生成多个阻塞 I/O 的进程并让它们在某种 IPC 上同步。

不幸的是,在BSD上没有适合的 IPC 机制。System V(Unix操作系统的一个版本)的 IPC 于 1983 年 1 月发布,但在与 BSD 上实现相比没有任何的可比性。4.2BSD的手册页上也找不到任何真正的 IPC。

由于缺乏任何严格的 IPC 机制,Blit 似乎只需要 select 就可以进行控制台多路复用。

套接字

Kirk McKusick(美国著名计算机科学家,致力于BSD UNIX相关工作)解释了为什么会出现select:

引入 select 是为了允许应用程序进行IO多路复用。

你可以思考下:有一个简单的应用程序,例如远程登录。它的descriptor(描述符)可以对终端设备和双向套接字进行读写。这个程序可以读取终端键盘的字符并将它们写入套接字,也可以从套接字读取字符并写入终端。读取没有数据的空描述符导致应用程序阻塞,直到数据到达。应用程序不知道是从终端读取还是从套接字读取,如果猜错将也导致错误地阻塞。所以 select 登场了,它可以帮助程序找出哪个描述符已经准备好读取数据。如果两者都没有,程序会被 select 堵塞住,直到数据到达。然后select会去唤醒程序并告诉它哪个描述符有数据要读取。

[…] 非阻塞是在 select 的同时添加的。但是在读取描述符时使用非阻塞IO并不好。因为你需要写一个for循环来读取每个描述符的数据,要决定什么时候暂停对每个描述符的读取,还要考虑多久读一次描述符,这个过程很复杂,相比而言Select 的效率要高得多。

Select 还允许你创建一个单独的 inetd 守护程序,而不必为每个服务都有一个单独的守护程序。(inetd守护程序通过仅在需要时调用其他守护程序以及通过在内部提供几个简单的 Internet 服务而不调用其他守护程序来减少系统负载)

McKusick 先生确认非阻塞 I/O 在 select 之前根本不存在。此外,他引用了 cu 终端用例----如果没有 I/O 多路复用,很难编写 telnet 客户端。最后,他提到了 inetd守护进程,虽然后来在 4.3BSD 中引入了它,但如果没有 select,它是不可能实现的。

章节回顾

必须运行两个进程才能让 cu 工作是一种 hack。由于缺乏任何严格的 IPC,所以如果没有select,就不可能在 Blit 中模拟套接字多路复用。

此外,还需要 select 来实现 inetd。在架构级别 select 需要实现有状态的服务器,允许客户端连接之间的一些状态共享。

这是 1966 年“UNIX 分时系统:回顾”页面的另一个片段:

[在 UNIX 中] 输入和输出通常看起来是同步的;程序得先等 I/O 完成。[…] 仍然有一些特殊的应用程序希望在多个流上启动 I/O 并延迟直到仅在其中一个流上完成操作。当流的数量很少时,可以用几个进程来模拟这种用法。然而,Arpanet UNIX ncp(网络控制程序)接口的作者认为真正的异步 I/O 会显著改进他们的实现。

早期的 Unix 系统非常基础,根本不需要 select。并不是说C语言中的阻塞 I/O 模型被认为是每个人的最佳编程范式。这个模型很有意义,因为你所能做的只是对文件进行简单的操作。

这一切都随着网络的出现而改变。网络应用程序需要诸如 inetd、有状态服务器和终端仿真器(如 telnet)之类的东西。如果操作系统不允许套接字多路复用,这些事情将很难实现。

结论

在这次讨论中,我害怕说出核心问题。Unix 进程是否打算成为 CSP 风格的进程?文件描述符是 CSP 派生的“channels"(通道)吗?select 是否等同于 ALT 语句?

1来自维基百科:
2
3通信顺序进程 (CSP) 是一种用于描述并发系统中交互模式的正式语言。 它是基于通过通道传递消息的并发数学理论家族的成员,称为进程代数或进程演算。CSP 在指令式过程式编程语言的设计中具有很大的影响力,也影响了 Limbo、RaftLib、Erlang、Go、Crystal等编程语言的设计。

我想不是的。即使有设计相似之处,它们也是偶然的。因为文件描述符出现的时间比 CSP 论文早。

似乎套接字 API 的发展完全脱离了普通的程序员,它不像CSP编程范式那样让普通程序员简单快速地编程并发程序。虽然很可惜,但看到一个与用户空间程序的编程范式一致的操作系统会很有趣。

引用

[1]https://man7.org/linux/man-pages/man7/epoll.7.html
[2]https://man7.org/linux/man-pages/man2/select.2.html
[3]https://linux.die.net/man/2/pipe
[4]https://ia601600.us.archive.org/30/items/bstj57-6-1947/bstj57-6-1947_text.pdf
[5]https://www.computerhope.com/unix/ucu.htm

Linux IO多路复用之Select简史相关推荐

  1. 聊聊IO多路复用之select、poll、epoll详解

    聊聊IO多路复用之select.poll.epoll详解 2016/04/22 · IT技术 · 1 评论 · epoll, IO多路复用, poll, select 分享到:0 本文作者: 伯乐在线 ...

  2. (转载) Linux IO模式及 select、poll、epoll详解

    注:本文是对众多博客的学习和总结,可能存在理解错误.请带着怀疑的眼光,同时如果有错误希望能指出. 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案 ...

  3. Linux IO模式及 select、poll、epoll详解

    https://segmentfault.com/a/1190000003063859 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的. ...

  4. 【python】-- IO多路复用(select、poll、epoll)介绍及实现

    IO多路复用(select.poll.epoll)介绍及select.epoll的实现 IO多路复用中包括 select.pool.epoll,这些都属于同步,还不属于异步 一.IO多路复用介绍 1. ...

  5. IO多路复用中select、poll、epoll之间的区别

    本文来说下IO多路复用中select.poll.epoll之间的区别 文章目录 什么是IO多路复用 为什么有IO多路复用机制 同步阻塞(BIO) 同步非阻塞(NIO) IO多路复用(现在的做法) 3种 ...

  6. Python之进程+线程+协程(事件驱动模型、IO多路复用、select与epoll)

    文章目录 一.事件驱动模型 二.IO多路复用 本篇文章是关于涉及网络编程与协程.进程之间结合的内容,其中事件驱动模型.IO多路复用.select与epoll的使用等方面的知识 一.事件驱动模型 1.事 ...

  7. Linux IO模式及select、poll、epoll实现nio详解

    注:本文是对众多博客的学习和总结,可能存在理解错误.请带着怀疑的眼光,同时如果有错误希望能指出. 转自:https://segmentfault.com/a/1190000003063859 同步IO ...

  8. IO多路复用之select全面总结(必看篇)

    转载:http://www.jb51.net/article/101057.htm 1.基本概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程.IO多路复用适用如 ...

  9. linux IO多路复用 select epoll

    概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程 通俗理解(摘自网上一大神) 这些名词比较绕口,理解涵义就好.一个epoll场景:一个酒吧服务员(一个线程),前 ...

最新文章

  1. boost::asio中的C/S同步实例源码
  2. python 模板引擎 对比_Python Web开发模板引擎优缺点总结
  3. Kali Linux 安全渗透教程第五更1.4 安装Kali Linux
  4. RedHat Satellite 弃 MongoDB ,全面改用 PostgreSQL
  5. P4989-二进制之谜【堆,贪心】
  6. inputstreamreader未关闭会导致oom_Linux内核OOM机制分析和防止进程被OOM杀死的方法...
  7. C++的四种强制类型转换
  8. 数据结构与算法之美-排序(下)
  9. Android 的主题的演变
  10. 床车长时间自驾游,大家晚饭后至睡觉前这段时间都在忙什么?
  11. Open3d之对点云进行DBSCAN 聚类
  12. Liberty glance 新功能 healthcheck
  13. 对网站商城源码的研究分析 分享大量源码下载
  14. MR 眼镜会取代手机成为下一代终端,中国产业有机会领先世界
  15. 京东登录界面html css,css制作京东登录页面 css样式,京东店面装修怎么写CSS样式?...
  16. 谷歌浏览器设置免跨域 Mac
  17. 整理的最新的前端面试题必问集锦 (持续更新)
  18. 将OPenCV的Dll 动态连接库改名字,变成自己想要的动态连接库名字
  19. 表格制作过程html,HTML进行表格制作
  20. elasticsearch清除过期数据

热门文章

  1. 电梯为什么显示停止服务器,教你奥的斯服务器怎么看故障
  2. 网课谁的最好_报补习班,还是“上网课”?利用课余时间提高成绩,网课更加适合...
  3. 如何让Python不回显获取密码输入
  4. 51单片机怎么编程,有什么好的课程?
  5. 关于学习Python的一点学习总结(28->收集参数及分配参数)
  6. 图论500 ---- HDU3631 Shortest Path Floyed 插点法 真正了解Floyed
  7. 【图论专题】图的存储与遍历(最小环、所有环的大小)
  8. 矢量对比_「插画原画必学教程」ps入门——03 图层、位图矢量图、调色
  9. android ble5.0添加扫描过滤,bluetooth-lowenergy
  10. shell实行mysql语句_【Mysql】shell运行mysql的sql语句_MySQL