重新写一下,加深理解。
什么场景解决什么问题
在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能。这时候服务端的任务是:将服务端主机磁盘中的文件不做修改地从已连接的socket发出去。
while((n = read(diskfd, buf, BUF_SIZE)) > 0)
write(sockfd, buf , n);
执行过程
具体流程如下:
- 用户进程通过read()方法向操作系统发起调用,此时上下文从用户态转向内核态
- DMA控制器把数据从硬盘中拷贝到读缓冲区
- CPU把读缓冲区数据拷贝到应用缓冲区,上下文从内核态转为用户态,read()返回
- 用户进程通过write()方法发起调用,上下文从用户态转为内核态
- CPU将应用缓冲区中数据拷贝到socket缓冲区
- DMA控制器把数据从socket缓冲区拷贝到网卡,上下文从内核态切换回用户态,write()返回
存在的问题
整个过程发生了4次用户态和内核态的上下文切换和4次数据拷贝
什么是零拷贝
零拷贝技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
一种零拷贝:mmap+write
mmap+write简单来说就是使用mmap替换了read+write中的read操作,减少了一次CPU的拷贝。
mmap主要实现方式是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,从而减少了从读缓冲区到用户缓冲区的一次CPU拷贝。
执行过程
- 调用mmap,磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝
- 调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态
- 最后,DMA控制器把socket缓冲区数据拷贝到网卡。
mmap
#include <sys/mman.h>
void * mmap(void *addr,size_t length, int prot, int flags, int fd,off_t offset)
addr: 指定映射区的开始地址
如果将addr指定为NULL,那么内核会为映射分配一个合适的地址。如果addr为一个非NULL值,则内核在选择地址映射时会将该参数值作为一个提示信息来处理。不管采用何种方式,内核会选择一个不与任何既有映射冲突的地址。在处理过程中, 内核会将指定的地址舍入到最近的一个分页边界处。
length:参数指定了映射的字节数。
尽管length 无需是一个系统分页大小的倍数,但内核会以分页大小为单位来创建映射,因此实际上length会被向上提升为分页大小的下一个倍数。
prot: 参数掩码,用于指定映射上的保护信息:标记有:
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
返回说明:
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。
this->memory_map_ = (char*)mmap(NULL, this->file_size_, (PROT_READ | PROT_WRITE), MAP_SHARED, this->file_fd_, 0);
一种零拷贝:sendfile
mmap虽然减少了一次用户态和内核态的CPU拷贝,但是在内核空间内仍然有一次CPU拷贝。
mmap对大文件传输有一定优势,但是小文件可能出现碎片,并且在多个进程同时操作文件时可能产生引发coredump的signal。
sendfile方式只使用一个函数就可以完成之前的read+write 和 mmap+write的功能,这样就少了2次状态切换,由于数据不经过用户缓冲区,因此该数据无法被修改。
sendfile
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
执行过程
1.DMA拷贝数据到内核缓冲区
- CPU拷贝数据到socket缓冲区
- DMA拷贝数据到网卡
但是sendfile在内核缓冲区和socket缓冲区仍然存在一次CPU拷贝,或许这个还可以优化。
第一次写的在这里:https://www.jianshu.com/p/0356c32fb595
Reference
[1] https://www.jianshu.com/p/d5bb7d18723c
[2] https://cloud.tencent.com/developer/article/1652533
[3] mmap说明
[4] mmap说明
[5] 零拷贝原理
网友评论