前言
本文主要介绍Linux的五种网络模型,仅涉及理论知识,不包含实践尝试。可作为快速了解指南
正文
IO模型就是Linux系统在进行操作系统读写任务时,对执行进程和数据缓冲区的不同管理模式
为什么需要多种IO模型
操作系统为避免用户进程对操作系统的内核进程造成内存干扰,将计算机内存分为了内核空间和用户空间
因此,用户进程下所有涉及系统操作的命令(如本文涉及到的IO操作),都只能通过调用Linux提供的API来进行。于是,用户态时想要执行系统操作就分为两个阶段
- 用户进程调用操作系统对应API
- 操作系统启用内核进程完成操作
我们以IO为例,上述流程可粗略表示为如下图示: 由于缓存区和阻塞的存在,IO的效率取决于两个方面:
- 数据等待:内核等待数据就绪的时间如何利用(数据就绪的标志是其放入了内核区的buffer)
- 数据拷贝:内核与用户之间拷贝数据的时间如何利用
上述疑问决定了我们需要不同的IO模型来实现对不同执行效率或读写约束的需求
阻塞IO与非阻塞IO(以读取为例)
阻塞IO | 非阻塞IO |
---|
| | 内核态处于等待数据与拷贝数据两阶段时,用户态均阻塞 | 内核态处于等待数据阶段时,用户态空转不断轮循;内核态处于拷贝数据阶段时,用户态阻塞 | 阻塞IO的利用率最低 | 非阻塞IO并未带来效率提升,反而增加了内核态处理压力 |
IO多路复用
三种模式:select, poll, epoll
相同之处:第一阶段都是由用户态通过多方监听命令向内核态发起读数据的监听请求,当不存在数据时阻塞;当至少有一方数据就绪,返回可读标识;第二阶段由用户态根据可读标识发起读请求,内核态拷贝数据至用户态,用户态阻塞至数据拷贝完成。过程如下图所示,三种模式皆适用
PS: socket的fd是什么[1]
root@ubuntu:~# ll /proc/1583/fd
total 0
lrwx------ 1 root root 64 Jul 19 12:37 7 -> socket:[18892]
lrwx------ 1 root root 64 Jul 19 12:37 8 -> socket:[18893]
这里的7和8就是socket:[18892]和socket:[18893]对应的fd
select | poll | epoll |
---|
| 与select一致 | | 特色:采用1024bit的fd标识socket就绪情况 | 特色:采用自定义的pollfd数组标识socket就绪情况 | 特色:采用红黑树存储监听位置,采用list存储就绪位置 | 执行方法:由用户态在fd对应位置标识监听位置,调用select时将fd拷贝至内核态,内核遍历fd直到至少一个监听位置就绪,置该位为1,其余为0,返回fd至用户态 | 执行方法:与select一致,仅传递参数变为pollfd数组 | 执行方法:用户态调用epoll_create做初始化,在内核态内存建立红黑树和list;用户态调用epoll_ctl向红黑树中建立监听节点;用户态调用epoll_wait等待监听节点回调方法执行,内核态list拷贝至用户态 | 缺点:需要重复拷贝;无法直接识别就绪的socket位置,需要遍历得知;最多监听1024位 | 缺点:需要重复拷贝;无法直接识别就绪的socket位置,需要遍历得知;虽监听无上限,但链表遍历速度慢 | 优势:监听信息不需要重复拷贝,红黑树直接建立在内核态;可以直接通过list确定就绪的socket位置;监听无上限,且红黑树的遍历复杂度不会随着节点增加而无限增加 |
epoll的服务端流程
用文字描述epoll服务端简单实现流程(包括实现代码)[2]
流程图如下,可与上面的链接博客对照理解
信号驱动IO与异步IO
信号驱动IO | 异步IO |
---|
| | 缺点:采用队列存储信号,相比epoll的红黑树而言有上限,当并发量过大时,有溢出风险;由于并未区分监听与就绪队列,所以也存在与select和poll类似的频繁拷贝问题 | 缺点:异步IO第二阶段的非阻塞在高并发场景下会不停接收读写,导致内存占用过高,从而致使服务器崩溃,必须主动限流,增加了实现的复杂程度。而epoll等同步IO由于二阶段一个内核阻塞式执行读写任务,完成后才再次接收读写,一定程度上通过IO的高消耗缓解了高并发压力,在一些场景下不需要主动限流,使用更为简单 |
|