零拷贝原理

Linux IO读写方式

在讲解零拷贝原理之前先来熟悉一下Linux系统下普通的IO读写的流程。

Linux 提供了轮询I/O 中断以及 DMA 传输这 3 种磁盘与主存之间的数据传输机制。其中轮询方式是基于死循环对 I/O 端口进行不断检测。I/O 中断方式是指当数据到达时,磁盘主动向 CPU 发起中断请求,由 CPU 自身负责数据的传输过程。 DMA 传输则在 I/O 中断的基础上引入了 DMA 磁盘控制器,由 DMA 磁盘控制器负责数据的传输,降低了 I/O 中断操作对 CPU 资源的大量消耗。

IO中断

在 DMA 技术出现之前,应用程序与磁盘之间的 I/O 操作都是通过 CPU 的中断完成的。每次用户进程读取磁盘数据时,都需要 CPU 中断,然后发起 I/O 请求等待数据读取和拷贝完成,每次的 I/O 中断都导致 CPU 的上下文切换。具体的流程如下所示:

使用 I/O 中断方式读取数据步骤:

  • 用户进程向 CPU 发起 read 系统调用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回;
  • CPU 在接收到指令以后对磁盘发起 I/O 请求,将磁盘数据先放入磁盘控制器缓冲区
  • 数据准备完成以后,磁盘向 CPU 发起 I/O 中断;
  • CPU 收到 I/O 中断以后将磁盘缓冲区中的数据拷贝到内核缓冲区,然后再从内核缓冲区拷贝到用户缓冲区
  • 用户进程由内核态切换回用户态,解除阻塞状态,然后等待 CPU 的下一个执行时间钟。

DMA

要把IO设备的数据读入内存或把内存的数据传送到IO设备中,一般都要通过 CPU 控制完成。利用中断进行数据传送,可以大大提高 CPU 的利用率。但是采用中断传送有它的缺点,对于一个高速 I/O 设 备以及批量交换数据的情况,如果中断 I/O 操作带来的将是性能的损耗。对于这种类型的操作如果可以找一个第三方来执行数据拷贝而 I/O 还继续执行数据读取主流程任务是最好的。DMA 在外设与内存间直接进行数据交换,而不通过 CPU,这样数据传送的速度就取决于存储器和外设的工作速度。

通常系统的总线是由 CPU 管理的。在 DMA 方式时,就希望 CPU 把这些总线让出来,即 CPU 总线处于第三种状态:高阻状态,而由 DMA 控制器接管,控制传送的字节数,以及在数据传输完成后通知CPU进行后续的操作。DMA 控制器必须有以下功能:

  • 能向 CPU 发出系统保持(HOLD)信号,提出总线接管请求;
  • 当 CPU 发出允许接管信号后,负责对总线的控制,进入 DMA 方式;
  • 能对存储器寻址及能修改地址指针,实现对内存的读写操作;
  • 能决定本次 DMA 传送的字节数,判断 DMA 传送是否结束;
  • 发出 DMA 结束信号,使 CPU 恢复正常工作状态。

CPU 从繁重的 I/O 操作中解脱,数据读取操作的流程如下:

  • 用户进程向 CPU 发起 read 系统调用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回;
  • CPU 在接收到指令以后对 DMA 磁盘控制器发起调度指令;
  • DMA 磁盘控制器对磁盘发起 I/O 请求,将磁盘数据先放入磁盘控制器缓冲区,CPU 全程不参与此过程;
  • 数据读取完成后,DMA 磁盘控制器会接受到磁盘的通知,将数据从磁盘控制器缓冲区拷贝到内核缓冲区
  • DMA 磁盘控制器向 CPU 发出数据读完的信号,由 CPU 负责将数据从内核缓冲区拷贝到用户缓冲区
  • 用户进程由内核态切换回用户态,解除阻塞状态,然后等待 CPU 的下一个执行时间钟。

传统的IO存在的问题

在 Linux 系统中,传统的访问方式是通过 write()read() 两个系统调用实现的,通过 read() 函数读取文件到到缓存区中,然后通过 write()方法把缓存中的数据输出到网络端口,伪代码如下:

read(file_fd, tmp_buf, len);
write(socket_fd, tmp_buf, len);

如下图所示分别对应传统 I/O 操作的数据读写流程,整个过程涉及 2 次 CPU 拷贝、2 次 DMA 拷贝总共 4 次拷贝,以及 4 次上下文切换,下面简单地阐述一下相关的概念。

