计算机操作系统学习笔记(12)——I/O 多路复⽤:select/poll/epoll

简介: 计算机操作系统学习笔记(12)——I/O 多路复⽤:select/poll/epoll

这篇的内容相当重要的

一、最基本的 Socket 模型

Socket 的中⽂名叫作插⼝,双⽅要进⾏⽹络通信前,各⾃得创建⼀个 Socket,这相当于客户端和服务器都开了⼀个“⼝⼦”,双⽅读取和发送数据的时候都通过这个“⼝⼦”。这样⼀看,是不是觉得很像弄了⼀根⽹线,⼀头插在客户端,⼀头插在服务端,然后进⾏通信。


二、建立Socket的过程

创建 Socket 的时候,可以指定⽹络层使⽤的是 IPv4 还是 IPv6,传输层使⽤的是 TCP 还是UDP。这里以TCP举个例子

服务端⾸先调⽤ socket() 函数,创建⽹络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调⽤ bind() 函数,给这个 Socket 绑定⼀个 IP 地址和端⼝,绑定这两个的⽬的是什么?


绑定端⼝的⽬的:当内核收到 TCP 报⽂,通过 TCP 头⾥⾯的端⼝号,来找到我们的应⽤程序,然后把数据传递给我们。


绑定 IP 地址的⽬的:⼀台机器是可以有多个⽹卡的,每个⽹卡都有对应的 IP 地址,当绑定⼀个⽹卡时,内核在收到该⽹卡上的包,才会发给我们;


绑定完 IP 地址和端⼝后,就可以调⽤ listen() 函数进⾏监听,此时对应 TCP 状态图中的listen ,如果我们要判定服务器中⼀个⽹络程序有没有启动,可以通过 netstat 命令查看对应的端⼝号是否有被监听。


服务端进⼊了监听状态后,通过调⽤ accept() 函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。


那客户端是怎么发起连接的呢?客户端在创建好 Socket 后,调⽤ connect() 函数发起连接,该函数的参数要指明服务端的 IP 地址和端⼝号,然后万众期待的 TCP 三次握⼿就开始了。


注意,监听的 Socket 和真正⽤来传数据的 Socket 是两个:


⼀个叫作监听 Socket;

⼀个叫作已连接 Socket;


连接建⽴后,客户端和服务端就开始相互传输数据了,双⽅都可以通过 read() 和write() 函数来读写数据。



d470442dcfaa4f3eb21c68296c5d3e2e.png

三、如何服务更多的⽤户?

TCP Socket 调⽤流程是最简单、最基本的,它基本只能⼀对⼀通信,因为使⽤的是同步阻塞的⽅式。


基于Linux一切皆文件的概念,Socket 实际上是⼀个⽂件,也就会对应⼀个⽂件描述符。在 Linux 下,单个进程打开的⽂件描述符数是有限制的,没有经过修改的值⼀般都是 1024,不过我们可以通过 ulimit 增⼤⽂件描述符的数⽬;


每个 TCP 连接在内核中都有对应的数据结构,意味着每个连接都是会占⽤⼀定内存的;


四、多进程模型

基于最原始的阻塞⽹络 I/O, 如果服务器要⽀持多个客户端,其中⽐较传统的⽅式,就是使⽤多进程模型,也就是为每个客户端分配⼀个进程来处理请求。


服务器的主进程负责监听客户的连接,⼀旦与客户端连接完成,accept() 函数就会返回⼀个「已连接 Socket」,这时就通过 fork() 函数创建⼀个⼦进程,实际上就把⽗进程所有相关的东⻄都复制⼀份,根据返回值来区分是⽗进程还是⼦进程,如果返回值是 0,则是⼦进程;如果返回值是其他的整数,就是⽗进程。正因为⼦进程会复制⽗进程的⽂件描述符,于是就可以直接使⽤「已连接Socket 」和客户端通信了。

26c546e4c97e44219149087d26de13ba.png


当「⼦进程」退出时,如果不做好“回收”⼯作,就会变成僵⼫进程。有两种⽅式可以在⼦进程退出后回收资源,分别是调⽤ wait() 和 waitpid() 函数。


这种⽤多个进程来应付多个客户端的⽅式,当客户端数量⾼达⼀万时,肯定扛不住的,因为每产⽣⼀个进程,必会占据⼀定的系统资源,⽽且进程间上下⽂切换的“包袱”是很重的,性能会⼤打折扣。


五、多线程模型

既然进程间上下⽂切换的“包袱”很重,那我们就搞个⽐较轻量级的模型来应对多⽤户的请求—— 多线程模型。

因为线程共享些资源在上下⽂切换时是不需要切换,开销会小很多


当服务器与客户端 TCP 完成连接后,通过 pthread_create() 函数创建线程,然后将「已连接 Socket」的⽂件描述符传递给线程函数,接着在线程⾥和客户端进⾏通信,从⽽达到并发处理的⽬的。

1fc345c06c4c41ef86b80c9c9cde4c2a.png

这种方式还是有问题的。新到来⼀个 TCP 连接,就需要分配⼀个进程或者线程,那么如果要达到 C10K,意味着要⼀台机器维护 1 万个连接,相当于要维护 1万个进程/线程,操作系统就算死扛也是扛不住的。


六、I/O 多路复⽤

既然为每个请求分配⼀个进程/线程的⽅式不合适,那有没有可能只使⽤⼀个进程来维护多个Socket 呢?答案是有的,那就是 I/O 多路复⽤技术。


