Linux零拷贝技术,看完这篇文章就懂了( 二 )


解决这个问题通常使用文件的租借锁:首先为文件申请一个租借锁 , 当其他进程想要截断这个文件时 , 内核会发送一个实时的 RT_SIGNAL_LEASE 信号 , 告诉当前进程有进程在试图破坏文件 , 这样 write 在被 SIGBUS 杀死之前 , 会被中断 , 返回已经写入的字节数 , 并设置 errno 为 success 。
通常的做法是在 mmap 之前加锁 , 操作完之后解锁:
 
方法三:sendfile从Linux 2.1版内核开始 , Linux引入了sendfile , 也能减少一次拷贝 。
#include<sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);sendfile 是只发生在内核态的数据传输接口 , 没有用户态的参与 , 自然避免了用户态数据拷贝 。它指定在 in_fd 和 out_fd 之间传输数据 , 其中 , 它规定 in_fd 指向的文件必须是可以 mmap 的 , out_fd 必须指向一个套接字 , 也就是规定数据只能从文件传输到套接字 , 反之则不行 。sendfile 不存在像 mmap 时文件被截获的情况 , 它自带异常处理机制 。

Linux零拷贝技术,看完这篇文章就懂了

文章插图
缺陷:
1)只能适用于那些不需要用户态处理的应用程序 。
 
方法四:DMA 辅助的 sendfile常规 sendfile 还有一次内核态的拷贝操作 , 能不能也把这次拷贝给去掉呢?
答案就是这种 DMA 辅助的 sendfile 。
这种方法借助硬件的帮助 , 在数据从内核缓冲区到 socket 缓冲区这一步操作上 , 并不是拷贝数据 , 而是拷贝缓冲区描述符 , 待完成后 , DMA 引擎直接将数据从内核缓冲区拷贝到协议引擎中去 , 避免了最后一次拷贝 。
Linux零拷贝技术,看完这篇文章就懂了

文章插图
缺陷:
1)除了3.4 中的缺陷 , 还需要硬件以及驱动程序支持 。
2)只适用于将数据从文件拷贝到套接字上 。
 
方法五:splicesplice 去掉 sendfile 的使用范围限制 , 可以用于任意两个文件描述符中传输数据 。
#define _GNU_SOURCE /* See feature_test_macros(7) */#include <fcntl.h>ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);但是 splice 也有局限 , 它使用了 Linux 的管道缓冲机制 , 所以 , 它的两个文件描述符参数中至少有一个必须是管道设备 。
splice 提供了一种流控制的机制 , 通过预先定义的水印(watermark)来阻塞写请求 , 有实验表明 , 利用这种方法将数据从一个磁盘传输到另外一个磁盘会增加 30%-70% 的吞吐量 , CPU负责也会减少一半 。
缺陷:
1)同样只适用于不需要用户态处理的程序
2)传输描述符至少有一个是管道设备 。
 
方法六:写时复制在某些情况下 , 内核缓冲区可能被多个进程所共享 , 如果某个进程想要这个共享区进行 write 操作 , 由于 write 不提供任何的锁操作 , 那么就会对共享区中的数据造成破坏 , 写时复制就是 Linux 引入来保护数据的 。
写时复制 , 就是当多个进程共享同一块数据时 , 如果其中一个进程需要对这份数据进行修改 , 那么就需要将其拷贝到自己的进程地址空间中 , 这样做并不影响其他进程对这块数据的操作 , 每个进程要修改的时候才会进行拷贝 , 所以叫写时拷贝 。这种方法在某种程度上能够降低系统开销 , 如果某个进程永远不会对所访问的数据进行更改 , 那么也就永远不需要拷贝 。
缺陷:
需要 MMU 的支持 , MMU 需要知道进程地址空间中哪些页面是只读的 , 当需要往这些页面写数据时 , 发出一个异常给操作系统内核 , 内核会分配新的存储空间来供写入的需求 。
 
方法七:缓冲区共享这种方法完全改写 I/O 操作 , 因为传统 I/O 接口都是基于数据拷贝的 , 要避免拷贝 , 就去掉原先的那套接口 , 重新改写 , 所以这种方法是比较全面的零拷贝技术 , 目前比较成熟的一个方案是最先在 Solaris 上实现的 fbuf (Fast Buffer , 快速缓冲区) 。


推荐阅读