基于传统的 I/O 读取方式,读取数据的时候会触发 2 次上下文切换,1 次 DMA 拷贝和 1 次 CPU 拷贝,发起数据读取的流程如下:

  • 用户进程通过read()函数向内核 (kernel) 发起系统调用,上下文从用户态 (user space) 切换为内核态 (kernel space);
  • CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间 (kernel space) 的读缓冲区 (read buffer);
  • CPU 将读缓冲区 (read buffer) 中的数据拷贝到用户空间 (user space) 的用户缓冲区 (user buffer)。
  • 上下文从内核态 (kernel space) 切换回用户态 (user space),read 调用执行返回。

基于传统的 I/O 写入方式,写数据的时候会触发 2 次上下文切换,1 次 CPU 拷贝和 1 次 DMA 拷贝,以用户程序发送网络数据的流程如下:

  • 用户进程通过 write() 函数向内核 (kernel) 发起系统调用,上下文从用户态 (user space) 切换为内核态(kernel space)。
  • CPU 将用户缓冲区 (user buffer) 中的数据拷贝到内核空间 (kernel space) 的网络缓冲区 (socket buffer)。
  • CPU 利用 DMA 控制器将数据从网络缓冲区 (socket buffer) 拷贝到网卡进行数据传输。
  • 上下文从内核态 (kernel space) 切换回用户态 (user space),write 系统调用执行返回。

零拷贝方式

由上面的传统IO的流程来看,为了提高IO的效率,需要减少用户空间和内核空间之间的拷贝次数,这也是零拷贝的主要思路。在 Linux 中零拷贝技术主要有 3 个实现思路:用户态直接 I/O减少数据拷贝次数以及写时复制技术

  • 用户态直接 I/O:应用程序直接访问硬件存储,操作系统内核只是辅助数据传输。这种方式可以使得硬件上的数据直接拷贝到用户空间,不需要经过内核的处理,因此,用户态直接IO是不存在内核空间缓冲区和用户空间缓冲区之间的拷贝的,但是仍然存在用户态和内核态之间的切换。
  • 减少数据拷贝次数:在数据传输过程中,避免数据在用户空间缓冲区和系统内核空间缓冲区之间的CPU拷贝,以及数据在系统内核空间内的CPU拷贝,这也是当前主流零拷贝技术的实现思路
  • 写时复制技术:写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么将其拷贝到自己的进程地址空间中,如果只是数据读取操作则不需要进行拷贝操作。

用户态直接 I/O

用户态直接 I/O 使得应用进程或运行在用户态(user space)下的库函数直接访问硬件设备,数据直接跨过内核进行传输,内核在数据传输过程除了进行必要的虚拟存储配置工作之外,不参与任何其他工作,这种方式能够直接绕过内核,极大提高了性能。

这种方式的优点很明显,但是仍然存在缺点:

  • 这种方法只能适用于那些不需要内核缓冲区处理的应用程序,这些应用程序通常在进程地址空间有自己的数据缓存机制,称为自缓存应用程序,如数据库管理系统就是一个代表。
  • 这种方法直接操作磁盘 I/O,由于 CPU 和磁盘 I/O 之间的执行时间差距,会造成资源的浪费,解决这个问题需要和异步 I/O 结合使用。

mmap + write

mmap实现了从磁盘到用户空间的直接拷贝操作,省去了内核空间和用户空间之间的交互,关于 mmap可以看这篇文章(https://blog.csdn.net/m0_37851345/article/details/119170079)。

一种零拷贝方式是使用 mmap + write 代替原来的 read + write 方式,减少了 1 次 CPU 拷贝操作。mmap 是 Linux 提供的一种内存映射文件方法,即将一个进程的地址空间中的一段虚拟地址映射到磁盘文件地址

使用mmap可以将内核中读缓冲区的地址与用户空间的缓冲区进行映射,实现了内核缓冲区与应用程序内存的共享,进而可以省去将数据从内核读缓冲区拷贝到用户缓冲区的过程。

sendfile

sendfile 系统调用在 Linux 内核版本 2.1 中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。sendfile 系统调用的引入,不仅减少了 CPU 拷贝的次数,还减少了上下文切换的次数

通过 sendfile 系统调用,数据可以直接在内核空间内部进行 I/O 传输,从而省去了数据在用户空间和内核空间之间的来回拷贝。与 mmap 内存映射方式不同的是, sendfile使用过程中数据对用户空间是完全不可见的,因此只能适用于那些不需要用户态处理的应用程序。

写时复制

写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中。这样做并不影响其他进程对这块数据的操作,每个进程要修改的时候才会进行拷贝,所以叫写时拷贝。这种方法在某种程度上能够降低系统开销,如果某个进程永远不会对所访问的数据进行更改,那么也就永远不需要拷贝。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值