目录
一、同步I/O
二、异步I/O
三、阻塞I/O
四、非阻塞I/O
五、信号驱动I/O
六、多路复用IO
一、同步I/O
在操作系统中,程序运行的空间分为内核空间和用户空间, 用户空间所有对io操作的代码(如文件的读写、socket的收发等)都会通过系统调用进入内核空间完成实际的操作。而且我们都知道CPU的速度远远快于硬盘、网络等I/O。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到I/O操作,如读写文件、发送网络数据时,就需要等待 I/O 操作完成,才能继续进行下一步操作,这种情况称为同步 I/O。
在某个应用程序运行时,假设需要读写某个文件,此时就发生了I/O操作,在I/O操作的过程中,系统会将当前线程挂起,而其他需要CPU执行的代码就无法被当前线程执行了,这就是同步I/O操作,因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们可以使用多线程或者多进程来并发执行代码,即当某个线程/进程被挂起后,不会影响到其他线程或进程。但多线程和多进程不能无限增加,且进程/线程之间切换的开销很大,而解决这个问题的另一种方法就是使用异步I/O。
二、异步I/O
当程序需要对I/O进行操作时,它只发出I/O操作的指令,并不等待I/O操作的结果, 然后就去执行其他代码了。一段时间后,当I/O返回结果时,再通知CPU进行处理。 这样子用户空间中的程序不需要等待内核空间中的 I/O 完成实际操作,就可执行其他任务,提高了CPU的利用率。总结就是用户不需要等待内核完成实际对I/O的读写操作就直接返回了。
三、阻塞I/O
当用户进程调用了 read()/recvfrom() 等系统调用函数,它会进入内核空间中,当这个网络I/O没有数据的时候,内核就要等待数据的到来,而在用户进程这边,整个进程会被阻塞,直到内核空间返回数据。当内核空间的数据准备好了,它就会将数据从内核空间中拷贝到用户空间,此时用户进程才解除阻塞的的状态,重新运行起来。
阻塞I/O的特点就是在IO执行的两个阶段(用户空间于内核空间)都被阻塞了。
四、非阻塞I/O
Linux下,可以通过设置socket使其变为非阻塞模式,这种情况下,当内核空间并无数据的时候,它会马上返回结果而不会阻塞,此时用户进程可以根据这个结果自由配置,比如继续请求数据,或者不再继续请求。
当用户进程调用 read()/recvfrom() 等系统调用函数时,如果内核空间中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个EWOULDBLOCK的错误码,表示数据没有准备好。此时用户进程会再次调用 read()/recvfrom() 等系统调用,当内核空间的数据准备好了,它就会将数据从内核空间中拷贝到用户空间,此时用户进程也就得到了数据。
非阻塞I/O的特点就是用户进程需要不断的主动询问内核空间的数据有没有准备好。
五、信号驱动I/O
信号驱动I/O的特点是内核将数据准备好的时候,使用SIGIO信号来通知应用程序进行I/O操作。(异步I/O是由内核在数据拷贝完成时, 通知应用程序,而信号驱动是告诉应用程序何时可以开始拷贝数据)
六、多路复用IO
多路复用I/O就是我们说的 select、poll、epoll 等操作,复用的好处就在于单个进程就可以同时处理多个网络连接的I/O,能实现这种功能的原理就是 select、poll、epoll 等函数会不断的轮询它们所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程 ,从而大大减小了系统的开销。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间 。
一般来说I/O复用多用于以下情况:
当客户处理多个描述符时。
服务器在高并发处理网络连接的时候。
服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。