⼀个进程虽然任⼀时刻只能处理⼀个请求,但是处理每个请求的事件时,耗时控制在 1 毫秒以内,这样 1 秒内就可以处理上千个请求,把时间拉⻓来看,多个请求复⽤了⼀个进程,这就是多路复⽤,这种思想很类似⼀个 CPU 并发多个进程,所以也叫做时分多路复⽤。


select/poll/epoll 这是三个多路复⽤接⼝


select/poll

select 实现多路复⽤的⽅式是,将已连接的 Socket 都放到⼀个⽂件描述符集合,然后调⽤select 函数将⽂件描述符集合拷⻉到内核⾥,通过遍历⽂件描述符集合的⽅式,当检查到有事件产⽣后,将此 Socket 标记为可读或可写, 接着再把整个⽂件描述符集合拷⻉回⽤户态⾥,然后⽤户态还需要再通过遍历的⽅法找到可读或可写的 Socket,然后再对其处理。


需要进⾏ 2 次「遍历」⽂件描述符集合,⼀次是在内核态⾥,⼀个次是在⽤户态⾥ ,⽽且还会发⽣ 2 次「拷⻉」⽂件描述符集合,先从⽤户空间传⼊内核空间,由内核修改后,再传出到⽤户空间中。


所⽀持的⽂件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最⼤值为 1024


poll 和 select 并没有太⼤的本质区别,都是存储进程关注的 Socket集合,因此都需要遍历⽂件描述符集合来找到可读或可写的 Socket,⽽且也需要在⽤户态与内核态之间拷⻉⽂件描述符集合,这种⽅式随着并发数上来,性能的损耗会呈指数级增⻓。


很明显发现,select 和 poll 的缺陷在于,当客户端越多,也就是 Socket 集合越⼤,Socket集合的遍历和拷⻉会带来很⼤的开销,因此也很难应对 C10K。


epoll

epoll 通过两个⽅⾯,很好解决了 select/poll 的问题。


第⼀点,epoll 在内核⾥使⽤红⿊树来跟踪进程所有待检测的⽂件描述字,这样就不需要像 select/poll 每次操作时都传⼊整个 socket 集合,只需要传⼊⼀个待检测的 socket,减少了内核和⽤户空间⼤量的数据拷⻉和内存分配。


第⼆点, epoll 使⽤事件驱动的机制,内核⾥维护了⼀个链表来记录就绪事件,只会返回有事件发⽣的⽂件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,⼤⼤提⾼了检测的效率。


epoll 的⽅式即使监听的 Socket 数量越多的时候,效率不会⼤幅度降低,能够同时监听的Socket 的数⽬也⾮常的多了,上限就为系统定义的进程打开的最⼤⽂件描述符个数。因⽽,epoll 被称为解决 C10K 问题的利器。

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
6月前
|
存储 程序员
操作系统(15)-----I/O设备管理(万字总结~)(4)
操作系统(15)-----I/O设备管理(万字总结~)
69 2
|
6月前
|
存储 Unix 人机交互
操作系统(15)-----I/O设备管理(万字总结~)(1)
操作系统(15)-----I/O设备管理(万字总结~)
73 2
|
6月前
|
存储 网络协议 程序员
操作系统(15)-----I/O设备管理(万字总结~)(2)
操作系统(15)-----I/O设备管理(万字总结~)(2)
73 1
|
6月前
|
存储 Unix Linux
手写操作系统(4)——计算机是如何启动的?BIOS、GRUB、文件系统......
手写操作系统(4)——计算机是如何启动的?BIOS、GRUB、文件系统......
101 1
|
3月前
|
存储 算法 网络协议
了解操作系统的基本原理和常见操作,提高计算机使用效率
了解操作系统的基本原理和常见操作,提高计算机使用效率
45 4
|
4月前
|
Linux 调度
部署02-我们一般接触的是Mos和Wimdows这两款操作系统,很少接触到Linux,操作系统的概述,硬件是由计算机系统中由电子和机械,光电元件所组成的,CPU,内存,硬盘,软件是用户与计算机接口之间
部署02-我们一般接触的是Mos和Wimdows这两款操作系统,很少接触到Linux,操作系统的概述,硬件是由计算机系统中由电子和机械,光电元件所组成的,CPU,内存,硬盘,软件是用户与计算机接口之间
|
5月前
|
运维 安全 Linux
计算机架构“寒武纪爆发”,操作系统进化迸发中国浪潮
计算机架构“寒武纪爆发”,操作系统进化迸发中国浪潮
|
6月前
|
存储 算法 Linux
【计算机操作系统】深入探究CPU,PCB和进程工作原理
【计算机操作系统】深入探究CPU,PCB和进程工作原理
178 1
|
6月前
|
存储 缓存 算法
操作系统(15)-----I/O设备管理(万字总结~)(3)
操作系统(15)-----I/O设备管理(万字总结~)(3)
95 0
|
6月前
|
监控 Linux 调度
操作系统学习笔记(一)
在Linux中,使用`ps -aux | grep PID`来查看特定进程的状态,或者用`top`指令监控进程和内存。通过`cat 文件名 | grep 关键词`或`grep -i 关键词 文件名`搜索日志文件。`grep`是一个强大的文本搜索工具,支持多种参数,如`-i`忽略大小写,`-c`计数,`-f`从文件读取关键词。要临时更改主机名用`hostname 新主机名`,永久更改则用`hostnamectl set-hostname 新主机名`
43 0