【IO】IO模型与零拷贝

前言:

        正在运行的程序其实就是系统中的一个进程,操作系统会为每一个进程分配内存空间,而内存空间分为两部分,一部分是用户空间,这是用户进程访问的内存区域;另一部分是内核空间,是操作系统内核访问的内存区域。

        如网络、磁盘IO等操作,出于安全性考虑,用户进程不能直接与外部设备进行数据交互,用户进程只能向操作系统发起IO调用请求,由操作系统内核与外部设备进行数据交互,完成真正的IO操作。

IO模型:

        首先明确一个概念,磁盘、socket通讯的网卡等都属于外部设备。我们经常看到的输入(input)输出(output)等概念其实就是指用户进程与这些外部设备的交互。

IO实质

        输入(input)将外部设备中的数据加载到用户进程内。

        输出(output)将用户进程内的数据迁移到外部设备。

一个完整的IO过程分为几步?

        1、用户进程向操作系统发起IO调用请求。

        2、操作系统准备数据,将外部设备中的数据加载到内核缓冲区。

        3、操作系统拷贝数据,将内核缓冲区的数据拷贝到用户进程缓冲区。

在一次完整IO过程中,根据用户进程的不同表现形式,我们可以将IO模型分成以下几种:

        (1)阻塞IO(Blocking IO)

【IO】IO模型与零拷贝_第1张图片

        表现:从发起IO调用请求(recvrom系统函数调用)至接收到操作系统内核拷贝来的数据的整个过程中,用户进程一直处于阻塞状态。

        (2)非阻塞IO(Non-Blocking IO)

【IO】IO模型与零拷贝_第2张图片

        表现:用户进程发起IO调用请求后,如果数据还未准备好,内核会直接返回错误信息,结束用户进程的阻塞状态,用户进程不断轮询发起IO调用请求,直至数据准备就绪。

        特点:不像BIO,用户进程在整个IO流程中都是阻塞的,通过轮询发起IO请求来获取数据。相较于BIO有性能提升,但在数据准备好之前,用户进程会不断调用系统函数,占用大量的CPU资源。

        (3)IO多路复用

【IO】IO模型与零拷贝_第3张图片

        表现:用户进程调用系统函数select后,可以监控多个fd,只要有任意一个fd的数据准备就绪,select函数就会返回可读提示给用户进程,此时进程再调用recvfrom系统函数读取数据。

        特点:解决了NIO频繁的系统调用问题,减少CPU资源的消耗。只有在监视的fd返回可读提升后,用户进程才会调用recvfrom函数,请求获取数据。

        fd概念

        1、文件描述符全称File Description,是一个从0开始的无符号整数,每个fd都可以关联一个文件。

        2、在linux中,万物皆文件,常规文件、视频、硬件设备、socket等都可以用一个fd来进行关联。

        简单提一下,IO多路复用模型涉及到的系统函数有三个:selectpollepoll

        select函数特点有连接数限制,一次最多只能监听1024个fd;select函数返回可读提示后,用户进程需要遍历fd集合才能得知哪个fd数据准备就绪(时间复杂度O(n))。

        poll函数特点解决了select函数有连接数限制的问题,但还是需要遍历fd集合。

        epoll函数特点既解决了连接数限制问题,又无需遍历fd集合获取,可以用O(1)的时间复杂度获取可读的fd。   

        (4)异步(Asynchronous IO)

【IO】IO模型与零拷贝_第4张图片

        表现:用户进程发起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函数返回。

【IO】IO模型与零拷贝_第5张图片

        如上图所示,整个过程包含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系统函数。

【IO】IO模型与零拷贝_第6张图片

        流程:DMA将磁盘缓冲区的数据拷贝到内核缓冲区,此时CPU不会将内核缓冲区中的数据拷贝到用户缓冲区,因为内核缓冲区内的数据会被映射到用户空间,但mmap函数返回时,还是会从内核态切换到用户态。

        特点:减少了一次数据拷贝操作,但整个IO过程还是有4次上下文切换操作。

        (2)sendfile使用sendfile系统函数代替read、write两个系统函数

【IO】IO模型与零拷贝_第7张图片

        流程:DMA将磁盘缓冲区的数据拷贝到内核缓冲区,随后CPU直接将内核缓冲区内的数据拷贝到socket缓冲区中。

        特点:减少了read函数返回时的上下文的切换、write函数调用时的上下文的切换。总计减少了一次数据拷贝操作和两次上下文切换操作。

        (3)sendfile&SG-DMA

【IO】IO模型与零拷贝_第8张图片

        流程:DMA将磁盘缓冲区的数据拷贝到内核缓冲区,缓冲区将文件描述符和数据长度传到 socket缓冲区,网卡的SG-DMA控制器可直接将内核缓冲区里的数据拷贝到网卡设备。

        特点:整个IO流程不涉及CPU,没有将数据从内核缓冲区拷贝到用户缓冲区这一流程,总计减少了两次数据拷贝操作和两次上下文切换操作。

你可能感兴趣的:(服务器,运维)