声明:这个文章只是从其他文章抄过来加上自己的理解组成的文章。可能会存在各种问题,还请指正.
关于IO这块,对于我来讲一直是个知识肓区,尤其在同步、异步、阻塞、非阻塞这块,有一点认识,但很不清晰,所以写这篇文章,它不能让我100%理解,但我希望以后可以在这个文章基础上继续加深对io的理解.
一、操作系统相关基础知识
学习JAVA IO前,我们先需要了解一下操作系统相关概念.因为常用为Linux操作系统,所以这里的概念或说明以Linux为主.
1.文件描述符(File descriptor 简写 fd)
Linux的内核将所有外部设备都可以看做一个文件来操作。那么我们对与外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个file descriptor(fd,文件描述符)。对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符)。描述符就是一个数字(可以理解为一个索引),指向内核中一个结构体(文件路径,数据区,等一些属性)。应用程序对文件的读写就通过对描述符的读写完成。
2.用户空间与内核空间
我个人简单理解的是用户程序不能直接和硬件打交道(为了安全和方便),所以有了一层内核空间.用户程序运行的用户空间。这个仅仅方便理解,一定是极期不准确的.
3.缓存 I/O
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间.
个人理解:缓存I/O大家一看到缓存两个字,不要以为仅仅用于读。其实写也有缓存(缓冲区)
二、IO 主要的阶段
以read为例读取数据主要由两个阶段(这个流程必须要牢牢记住,这个样子下面才可以讲,写流程与read相似)
1. 内核准备数据(即下图中的2、3)。即用户空间(即我们自己的应用)调用内核的方法读取数据,内核就会从硬盘上读取数据,存放到内核的缓冲区。这个阶段即内核准备阶段
2. 将数据从内核拷贝到用户空间(即下图中的4)。内核从硬盘上读取数据后,并非直接反回给应用程序,
三、Linux IO模式
主要有5种IO 模式,分为两大类同步IO和异步IO(注意,这里是按同步和异步分的,没有按阻塞非阻塞区分)
同步IO模式包括
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路复用( IO multiplexing)
- 信号驱动 I/O( signal driven IO)
异步IO模式包括
- 异步 I/O(asynchronous IO)
重复一次:阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步I/O模型(看完下面的详解,你会更清楚的)
下面详细讲解这5种IO模式(参考:https://www.cnblogs.com/diegodu/p/6823855.html 这个介绍的不错,我就全面抄过来了)
1. 阻塞 I/O
阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误才返回。进程从调用recvfrom开始到它返回的整段时间内是被阻塞的。(图和文字多看几次)
2. 非阻塞 I/O
当一个应用进程像这样对一个非阻塞描述字循环调用recvfrom时,我们称之为轮询(polling)。应用进程持续轮询内核,以查看某个操作是否就绪。
个人理解:这里我要说一下,我个人理解这个非阻塞 I/O,并非真正的非阻塞,前面已经讲了,IO共分为两个阶段:1.内核准备数据阶段。2.将数据从内核拷到用户空间。 非阻塞IO在第二阶段时仍然还是阻塞的,但第二阶段我们我们一般不说是阻塞和非阻塞,而说同步异步。这里就可以很明显的了解到。阻塞与非阻塞是用来描述第一阶段(因为这一步是真正的读取数据。即将数据从硬盘拷到操作系统的内核中)。同步与异步是用来描述第二阶段的
3. I/O 多路复用
个人理解:我之前仅仅看到这个图时,总会产生疑惑,产生疑惑在于等待数据阶段。这么看也是阻塞的,和阻塞I/O看起来没有区别。大家仔细理解一下我摘抄的下面一句话
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如下面的图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
不知道大家是如何理解上面这句话的,我给大家解读一下我的个人理解。首先一个select 对应多个socket(这里针对的是网络),每个socket会设置成非阻塞的。select底层方法类似循环不停的遍历每个socket是否有数据。select方法内部不停的循环遍历每个socket是否有数据,这个是阻塞的,即select是阻塞的,至到socket有数据。这段至关重要,我之前不太理解,就是一直没有理解明白这里。也就是说下面这个图其实是没有很好的表达一个select对应多个socket情况下,他只表达了一个select 对应一个socket情况。
4. 信号驱动 I/O
信号驱动IO这个我没有仔细去了解,因为看到说使用较少。但从下面这个图我们可以了解到他的确是同步的,原因看第二阶段,第二阶段是同步的。
5. 异步 I/O
从下面看,我们可以看到异步I/O其实是异步非阻塞的。因为第一阶段是马上返回的,并没有阻塞。
I/O模型比较
根据上述5种IO模型,前4种模型-阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程,在内核数据copy到用户空间时都是阻塞的。这段话相关内其实在这个文章中出现过了三次了,要注意这个很重要了。其实反过来我们按第一阶段(阻塞非阻塞)第二阶段(异步非异步)来整理记IO模式其实也是可以的。
至此,你应该理解对于Linux 5种IO中对应是阻塞还是非阻塞,是同步还是异步。如果你还不明白,再看一次或看一下我后面的那些参考文章或留言沟通。我有脑子不太好用,又不想去看操作系统相关的书(主要无法快速找到我想要的内容),所以我翻阅了大量的网上文章
四、IO模式-多路复用详解(全是拷的)
为什么这里要重点说一下多路复用的IO模型,很简单,因为JavaNIO底层就是这个模型,NIO是目前应用最多,性能还不错的,看起来没有AIO那么复杂。
定义: IO多路复用,就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.
Linux支持IO多路复用的系统调用有select、poll、epoll,这些调用都是内核级别的。但select、poll、epoll本质上都是同步I/O,先是block住等待就绪的socket,再是block住将数据从内核拷贝到用户内存。
之所以本质上是同步IO原因:因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。 -- 其实还是我前面提的,在第二阶段是要进行等待的,这个等待我们定义为同步。
大家要理解:linux多路复用在内核级别有三种实现方式select或poll或epoll.
epoll的效率更高,优化了select的轮询操作,通过callback事件响应方式。
poll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现(POSIX标准是一个针对操作系统(准确地说是针对类Unix操作系统)的标准化协议)
select:
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。
这样所带来的缺点是:
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
poll:
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:
1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
epoll:
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd(fd是文件描述符)还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。
epoll为什么要有EPOLLET触发模式?
如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符
epoll的优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;
即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
五、JavaNIO
JavaNIO是基于多路复用技术的, Java NIO中的选择器依赖操作系统内核的这些系统调用,即在Linux中NIO其实就是调用内核的select或poll或epoll实现的.至于选择哪一种实现方式与内核版本有关,详细看:https://blog.csdn.net/hsuxu/article/details/9876983
六、说明
关于JavaNIO不可能这么少就讲完了。后面我会再写一篇讲解一下Java BIO,AIO,NIO的实现,重点会在AIO和NIO上,因为这块我一直了解的不太后,之后会分析一下Tomcat中的IO模型是怎么样的。如果一篇写不开会多写几篇。原计划还想分析一下Netty或dubbo中的模型,由于能力时间精力有限,可能会先分析到Tomcat Java IO这块暂靠段落。
mysql这块我会继续整理。
虽然这篇文章很多不是我写得。但是从我今天整理到现在写完,也用了近六七个个小时。写这样的博客都难,何况真自己从头写呢。。
七、参考资料nio
https://www.cnblogs.com/diegodu/p/6823855.html
https://www.cnblogs.com/dongguacai/p/5770287.html
https://www.jianshu.com/p/486b0965c296
https://blog.csdn.net/youyaecho/article/details/51799423
linux
https://www.cnblogs.com/jeakeven/p/5435916.html
https://www.cnblogs.com/zhaodahai/p/6831456.html
https://www.2cto.com/kf/201611/561895.html
https://www.cnblogs.com/zhaodahai/p/6831456.html
POSIX
https://blog.csdn.net/yongyu_it/article/details/77094089
java nio
https://blog.csdn.net/u014507083/article/details/73784898
http://baijiahao.baidu.com/s?id=1570735523203847&wfr=spider&for=pc
http://weixiaolu.iteye.com/blog/1479656
综合:
https://blog.csdn.net/u014507083/article/details/73784898
https://www.cnblogs.com/losing-1216/p/5073051.html
https://blog.csdn.net/zxzx2966381/article/details/50420467
阻塞与挂起
https://www.cnblogs.com/hoobey/p/6915638.html
https://www.zhihu.com/question/42962803
Linux多路复用
https://www.cnblogs.com/jeakeven/p/5435916.html