一 Linux IO基础原理
应用程序的IO读写,依赖于底层操作系统的IO读写,它是通过操作系统的两大系统调用Read/Write实现的。在Linux操作系统中设置了唯一的一个内核缓冲区,并为每个上层应用程序设置一个用户缓冲区,当应用程序进行数据读取时,内核将数据从内核缓冲区复制到用户缓冲区,当应用程序进行数据写入时,内核将数据从用户缓冲区复制到内核缓冲区,设置缓冲区的目的是减少性能消耗,因为直接对外部设备进行IO 读写会造成操作系统中断,操作系统频繁访问外部设备会,会造成不必要的性能开销。
Linux IO读取操作流程,分为两个阶段,第一个阶段:等待数据准备好,它是等待数据从网络中到达网卡,操作系统将数据从网卡复制到内核缓冲区;第二个阶段:内核复制数据,内核将数据从内核缓冲区拷贝到用户缓冲区,供应用程序使用。
Linux IO写入操作流程,分为连个阶段,第一个阶段:内核复制数据,内核将数据从应用程序的用户缓冲区拷贝到内核的内核缓冲区;第二个阶段:操作系统将内核缓冲区的数据复制到网卡,网卡基于底层通信协议将数据发送到目标客户端。
二 Linux IO模型
根据阻塞/非阻塞、同步/异步常用的Linux IO模型有五种:同步阻塞IO、同步非阻塞IO、IO多路复用、信号驱动IO和异步IO,除了异步IO其余的四种IO模型都是同步IO。
阻塞/非阻塞是指在应用程序进行系统调用后,是否需要等待内核IO操作彻底完成,如果需要等待内核IO操作彻底完成则为阻塞IO,如果不需要等待内核IO操作彻底完成则为非阻塞IO。阻塞/非阻塞指的是应用程序线程的阻塞状态。
同步/异步是指在应用程序进行系统调用后,是否需要等待内核在内核缓冲区和用户缓冲区复制数据完成,如果需要等待则为同步IO,如果不需要等待则为异步IO。同步IO的IO请求发起者是应用程序,内核是被动接收方;异步IO的IO请求发起者是内核,内核完成IO操作后通知应用程序。
1 同步阻塞IO

应用程序进行Read系统调用,操作系统收到Read系统调用请求,经过两个阶段:等待数据准备好、内核将数据从内核缓冲区复制到用户缓冲区,这两个阶段完成后调用返回,应用程序解除阻塞。
优点:模型简单
缺点:一个连接需要一个线程处理,高并发场景下内存、线程切换资源消耗高
2 同步非阻塞IO
同步非阻塞IO,并不是java中的NIO,java的NIO是IO多路复用模型。

应用程序进行Read系统调用,操作系统收到Read系统调用请求,进入第一个阶段:等待数据准备好,在这个阶段系统调用会立刻返回一个错误状态,不会阻塞,应用程序需要不断轮询着去请求直到内核缓冲区数据准备好,当进入第二个阶段:内核将数据从内核缓冲区拷贝到用户缓冲区,这个阶段应用程序的调用会被阻塞,直到拷贝完成,应用程序的系统调用返回。同步非阻塞IO在内核将数据从内核缓冲区拷贝到用户缓冲区也会阻塞。
优点:非阻塞,实时性高
缺点:应用程序需要不断轮询内核,占用CPU时间,效率低。
3 IO多路复用
主要用于解决同步非阻塞IO模型轮询内核问题,JAVA NIO是采用IO多路复用模型。

首先将Socket连接注册到选择器上。应用程序进行就绪查询系统调用,会被阻塞,直到连接就绪,调用才会返回就绪的连接。连接就绪一般是指内核缓冲区中的数据准备好。接下来使用就绪的连接进行Read系统调用,会被阻塞直到内核将内核缓冲区的数据拷贝到用户缓冲区,才会返回。
优点:使用一个线程可以处理多个连接
缺点:由于有两次阻塞的系统调用,当并发较低时,可能性能会比多线程+同步阻塞IO差。
IO多路复用的实现机制有三种:select、poll、epoll。
(1) select
它可以监视readfds、writefds和acceptfds三种文件描述符fd,文件描述符fd可以理解为打开的文件或者socket连接,当进行select系统调用时候会被阻塞,直到有数据可读、可写、出现异常或者select调用超时才会返回。当select调用正常返回后,轮询查询fd集合可以得到就绪的fd,然后使用的就绪的fd连接进行IO操作。
优点:跨平台好
缺点:a)每次调用select都需要将用户空间的fd集合拷贝到内核空间,拷贝操作会随着fd增多性能变差;
b) 获取就绪的fd需要轮询fd集合,时间复杂度为o(n),随着fd增多性能变差;
c) 单个进程默认可以同时操作的最大fd数是1024,虽然可以通过修改宏定义调大这个值,但是随着fd增多性能会变差。
(2) poll
poll和select类似,也是需要将用户空间中的数据拷贝到内核空间,需要轮询fd集合查找就绪fd,会随着fd增多性能变差。它有一点与select不同,它使用了链表存储fd,单个进程同时处理的fd数没有限制,但是随着fd增多性能会变差。
(3) epoll
首先将用户空间中的fd集合拷贝到共享内存,用户空间和内核空间都可以访问共享内存,避免他们之间的拷贝操作带来的性能消耗。接下来通过epoll\_create系统调用创建epoll对象,它是保存在内核空间的红黑树。然后通过epoll\_ctl系统调用来操作epoll对象,进行添加、修改或者删除连接fd,并注册监听事件,当事件触发后会通过回调函数将fd保存在就绪fd链表中。在通过epoll\_wait调用返回链表中就绪的fd。
优点:使用共享内存,避免在用户空间和内核空间数据拷贝带来的性能消耗;
使用监听通知机制代替轮询fd集合,时间复杂度为o(1),不会随着fd增多影响性能。
没有单进程操作的最大fd数限制,最大允许为操作系统的最大文件句柄数,1G内存大约为10万文件句柄。
单机百万连接,三种实现机制的区别:
select: 单个进程默认处理1000个左右的连接,不修改宏定义需要1000个进程;
poll: 虽然没有最大fd数量限制,但是100万个fd在进行复制和轮询消耗严重;
epoll: 1G内存一般可以同时处理10万连接,需要16G内存即可实现百万连接;
4 信号驱动IO

应该程序进行Read系统调用,不会阻塞,立即返回,操作系统收到系统调用请求后,等待内核缓冲区数据准备好后,通过信号通知应用程序,应用程序再进行Read系统调用,内核将内核缓冲区中的数据拷贝到用户缓冲区,调用完成。
优点:在等待内核缓冲区数据准备好过程中应用程序不会阻塞,当准备好后,通知应用程序,不用像同步非阻塞IO一样去轮询内核。
缺点:依据是同步IO,因为在内核将内核缓冲区数据拷贝到用户缓冲区过程中,应用程序会阻塞。
5 异步IO

应用程序进行Read系统调用,不会阻塞,立即返回。操作系统收到系统调用请求后,等待内核数据准备好,内核将内核缓冲区数据拷贝到用户缓冲区,完成后通过信号或者应用程序注册的回调函数通知应用程序,完成调用。
优点:在内核将内核缓冲区数据拷贝到用户缓冲区过程应用程序也不会阻塞,是真正的异步IO。
缺点:异步IO还不是太成熟,底层依旧使用epoll机制,没有很好的实现异步IO,性能没有明显提升。
还没有评论,来说两句吧...