传统I/O的过程
当一个进程发起I/O操作(例如读取文件、网络请求等)时,需要通过系统调用(如 read、write、recv、send 等)与操作系统内核进行交互。读取文件时
- 数据首先从磁盘读取到内核缓冲区
- 数据从内核缓冲区复制到用户空间的缓冲区
- 如果数据需要通过网络传输,数据会从用户缓冲区复制到内核空间的网络缓冲区。
虽然I/O操作消耗CPU资源,但相比于CPU密集型任务(如计算密集型任务),I/O操作对CPU的占用通常较低。现代操作系统和硬件(如DMA,直接内存访问)已经尽量优化了这些操作,以减少CPU的负担。
零拷贝
“零拷贝”(Zero Copy)是一种用于减少数据在计算机内存中的复制次数,以提高 I/O 操作的效率。通过零拷贝技术,数据可以直接在 I/O 设备和用户空间之间传输,而不需要通过多次拷贝,从而减少了 CPU 的负担和内存带宽的使用。
零拷贝技术旨在减少这些数据复制过程,具体实现可能有所不同,但大致有以下几种方式:
sendfile
系统调用
直接将文件描述符的数据传输到网络套接字
- 数据从磁盘读取到内核缓冲区
- 数据直接从内核缓冲区传输到网络缓冲区,避免了内核空间和用户空间之间的复制
sendfile 通常用于高效地传输大块数据,而在用户空间中无法读取和处理数据。例如:文件服务器将文件内容传输给客户端。
mmap
系统调用
允许将文件直接映射到进程的地址空间,使得进程可以直接访问文件的数据
- 数据从磁盘读取到内核缓冲区
- 数据通过内存映射直接在用户空间访问,不需要额外的复制
用户程序是否可以修改这些数据取决于 mmap 的使用模式和权限设置
1 |
|
- 内存映射权限
- PROT_READ:映射区域可读
- PROT_WRITE:映射区域可写
- PROT_EXEC:映射区域可执行
- 映射类型
- MAP_SHARED:进程对映射区域的修改会同步到文件
- MAP_PRIVATE:进程对映射区域的修改不会影响文件,内核会创建一个写时拷贝(copy-on-write)
- 同步修改
- 使用 msync 系统调用将修改同步回文件,确保数据的一致性
尽管 mmap 允许修改映射的数据,但这些修改的范围和效果受到一些限制,某些文件系统或设备可能不支持对内存映射区域的写入操作。内核会保护某些内存区域不被修改,进程尝试修改这些区域可能会导致段错误(segmentation fault)
使用场景
- mmap 提供了一个简单的编程模型,程序员可以像操作内存一样操作文件数据,无需显式的 I/O 操作
- 通过 mmap,多个进程可以共享同一内存区域,实现进程间通信。
- 对于非常大的文件,可以使用 mmap 逐步将文件映射到内存,而不必一次性加载整个文件,节省内存。
- 如果需要随机访问文件中的数据,而不是顺序读取,mmap 提供了很大的灵活性。
直接内存访问(DMA)
允许 I/O 设备(如磁盘控制器、网络卡等)直接将数据传输到内存,而不需要 CPU 的干预
- 数据从磁盘通过 DMA 直接传输到用户空间缓冲区,避免了内核缓冲区的复制过程。
在使用DMA的过程中,程序是无法直接控制和修改正在传输的数据的,因为DMA操作通常在硬件级别完成。在DMA传输过程中,数据在内存中的一致性是由硬件保证的,而不是由用户进程进行控制。
但在DMA传输完成后对数据进行操作
使用场景
- DMA 最大的优点是高效的数据传输,减少 CPU 的负担,适合于需要快速传输大量数据的场景。
- 在实时系统中,CPU 资源宝贵,需要尽可能减少 CPU 的负担,DMA 是一个很好的选择。
- 如果数据传输模式是固定的(例如,从设备到内存,或从内存到设备),DMA 可以很好地处理。
比较和选择
- CPU 使用
- mmap 会消耗一些 CPU 资源,特别是在处理页错误和内存管理时
- DMA 则极大地减少了 CPU 的参与,可以将 CPU 资源用于其他任务
- 数据处理
- 使用 mmap 时,程序可以直接访问和修改映射的数据,非常灵活
- 使用 DMA 时,数据传输后可以在用户空间进行处理,虽然不能在传输过程中修改数据,但可以在传输完成后自由操作数据
- 编程复杂度
- mmap 的使用较为简单,适合快速开发和实现
- DMA 的使用通常需要更复杂的设置和硬件支持,可能需要编写驱动程序或使用特定的库