今天下班早些来普及下nginx io模型:linux
用户空间与内核空间:nginx
如今操做系统都是采用虚拟存储器,那么对32位操做系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操做系统的核心是内核,独立于普通的应用程序,能够访问受保护的内存空间,也有访问底层硬件设备的全部权限。为了保证用户进程不能直接操做内核(kernel),保证内核的安全,操做系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操做系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。编程
进程切换:浏览器
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复之前挂起的某个进程的执行。这种行为被称为进程切换。所以能够说,任何进程都是在操做系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另外一个进程上运行,这个过程当中通过下面这些变化:
保存处理机上下文,包括程序计数器和其余寄存器。
更新PCB信息。
把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
选择另外一个进程执行,并更新其PCB。
更新内存管理的数据结构。
恢复处理机上下文。缓存
注:总而言之就是很耗资源,具体的能够参考这篇文章:安全
http://guojing.me/linux-kernel-architecture/posts/process-switch/
bash
进程阻塞:服务器
正在执行的进程,因为期待的某些事件未发生,如请求系统资源失败、等待某种操做的完成、新数据还没有到达或无新工做作等,则由系统自动执行阻塞原语(Block),使本身由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也所以只有处于运行态的进程(得到CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。网络
文件描述符:数据结构
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者建立一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写每每会围绕着文件描述符展开。可是文件描述符这一律念每每只适用于UNIX、Linux这样的操做系统。
缓存IO:
缓存 IO 又被称做标准 IO,大多数文件系统的默认 IO 操做都是缓存 IO。在 Linux 的缓存 IO 机制中,操做系统会将 IO 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间。
缓存IO的缺点:
数据在传输过程当中须要在应用程序地址空间和内核进行屡次数据拷贝操做,这些数据拷贝操做所带来的 CPU 以及内存开销是很是大的。
Linux IO模型:
网络IO的本质是socket的读取,socket在linux系统中被抽象为流,IO能够理解为对流的操做.对于一次IO访问,数据会先被拷到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间,因此说当一个read操做发生时,它会经理两个阶段:
1
2
|
第一阶段:等待数据准备 (Waiting
for
the data to be ready)。
第二阶段:将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。
|
对socket流而言:
1
2
|
第一步:一般涉及等待网络上的数据分组到达,而后被复制到内核的某个缓冲区。
第二步:把数据从内核缓冲区复制到应用进程缓冲区。
|
网络应用须要处理的无非就是两大类问题,网络IO,数据计算。相对于后者,网络IO的延迟,给应用带来的性能瓶颈大于后者。网络IO的模型大体有以下几种:
1
2
3
4
5
6
|
同步模型(synchronous IO)
阻塞IO(bloking IO)
非阻塞IO(non-blocking IO)
多路复用IO(multiplexing IO)
信号驱动式IO(signal-driven IO)
异步IO(asynchronous IO)
|
同步4个:阻塞IO、非阻塞IO、多路复用IO、信号驱使式IO
异步1个:异步IO
注:因为信号驱动式(signal driven IO)在实际中并不经常使用,因此我这只说起剩下的四种IO Model。
在深刻介绍Linux IO各类模型以前,让咱们先来探索一下基本 Linux IO 模型的简单矩阵。以下图所示:
每一个 IO 模型都有本身的使用模式,它们对于特定的应用程序都有本身的优势。
同步和异步主要针对C(client)端
同步:
所谓的同步,就是在c端发出一个功能调用时,在没有获得结果以前,该调用步返回,也就是说必须一件一件事作,等前一件事完了以后才作后一件事。
如:普通的B/S模式(同步):提交请求->等待服务器处理->处理完毕返回,这期间客户端浏览器不能干任何事
异步:
与同步相对。当C端一个异步过程调用发出以后,调用者不能当即获得结果,实际处理这个调用的部件在完成后,经过状态,通知和回调来通知调用者。
如:请求经过事件触发->服务器处理(浏览器仍然能够作其余事情)->处理完毕
阻塞和非阻塞主要针对S端(server)
阻塞:
阻塞调用是指调用结果返回以前,当前线程会被挂起(线程进入非可执行状态,在这个状态,cpu不会分配时间片,线程暂停运行)函数只有获得结果返回。
阻塞调用和同步调用的区别:对同步来讲,不少时候当前线程仍是激活的,只是逻辑上没有返回,如,在socket编程中调用recv函数,若是缓冲区没有数据,这个函数就会一直等待,直到有数据返回。而此前当前线程还有可能继续处理各类各样的消息。
阻塞的例子:好比去取A楼一层(假设是内核缓冲区)取快递,可是比不知道何时来,你有不能干别的事情,只能死等着可是能够睡觉(进程处于休眠状态),由于你知道快递把货送来时必定会给比大电话
非阻塞:
非阻塞与阻塞概念想对应,指在不能当即获得结果以前,该函数不会阻塞当前线程,而会当即返回。
非阻塞的例子:仍是等快递,若是用轮询的方式,每隔5分钟去A楼一层(内核缓冲区)去看快递来了没,没来,当即返回,若是快递来了,就放到A楼一层,等你去取。
对象是否处于阻塞模式和函数是否是阻塞调用有很强的相关性,但不是一一对应的。阻塞对象上能够有非阻塞的调用方式,咱们能够经过轮询状态,在适当的时候调用阻塞函数,就能够避免阻塞,而对于非阻塞对象,调用函数能够进入阻塞调用,对于select:
1:同步
1
|
我客户端(C端调用者)一个功能,该功能没有结束前,我死等结果。
|
2:异步
1
2
|
我(c端调用者)调用一个功能,不知道该功能结果,该功能有结果后通知我,即回调通知
同步和异步主要针对c端,可是跟s端不是彻底不要紧,同步和异步必须s端配合才能实现,同步和异步由c端控制,可是s端是否为阻塞仍是非阻塞,c端不关心。
|
3:阻塞
1
|
就是调用我(s端被调用者,函数),我(s端被调用者,函数)没有彻底接受完数据或者没有获得结果以前,我不会返回。
|
4:非阻塞
1
|
就是调用我(s端被调用者,函数),我(s端被调用者,函数)当即返回,经过
select
通知调用者
|
同步I/O与异步I/O的区别在与数据访问的时候进程是否阻塞
阻塞I/O与非阻塞I/O的区别在与:应该程序的调用是否当即返回。
阻塞和非阻塞是指server端的进程访问的数据若是还没有就绪,进程是否须要等待,简单说这至关于函数内部的实现区别,也就是未就绪时时直接返回仍是等待就绪。
就同步和异步是指client端访问数据的机制,同步通常指主动请求并等待I/O操做完毕的方式,当数据就绪后再读写额时候必须阻塞,异步则指主动请求数据后即可以继续处理其余任务,随后等待I/O,操做完毕的通知。
1、阻塞I/O模型:
简介:进程会一直阻塞,直到数据拷贝完成
应用程序调用一个I/O函数,致使应用程序阻塞,等待数据准备好,若是数据没有准备好,一直等待。。数据准备好,从内核拷贝到用户空间,I/O函数返回成功
阻塞I/O模型图:在调用recv()/recvfrom(),发生在内核中等待数据和复制数据过程。
当调用recv()函数时,系统首先检查是否有准备好的数据,若是数据没有准备好,那么系统就处于等待状态,当数据准备好后,将数据从系统缓冲区复制到用户空间,而后函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数处于等待状态
2、非阻塞I/O模型:
简介:咱们把一个套接口设置为非阻塞就是告诉内存,当所请求的I/O操做没法完成时,不要惊进程睡眠,而是返回一个错误,河阳I/O函数会不断的测试数据是否准备好,没有准备好,继续测试,直到数据准备好为止。在测试的过程当中会占用大量的CPU时间。
3、I/O复用模型:
简介:主要是select和epoll;对于一个I/O端口,两次调用,两次返回,比阻塞I/O并无什么优点,只是能实现同时对多个I/O端口进行监听。
I/O复用模型会调用select,poll函数,这几个函数也会使进程阻塞,可是和阻塞I/O不一样的,这个函数能够同时阻塞多个I/O操做,并且能够同时对多个读操做,多个写操做的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操做函数。
4、信号驱动I/O:
简介:两次调用,两次返回
首先容许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。昂数据准备好时,进程会收到一个SIGIO信号,能够在信号处理函数中调用I/O操做函数处理数据。
5、异步I/O模型:
简介:数据拷贝的时候进程无需阻塞
当一个异步过程调用发出后,调用者不能马上获得结果。实际处理这个调用的部件在完成后,经过状态,通知和回调通知调用者输入输出操做。
同步I/O引发进程阻塞,直到I/O操做完成
异步I/O不会引发进程阻塞
I/O复用先经过select调用阻塞
NGINX:
nginx 支持多种并发模型,并发模型的具体实现根据系统平台而有所不一样。
在支持多种并发模型的平台上,nginx 自动选择最高效的模型。但咱们也可使用 use 指令在配置文件中显式地定义某个并发模型。
NGINX中支持的并发模型:
select:
1
|
IO多路复用、标准并发模型。在编译 nginx 时,若是所使用的系统平台没有更高效的并发模型,
select
模块将被自动编译。configure 脚本的选项:--with-select_module 和 --without-select_module 可被用来强制性地开启或禁止
select
模块的编译
|
poll:
1
|
IO多路复用、标准并发模型。与
select
相似,在编译 nginx 时,若是所使用的系统平台没有更高效的并发模型,poll 模块将被自动编译。configure 脚本的选项:--with-poll_module 和 --without-poll_module 可用于强制性地开启或禁止 poll 模块的编译
|
epoll:
1
|
IO多路复用、高效并发模型,可在 Linux 2.6+ 及以上内核可使用
|
kqueue:
1
|
IO多路复用、高效并发模型,可在 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, and Mac OS X 平台中使用
|
/dev/poll:
1
|
高效并发模型,可在 Solaris 7 11
/99
+, HP
/UX
11.22+ (eventport), IRIX 6.5.15+, and Tru64 UNIX 5.1A+ 平台使用
|
eventport:
1
|
高效并发模型,可用于 Solaris 10 平台,PS:因为一些已知的问题,建议 使用
/dev/poll
替代。
|
为何epoll快?
比较一下Apache经常使用的select,和Nginx经常使用的epoll
select:
1
2
3
|
一、最大并发数限制,由于一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024
/2048
,所以 Select 模型的最大并发数就被相应限制了。本身改改这个 FD_SETSIZE ?想法虽好,但是先看看下面吧 …
二、效率问题,
select
每次调用都会线性扫描所有的 FD 集合,这样效率就会呈现线性降低,把 FD_SETSIZE 改大的后果就是,你们都慢慢来,什么?都超时了。
三、内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上
select
采起了内存拷贝方法,在FD很是多的时候,很是的耗费时间。
|
总结为:一、链接数受限 二、查找配对速度慢 三、数据由内核拷贝到用户态消耗时间
epoll:
1
2
3
|
一、Epoll 没有最大并发链接的限制,上限是最大能够打开文件的数目,这个数字通常远大于 2048, 通常来讲这个数目和系统内存关系很大 ,具体数目能够
cat
/proc/sys/fs/file-max
查看。
二、效率提高, Epoll 最大的优势就在于它只管你“活跃”的链接 ,而跟链接总数无关,所以在实际的网络环境中, Epoll 的效率就会远远高于
select
和 poll 。
三、内存共享, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
|
还不懂?
举个栗子
假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,可是不知道你具体住在哪里,因而大家约好了在A号楼门口见面.
若是你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能作别的事情,不难知道,这种方式的效率是低下的.
如今时代变化了,开始使用多路复用IO模型来处理这个问题.你告诉你的朋友来了A号楼找楼管大妈,让她告诉你该怎么走.这里的楼管大妈扮演的就是多路复用IO的角色.
select版大妈作的是以下的事情:好比同窗甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同窗甲,你等的朋友来了,因而在实际的代码中,select版大妈作的是如下的事情:
1
2
3
4
5
6
7
8
9
10
|
int n =
select
(&readset,NULL,NULL,100);
for
(int i = 0; n > 0; ++i)
{
if
(FD_ISSET(fdarray[i], &readset))
{
do_something(fdarray[i]);
--n;
}
}
|
epoll版大妈就比较先进了,她记下了同窗甲的信息,好比说他的房间号,那么等同窗甲的朋友到来时,只须要告诉该朋友同窗甲在哪一个房间便可,不用本身亲自带着人满大楼的找人了.因而epoll版大妈作的事情能够用以下的代码表示:
1
2
3
4
5
6
|
n=epoll_wait(epfd,events,20,500);
for
(i=0;i<n;++i)
{
do_something(events[n]);
}
|
在epoll中,关键的数据结构epoll_event定义以下:
1
2
3
4
5
6
7
8
9
10
|
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
|
能够看到,epoll_data是一个union结构体,它就是epoll版大妈用于保存同窗信息的结构体,它能够保存不少类型的信息:fd,指针,等等.有了这个结构体,epoll大妈能够不用吹灰之力就能够定位到同窗甲.别小看了这些效率的提升,在一个大规模并发的服务器中,轮询IO是最耗时间的操做之一.再回到那个例子中,若是每到来一个朋友楼管大妈都要全楼的查询同窗,那么处理的效率必然就低下了,过不久楼底就有很多的人了.
对比最先给出的阻塞IO的处理模型, 能够看到采用了多路复用IO以后, 程序能够自由的进行本身除了IO操做以外的工做, 只有到IO状态发生变化的时候由多路复用IO进行通知, 而后再采起相应的操做, 而不用一直阻塞等待IO状态发生变化了.
从上面的分析也能够看出,epoll比select的提升其实是一个用空间换时间思想的具体应用.