前言:
正在运行的程序其实就是系统中的一个进程,操作系统会为每一个进程分配内存空间,而内存空间分为两部分,一部分是用户空间,这是用户进程访问的内存区域;另一部分是内核空间,是操作系统内核访问的内存区域。
如网络、磁盘IO等操作,出于安全性考虑,用户进程不能直接与外部设备进行数据交互,用户进程只能向操作系统发起IO调用请求,由操作系统内核与外部设备进行数据交互,完成真正的IO操作。
IO模型:
首先明确一个概念,磁盘、socket通讯的网卡等都属于外部设备。我们经常看到的输入(input)、输出(output)等概念其实就是指用户进程与这些外部设备的交互。
IO实质:
输入(input):将外部设备中的数据加载到用户进程内。
输出(output):将用户进程内的数据迁移到外部设备。
一个完整的IO过程分为几步?
1、用户进程向操作系统发起IO调用请求。
2、操作系统准备数据,将外部设备中的数据加载到内核缓冲区。
3、操作系统拷贝数据,将内核缓冲区的数据拷贝到用户进程缓冲区。
在一次完整IO过程中,根据用户进程的不同表现形式,我们可以将IO模型分成以下几种:
(1)阻塞IO(Blocking IO):
表现:从发起IO调用请求(recvrom系统函数调用)至接收到操作系统内核拷贝来的数据的整个过程中,用户进程一直处于阻塞状态。
(2)非阻塞IO(Non-Blocking IO):
表现:用户进程发起IO调用请求后,如果数据还未准备好,内核会直接返回错误信息,结束用户进程的阻塞状态,用户进程不断轮询发起IO调用请求,直至数据准备就绪。
特点:不像BIO,用户进程在整个IO流程中都是阻塞的,通过轮询发起IO请求来获取数据。相较于BIO有性能提升,但在数据准备好之前,用户进程会不断调用系统函数,占用大量的CPU资源。
(3)IO多路复用:
表现:用户进程调用系统函数select后,可以监控多个fd,只要有任意一个fd的数据准备就绪,select函数就会返回可读提示给用户进程,此时进程再调用recvfrom系统函数读取数据。
特点:解决了NIO频繁的系统调用问题,减少CPU资源的消耗。只有在监视的fd返回可读提升后,用户进程才会调用recvfrom函数,请求获取数据。
fd概念:
1、文件描述符全称File Description,是一个从0开始的无符号整数,每个fd都可以关联一个文件。
2、在linux中,万物皆文件,常规文件、视频、硬件设备、socket等都可以用一个fd来进行关联。
简单提一下,IO多路复用模型涉及到的系统函数有三个:select、poll、epoll。
select函数特点:有连接数限制,一次最多只能监听1024个fd;select函数返回可读提示后,用户进程需要遍历fd集合才能得知哪个fd数据准备就绪(时间复杂度O(n))。
poll函数特点:解决了select函数有连接数限制的问题,但还是需要遍历fd集合。
epoll函数特点:既解决了连接数限制问题,又无需遍历fd集合获取,可以用O(1)的时间复杂度获取可读的fd。
(4)异步(Asynchronous IO):
表现:用户进程发起IO调用请求后,内核直接返回提示信息,在随后的数据准备阶段以及数据拷贝阶段,用户进程不会阻塞;在数据拷贝操作完成后,内核发送信号通知用户进程。
特点:无论是NIO模型还是IO多路复用模型,它们都会在数据拷贝阶段:将数据从内核缓冲区拷贝到用户缓冲区阻塞,而AIO模型实现了真正的IO全过程无阻塞。
零拷贝:
服务端一般都会提供文件下载功能,这个功能的实质是:基于与客户端建立的socket连接,将服务器磁盘上的文件发送到客户端主机的网卡上。
文件下载功能大概的IO流程:
从磁盘中读取数据到应用程序内存:
1、用户进程调用read函数,向操作系统发起IO请求,上下文从用户态切换为内核态。
2、DMA控制器将数据从磁盘控制缓冲区中拷贝到内核缓冲区。
3、CPU再把内核缓冲区的数据,拷贝到用户缓冲区,上下文从内核态切换为用户态,read函数返回。
将应用程序内存中的数据写入到socket:
4、用户进程调用write函数,发起IO调用请求,上下文从用户态切换为内核态。
5、CPU将用户缓冲区中的数据,拷贝到socket缓冲区。
6、DMA控制器再将数据从socket缓冲区,拷贝到网卡设备,上下文从内核态切换回用户态,write函数返回。
如上图所示,整个过程包含4次上下文切换(用户态、内核态转换)、4次数据拷贝操作,效率较低。看到这里,可能大家会对DMA有疑惑,它是什么?有什么用?
DMA:全称Direct Memory Access,直接内存访问,本质上是一块主板上独立的芯片。它的作用是替代CPU完成与IO设备的数据传输工作,减少CPU的负担,提高CPU的利用效率。
读取磁盘文件的完整IO流程:
1、用户进程调用read函数,发起IO调用请求。
2、CPU收到指令后,对DMA控制器发起指令调度。
3、DMA收到IO请求(CPU指令调度)后,请求获取磁盘数据。
4、磁盘将数据放入磁盘控制缓冲区,通知DMA控制器。
5、DMA将数据从磁盘控制缓冲区拷贝到内核缓冲区。
6、DMA通知CPU,CPU负责将数据从内核缓冲区拷贝到用户缓冲区。
7、用户应用进程从内核态切换回用户态。
通过读取磁盘文件的IO流程,我们也不难得到将数据写出到网卡的整个IO流程。
零拷贝概念:不是指没有数据拷贝操作,而是减少上下文切换次数和数据拷贝的次数。
零拷贝实现方案:
(1)mmap&write:使用mmap系统函数代替read系统函数。
流程:DMA将磁盘缓冲区的数据拷贝到内核缓冲区,此时CPU不会将内核缓冲区中的数据拷贝到用户缓冲区,因为内核缓冲区内的数据会被映射到用户空间,但mmap函数返回时,还是会从内核态切换到用户态。
特点:减少了一次数据拷贝操作,但整个IO过程还是有4次上下文切换操作。
(2)sendfile:使用sendfile系统函数代替read、write两个系统函数。
流程:DMA将磁盘缓冲区的数据拷贝到内核缓冲区,随后CPU直接将内核缓冲区内的数据拷贝到socket缓冲区中。
特点:减少了read函数返回时的上下文的切换、write函数调用时的上下文的切换。总计减少了一次数据拷贝操作和两次上下文切换操作。
(3)sendfile&SG-DMA:
流程:DMA将磁盘缓冲区的数据拷贝到内核缓冲区,缓冲区将文件描述符和数据长度传到 socket缓冲区,网卡的SG-DMA控制器可直接将内核缓冲区里的数据拷贝到网卡设备。
特点:整个IO流程不涉及CPU,没有将数据从内核缓冲区拷贝到用户缓冲区这一流程,总计减少了两次数据拷贝操作和两次上下文切换操作。