0. 前言
目前很多技术效率高都用到了零拷贝技术,Netty,Nginx,kafka等。
要想了解零拷贝技术为什么快,就要知道和以前IO的区别和进步。
I/O:输入/输出是在主存和外部设备(如磁盘驱动器、终端和网络)直接拷贝数据的过程。
例如下边是两个函数命令
1.ssize_t read(int fd, void *buf, size_t n);
2.ssize_t write(int fd, const void *buf, size_t n)
1. DMA的作用
以read函数分析:
read函数从描述符为fd的当前文件位置拷贝最多n个字节到储存器位置buf。返回-1表示一个错误,而返回0表示EOF,否则,返回值表示是实际串送字节的数量。
在没有DMA时,read函数实际上会经过下边的步骤
- 用户进程调用read函数,用户态切换成内核态,CPU发送指令到磁盘控制器
- 磁盘控制器接收指令后,把数据放入磁盘控制器的内部缓冲区,产生一个中断。
- CPU收到中断指令后,把磁盘缓冲区的数据读进自己的寄存器,然后把寄存器的数据写入内存。内核态切换为用户态。
CPU需要参与数据搬运过程,如果数据量大的话,太消耗CPU资源了。
由此,出现了DMA技术。
先了解下什么是DMA
DMA(直接内存访问)Direct Memory Access:在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器。
同样的read命令。
- 用户进程调用read方法,CPU将请求发给DMA.
- DMA将请求发给磁盘
- 磁盘控制器接收指令后,把数据放入磁盘控制器的内部缓冲区,产生一个中断。
- DMA 收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中.
- 当DMA读取足够多的数据,就发送中断信号给CPU
- CPU 收到 DMA 的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回。
CPU不在参与数据搬运的工作,而是由DMA完成。
DMA技术的出现减少了CPU的占用和消耗,但是指令还是由CPU发出,一个read命令,两次上下文切换,两次数据拷贝
2. 文件传输
2.1. read+write
以正常一次读取磁盘文件发送数据为例,需要调用下列命令
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
发生了4次用户态和内核态的上下文切换(两次调用都是:用户态->内核态,内核完成任务后:内核态->用户态),4次数据的拷贝(磁盘数据->操作系统内核缓冲区->用户缓冲区->内核的socket的缓冲区->网卡的缓冲区)
文件传输的性能
- 减少上下文切换次数(每一次系统调用,都会经过两次上下文切换)
- 减少数据拷贝次数
优化方案
- mmap+write
- sendfile
2.2. mmap+witer
buf = mma(file,len)
mmap函数会直接吧内核缓冲区里的数据映射到用户空间,操作系统不需要进行任何数据的拷贝操作
发生了4次用户态和内核态的上下文切换(两次调用都是:用户态->内核态,内核完成任务后:内核态->用户态),3次数据的拷贝(磁盘数据->操作系统内核缓冲区(应用进程和操作系统共享)->内核的socket的缓冲区->网卡的缓冲区)
2.3. sendfile
linux内核版本2.1后
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。
发生了2次用户态和内核态的上下文切换(一次调用),3次数据的拷贝(磁盘数据->操作系统内核缓冲区->内核的socket的缓冲区->网卡的缓冲区)
网卡支持 SG-DMA技术,从 Linux 内核 2.4 版本开始起支持
2次数据的拷贝(磁盘数据->操作系统内核缓冲区->网卡的缓冲区)
这就是零拷贝,2次上下问切换,2次数据拷贝,全是DMA拷贝,没有CPU拷贝。
2.4. 对比
3. PageCache(磁盘高速缓存)
当文件传输时,第一步都是需要先把磁盘文件数据拷贝待内核缓存区(PageCache)
读写磁盘相比与读写内存速度慢太多了,所以要用读写磁盘替换为读写内存。
- PageCache来缓存最近被访问的数据,当空间不足时淘汰最久未被访问的缓存。
- PageCache使用可预读功能
因为局部性(刚访问的数据,短时间内再次访问的概率很高),所以缓存到PageCache里
PageCache来缓存最近被访问的数据,当空间不足时淘汰最久未被访问的缓存。
因为磁盘数据读取时(通过磁头旋转到数据所在的扇区,然后开始顺序读取)。旋转磁头非常耗时。预读功能(kafka顺序读写,很快)
读磁盘数据->先在PageCache找,找不到,再从磁盘中读取,缓存到PageCache。
4. 大文件传输
PageCache被大文件占据,其他热点小文件无法使用pageCache,磁盘读写下降
PageCache没有享受到缓存带来的好处,却耗费了DMA多拷贝到PageCache一次
异步IO,前半部分,内核向磁盘发起请求,不等数据就位,就返回;
后半部分,内核数据拷贝到缓冲区后,进程将接收到内核的通知,处理数据,绕开PageCache,直接IO;
异步IO+直接IO(绕过PageCache)。