linux中存在许多I/O流,文件流,socket流等等。无论是文件,还是管道,我们都可以称之为流。
通过read操作,我们可以从流中读入数据,通过write操作,我们可以往流写入数据。
大部分文件都是存储在磁盘而非主存,频繁的I/O会使程序性能降低,所以linux存在缓冲区,引入缓冲区是为了减少频繁的I/O调用。
有了缓冲区之后,我们大部分的操作都是在缓冲区中进行。
这里假设我们有一个缓冲区Zone,进程A为写入,进程B为读出,缓冲区有如下状态:
缓冲区非空:
一开始缓冲区为空,进程B读出阻塞,然后进程A写入数据,这时候缓冲区非空,内核就会产生一个事件告诉进程B可以读取了。
缓冲区满:
如果进程B已经接收到可读取的时间消息,但是迟迟未读消息(可能睡着了吧),这个时候进程A还在写数据,使缓冲区满,内核就会产生一个事件告诉进程A你该停止写入了。
缓冲区非满:
进程B终于醒过来了(简直是猪),慢慢消费了缓冲区中的数据,使缓冲区非满,这时内核就会产生一个事件告诉进程A可以接着写了。
缓冲区空:
进行A写累了,停止写入,进行B还在傻傻地一直读,缓冲区的数据被消费光了,使缓冲区空,这时内核就会产生一个事件告诉进程B你该停止读取了。
同步阻塞I/O模式:
一个线程只能处理一个流的I/O事件,要想同时处理多个流,就必须使用多线程。
同步非阻塞I/O模式:
采用忙轮询的方式,一个线程可以同时处理多个流:
while true {
for i in stream[]{
if i has data
read until unavailable
}
}
虽然这个线程会不断地询问所有流是否有数据,但是如果所有的流都没有数据,CPU会造成空转。
多路复用I/O模式:
为了避免CPU空转,我们引入select代理,该代理同时观察许多流的I/O事件,在空闲的时候,阻塞当前线程,当有事件的时候,才唤醒当前线程。
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
这种模式虽然解决了CPU空转的问题,当没有I/O事件产生,程序就阻塞在select处。当有I/O事件产生,只能进行无差别的轮询,无论这个流是否真的有数据,都会被轮询到。处理的流越多,每一次无差别轮询的事件就越长
epoll模式:
不同于忙轮询和无差别轮询,epoll可以告诉我们哪个流发生了事件,相比于无差别轮询(只能告知我们所有的流中存在事件)进步了很多,此时我们只需要获取所需要的处理的流并对其进行处理即可
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}
}