在学习I/O之前,有必要了解用户空间和内核空间的概念,因为所有的I/O操作都牵涉到用户空间到内核空间的切换。
用户空间:用户空间是常规进程所在的区域,什么是常规进程,打开任务管理器看到的就是常规进程。
内核空间:操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
划分用户空间和内核空间:主要是为了保证操作系统的稳定性和安全性。用户程序不可以直接访问硬件资源,如果用户程序需要访问硬件资源,必须调用操作系统提供的接口,这个调用接口的过程也就是系统调用。每一次系统调用都会存在两个内存空间之间的相互切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间接收到远程主机的数据,然后再从内核空间复制到用户空间,供用户程序使用。
IO的分类包括:同步IO、异步IO、阻塞IO和非阻塞IO。
同步/异步:关注的是消息通信机制
同步:调用者等待被调用者返回消息,才能继续执行
异步:被调用者通过状态、通知或回调机制主动通知调用者被调用者的运行状态。
阻塞/非阻塞:关注调用者在等待结果返回之前所处的状态
阻塞:指I/O操作彻底完成后才返回用户空间,调用结果返回之前,调用者被挂起。
非阻塞:指I/O操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成,最终的调用结果返回之前,调用者不会被挂起。
Linux网络I/O模型,分为5类:
1.阻塞I/O模型:
阻塞I/O是指用户线程在内核进行IO操作时被阻塞。
用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间,内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。
用户需要等到read将数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。
2.菲阻塞I/O模型:
用户线程发起IO请求时立即返回。但未读取到任何数据,用户线程需要不断地发起IO请求,直到数据达到后,才真正读取到数据,继续执行,即“轮询”机制。
整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU资源。
是比较浪费CPU的方式,一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
3.IO多路复用模型
IO多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符。
IO多路复用适用以下场景:
<1> 客户端程序要同时处理多个socket。
<2> 客户端程序要同时处理用户输入和网络连接。
<3> TCP服务器要同时处理监听socket和连接socket。
<4> 服务器要同时监听多个端口,或者处理多种服务。
实现I/O多路复用的系统调用主要有select,poll,epoll_wait,它们的好处在于单个线程可以同时处理多个网络连接的IO,这三个函数会不断轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
4.信号驱动IO模型
用户进程可以通过sigaction系统调用注册一个信号处理程序,然后主程序可以继续向下执行,当有IO操作准备就绪时,由内核通知触发一个SIGIO信号处理程序执行,然后将用户进程所需要的数据从内核空间拷贝到用户空间。
此模型的优势在于等待数据报到达期间进程不被阻塞。用户主程序可以继续执行,只要等待来自信号处理函数的通知。
5.异步I/O
异步IO与信号驱动IO最最主要的区别是信号驱动IO是由内核告诉用户线程IO操作何时完成。信号驱动IO当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到用户空间缓冲区这个阶段,而异步IO直接在第二个阶段完成后,内核直接通知用户线程可以进行后续操作了。
相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方法(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。