Linux 下的实现
mmap 与write
tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

整个过程发生了
- 用户进程通过
mmap() 方法向操作系统发起调用,上下文从用户态转向内核态 DMA 控制器把数据从硬盘中拷贝到读缓冲区- 上下文从内核态转为用户态,
mmap 调用返回 - 用户进程通过
write() 方法发起调用,上下文从用户态转为内核态 CPU 将读缓冲区中数据拷贝到socket 缓冲区DMA 控制器把数据从socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,write() 返回
使用
- 为
SIGBUS 信号建立信号处理程序:当遇到SIGBUS 信号时,信号处理程序简单地返回,write 系统调用在被中断之前会返回已经写入的字节数,并且errno 会被设置成success, 但是这是一种糟糕的处理办法,因为你并没有解决问题的实质核心。 - 使用文件租借锁:通常我们使用这种方法,在文件描述符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的
RT_SIGNAL_LEASE 信号,告诉我们内核正在破坏你加持在文件上的读写锁。这样在程序访问非法内存并且被SIGBUS 杀死之前,你的write 系统调用会被中断。write 会返回已经写入的字节数,并且置errno 为success 。
我们应该在
if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
perror("kernel lease set signal");
return -1;
}
/* l_type can be F_RDLCK F_WRLCK 加锁*/
/* l_type can be F_UNLCK 解锁*/
if(fcntl(diskfd, F_SETLEASE, l_type)){
perror("kernel lease set type");
return -1;
}
sendfile
在内核版本
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
系统调用

整个过程发生了
- 用户进程通过
sendfile() 方法向操作系统发起调用,上下文从用户态转向内核态 DMA 控制器把数据从硬盘中拷贝到读缓冲区CPU 将读缓冲区中数据拷贝到socket 缓冲区DMA 控制器把数据从socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,sendfile 调用返回
sendfile+DMA Scatter/Gather

整个过程发生了
- 用户进程通过
sendfile() 方法向操作系统发起调用,上下文从用户态转向内核态 DMA 控制器利用scatter 把数据从硬盘中拷贝到读缓冲区离散存储CPU 把读缓冲区中的文件描述符和数据长度发送到socket 缓冲区DMA 控制器根据文件描述符和数据长度,使用scatter/gather 把数据从内核缓冲区拷贝到网卡sendfile() 调用返回,上下文从内核态切换回用户态
splice
#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_F_MOVE:尝试去移动数据而不是拷贝数据。这仅仅是对内核的一个小提示:如果内核不能从
pipe 移动数据或者pipe 的缓存不是一个整页面,仍然需要拷贝数据。Linux 最初的实现有些问题,所以从2.6.21 开始这个选项不起作用,后面的Linux 版本应该会实现。 - SPLICE_F_NONBLOCK:
splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的IO ,那么调用splice 有可能仍然被阻塞。 - SPLICE_F_MORE:后面的
splice 调用会有更多的数据。