网络IO模型之BIO、NIO、SELECT、EPOLL简析

铺垫

用户态和内核态

首先来看一Linux架构图: redis

从上图能够看到,系统调用把Linux系统最底下的内核和上面部分作了分隔,而分开的这两部分,上层“应用程序+库函数+Shell”就是用户空间,而底下的内核就是内核空间了。

在用户空间中,应用程序为了访问途中最底层的硬件资源,必须经过系统调用来让内核去操做全部的硬件资源,而后内核从硬件资源获取反馈后,将反馈再拿到内核空间,再从内核空间返回给用户空间的应用程序。网络

为何要区分用户空间和内核空间呢?
架构

在 CPU 的全部指令中,有些指令是很是危险的,若是错用,将致使系统崩溃,好比清内存、设置时钟等。若是容许全部的程序均可以使用这些指令,那么系统崩溃的几率将大大增长。
因此,CPU 将指令分为特权指令和非特权指令,对于那些危险的指令,只容许操做系统及其相关模块使用,普通应用程序只能使用那些不会形成灾难的指令。好比 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3。
其实 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别(Windows 系统也是同样的)。当进程运行在 Ring3 级别时被称为运行在用户态,而运行在 Ring0 级别时被称为运行在内核态。
在内核态下,进程运行在内核地址空间中,此时 CPU 能够执行任何指令。运行的代码也不受任何的限制,能够自由地访问任何有效地址,也能够直接进行端口的访问。
在用户态下,进程运行在用户地址空间中,被执行的代码要受到 CPU 的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中 I/O 许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。
对于之前的 DOS 操做系统来讲,是没有内核空间、用户空间以及内核态、用户态这些概念的。能够认为全部的代码都是运行在内核态的,于是用户编写的应用程序代码能够很容易的让操做系统崩溃掉。
对于 Linux 来讲,经过区份内核空间和用户空间的设计,隔离了操做系统代码(操做系统的代码要比应用程序的代码健壮不少)与应用程序代码。即使是单个应用程序出现错误也不会影响到操做系统的稳定性,这样其它的程序还能够正常的运行。socket

Linux 系统调用和库函数的区别函数

cpu中断

软中断
硬中断操作系统

有了上述的基本铺垫后,下面来解析下各网络IO模型的结构,这里咱们以redis服务为例。线程

BIO(Blocking IO)同步阻塞IO

    1. redis-server启动,调用系统调用函数socket来在内核空间建立一个文件描述符6 - fd6(仅有一个fd6但尚未实际内存地址),接着使用系同调用bind函数为该文件描述符绑定一个内存地址,而后再使用系统调用函数listen来监听fd6,最后使用accept来接收链接至fd6的链接。
    1. 一个redis-cli启动准备链接至redis-server,也就是要链接fd6,也会调用一系列系统函数,当步骤1阻塞中的accept收到该客户端发来的链接请求时会生成一个文件描述符fd7,而后会调用read函数来读取fd7文件描述符发送过来的消息,若是此时没有消息发送过来,read函数将一直阻塞直到有消息发送过来。
    1. 若是读取fd7的进程一直在阻塞当中的时候,有一个客户端来链接redis-server,那么该客户端将没法链接,由于原先的进程还在阻塞当中,须要等待处理完fd7这个客户端的请求(内核将接收到的fd7的数据从内核空间拷贝到用户空间)或者超时后,另外一个客户端才能链接进来 以上就是整个BIO的阻塞过程。

BIO的缺点显而易见了,一个进程只能去处理一个fd,若是有多个客户端链接,就会出现堵塞住,固然,能够启动多个redis-server进程或者线程来处理客户端链接,一旦请求量上来后,这样会浪费不少的系统资源。建立线程也须要系统调用设计

NIO(NoneBlocking IO)同步非阻塞IO

能够看出,NIO和BIO由图中得出的最大不一样就是在read读取上,BIO是redis-server调用read,等待读取消息,可是没有消息到达时,他会一直阻塞在那,直到有消息进来,他才读取后从内核态拷贝至用户态才算完事儿;而NIO是不断循环调用read来读取链接至redis-server的fd,若是有消息那就处理,须要将数据从内核空间拷贝至用户空间,因此这里会阻塞进程,若是没消息则不会阻塞,而是当即返回无数据的结果。

循环不断地调用read去检测数据是否准备好,是十分浪费系统资源的,若是1000个客户端链接中只有1个哭护短发送了消息,也就是白白浪费了999个系统调用read,并且每一次调用read,都要让系统内核去判断一次,加大了系统内核的压力。3d

Select 多路复用

select模式不像NIO模式,循环系统调用read来检测每个fd文件描述符,而是将全部链接进来的文件描述符都丢给内核,也就是调用 select系统调用函数,将全部链接过来的文件描述符一块发送给内核,此时select进入阻塞状态,而内核来监视全部的文件描述符是否有新消息到达,若是有文件描述符活跃,则select返回,而后redis-server再调用 read函数将消息从内核态拷贝到用户态。

select模式解决了NIO模式的循环调用问题,可是他把监视的循环丢给了内核来处理,这也是个问题,CPU就会被占用去主动遍历全部fdcode

Epoll 多路复用

Epoll模型是调用epoll_create来在内核中建立一个空间,而后再使用epoll_ctl来将链接过来的客户端文件描述符加到这个空间中,来一个加一个,若是有客户端发送消息,那么经过事件驱动,正在处理其余操做的cpu会收到一个软中断,来将有消息的fd移到内核中的另外一块空间中,而redis-server则不断调用epoll_wait来循环遍历有消息的这块内存,若是该内存中有fd,则调用read来读取数据。

Epoll的好处就是充分利用了cpu,不须要cpu主动去遍历全部客户端链接

相关文章
相关标签/搜索