I/O在计算机里指的是INPUT/OUTPUT,也就是输入/输出。IOPS即每秒钟的输入输出量。I/O的工作机制就是把数据从内核空间中的内存数据复制到用户空间中进程的内存当中。
Linux的I/O
- 磁盘I/O
磁盘I/O是进程向内核发起系统调用,请求磁盘上的某个资源,然后通过相应的驱动程序将目标文件加载到内核的内存空间,加载完成后把数据从内核内存再复制给进程内存,如果是比较大的数据也需要等待时间。
- 网络I/O:一切皆文件,本质为对socket文件的读写
网络通信就是网络协议栈到用户空间进程的IO就是网络IO
1.客户端发起请求 先发送到网卡
2.网卡收到的报文复制到内核空间
3.内核空间再复制到用户空间的应用程序空间
4.nginx 分析得到一个磁盘页面文件
5.再将需求反馈给内核空间,应为应用程序没有权限从磁盘上直接读取文件,需要依靠内核
6.内核去磁盘上找到所需要的文件,加载到内核空间
7.加载后再复制到用户空间
8.用户空间构建响应报文,交给内核空间,内核空间再复制给网卡,返回给用户
整个过程会来回切换 用户空间,内核空间 那么我们可以再次基础上做优化处理
简单来说,A(nginx)--------->B(内核),A程序布置一个任务给B,B的任务就是找到某个文件,先复制内核,再复制给我。
零拷贝技术
传统的Linux系统的标准I/O借口是基于数据拷贝的,也就是数据都是“拷贝给用户”或者“从用户那儿拷贝”。
优点:通过中间缓存的机制,减少磁盘I/O的操作
缺点:消耗大量CPU资源,降低传输速率。
MMAP
mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
优点:可以实现不同进程中的文件共享,适合数据大量传输。
1. I/O模型相关概念
同步/异步(消息反馈机制) :关注的是消息通讯机制,即调用者在等待一件事情的处理结果时,被调用者是否提供完成状态的通知。
同步:被调用者并不提供事件的处理结果相关的通知消息,需要调用者主动询问事情是否处理完成。简而言之,发起者需要主动联系执行者是否完成。
异步:被调用者通过状态、通知或回调机制主动通知调用者被调用者的运行状态。简而言之,执行者会自动反馈,是否完成。
阻塞/非阻塞: 关注调用者在等待结果返回之前所处的状态
阻塞:指IO操作需要彻底完成后才返回到用户空间,调用结果返回之前,调用者被挂起,干不了别的事情。简而言之,只可以做一件事,必须要等这件事情完成才能进行下一件事。
非阻塞:指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成,在最终的调用结果返回之前,调用者不会被挂起,可以去做别的事情。简而言之,在等待时间可以做其他事。
2. 网络I/O模型
阻塞型、非阻塞型、复用型、信号驱动型、异步
2.1 阻塞性I/O模型
阻塞IO模型是最简单的I/O模型,用户线程在内核进行IO操作时被阻塞用户线程通过系统调用read发起I/O读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作用户需要等待read将数据读取到buffer后,才继续处理接收的数据。整个I/O请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。
优点:基本不占用资源
缺点:为何程序、内存、线程切换开销较大
2.2 非阻塞性I/O模型
用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。即 “轮询”机制存在两个问题:如果有大量文件描述符都要等,那么就得一个一个的read。这会带来大量的Context Switch(read是系统调用,每调用一次就得在用户态和核心态切换一次)。轮询的时间不好把握。这里是要猜多久之后数据才能到。等待时间设的太长,程序响应延迟就过大;设的太短,就会造成过于频繁的重试,干耗CPU而已,是比较浪费CPU的方式,一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
2.3 多路复用I/O型
I/O multiplexing 主要包括:select,poll,epoll三种系统调用,select/poll/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/poll/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。Apache prefork是此模式的select,work是poll模式。
2.4 信号驱动式I/O模型
调用的步骤是,通过系统调用 sigaction ,并注册一个信号处理的回调函数,该调用会立即返回,然后主程序可以继续向下执行,当有I/O操作准备就绪,即内核数据就绪时,内核会为该进程产生一个SIGIO 信号,并回调注册的信号回调函数,这样就可以在信号回调函数中系统调用 recvfrom 获取数据,将用户进程所需要的数据从内核空间拷贝到用户空间。
当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
- 优点:线程并没有在等待数据时被阻塞,内核直接返回调用接收信号,不影响进程继续处理其他请求因此可以提高资源的利用率
- 缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知
2.5 异步I/O模型
异步I/O是由内核通知我们I/O操作何时完成,所有事情都交给内核处理。
异步I/O与信号驱动I/O的区别
- 信号驱动I/O:何时开始一个IP操作
- 异步I/O是由内核通知我们I/O操作何时完成
ginx支持在多种不同的操作系统实现不同的事件驱动模型,但是其在不同的操作系统甚至是不同的系统版本上面的实现方式不尽相同,主要有以下实现方式:
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 哈希表 |
IO效率 | 每次调用都是线性遍历,时间复杂度O(n) | 每次调用都是线性遍历,时间复杂度O(n) | 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪的rdlllist,事件复杂度O(1) |
最大连接数 | 1024(x86) 2048(x64) | 无上限 | 无上限 |
fd拷贝 | 每次调用都要把fd集合从用户拷贝到内核态 | 每次调用poll都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝近内核并保存,之后每次epoll_wait不拷贝 |
缺点 | 1.数量受到限制 2.效率降低 3.开销成本大 | 1.数量受到限制 2.效率降低 3.开销成本大 | |
使用次数 | 少 | 基本不用 | 目前主流 |
select和epoll的区别
select:会轮询遍历所有的事件集合,其次遍历的事件个数有限制
epoll:只会遍历已准备好的事件集合,事件个数无限制