首先来看一Linux架构图: redis
在用户空间中,应用程序为了访问途中最底层的硬件资源,必须经过系统调用来让内核去操做全部的硬件资源,而后内核从硬件资源获取反馈后,将反馈再拿到内核空间,再从内核空间返回给用户空间的应用程序。网络
为何要区分用户空间和内核空间呢?
架构
在 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
有了上述的基本铺垫后,下面来解析下各网络IO模型的结构,这里咱们以redis服务为例。线程
socket
来在内核空间建立一个文件描述符6 - fd6(仅有一个fd6但尚未实际内存地址),接着使用系同调用bind
函数为该文件描述符绑定一个内存地址,而后再使用系统调用函数listen
来监听fd6,最后使用accept
来接收链接至fd6的链接。accept
收到该客户端发来的链接请求时会生成一个文件描述符fd7,而后会调用read
函数来读取fd7文件描述符发送过来的消息,若是此时没有消息发送过来,read
函数将一直阻塞直到有消息发送过来。BIO的缺点显而易见了,一个进程只能去处理一个fd,若是有多个客户端链接,就会出现堵塞住,固然,能够启动多个redis-server进程或者线程来处理客户端链接,一旦请求量上来后,这样会浪费不少的系统资源。建立线程也须要系统调用设计
循环不断地调用read去检测数据是否准备好,是十分浪费系统资源的,若是1000个客户端链接中只有1个哭护短发送了消息,也就是白白浪费了999个系统调用read,并且每一次调用read,都要让系统内核去判断一次,加大了系统内核的压力。3d
select
系统调用函数,将全部链接过来的文件描述符一块发送给内核,此时select进入阻塞状态,而内核来监视全部的文件描述符是否有新消息到达,若是有文件描述符活跃,则select返回,而后redis-server再调用
read
函数将消息从内核态拷贝到用户态。
select模式解决了NIO模式的循环调用问题,可是他把监视的循环丢给了内核来处理,这也是个问题,CPU就会被占用去主动遍历全部fdcode
Epoll的好处就是充分利用了cpu,不须要cpu主动去遍历全部客户端链接