网络系统
什么是零拷贝?
磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存10倍以上,所以针对优化磁盘技术非常多,比如零拷贝,直接IO,异步IO等,这些优化的目的就是为了提高系统的吞吐量,另外操作系统内核中的磁盘告诉缓存区可以有效减少磁盘访问次数
为什么要有DMA技术?
在没有DMA技术前,IO的过程是这样的:
- CPU发出对应指令给磁盘控制器,然后返回
- 磁盘控制器收到指令后,开始准备数据,会把数据放入到磁盘控制器内部的缓冲区,然后产生一个终端
- CPU收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一次一个字节的读进自己的寄存器,然后再把寄存器里的数据写入到内存,而在数据传输期间CPU是无法执行其他任务的
因此在进行IO设备和内存的数据传输的时候,数据搬运的工作全部交给DMA控制器,而CPU不再参与任何与数据搬运相关的事情
具体流程
- 用户进程调用read方法,向操作系统发出IO请求,请求读取数据到自己的内存缓冲区,进程进入阻塞状态
- 操作系统收到请求后,进一步将IO请求发送DMA,然后让CPU执行其他任务
- DMA进一步将IO请求发送给磁盘
- 磁盘收到DMA的IO请求后,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向DMA发起中断信号,告知自己缓冲区已满
- DMA收到磁盘信号,将磁盘控制缓冲区中的数据拷贝到内核缓冲区中,此时不占用CPU,CPU可以执行其他任务
- 当DMA读取了足够多的数据后,就会发中断信号给CPU
- CPU收到DMA信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回
CPU不再参与将数据从磁盘控制缓冲区搬运到内核空间的工作,这部分工作全程由DMA完成。
服务端的文件传输
如果服务端要提供文件传输功能,两个步骤:
- 将磁盘上的文件读取出来
- 通过网络协议发送给客户端
1 | read(file, tmp_buf, len); |
期间发生了4次用户态和内核态的上下文切换。
4次数据拷贝:其中两次是DMA拷贝,另外两次是通过CPU拷贝:
- 第一次拷贝:把磁盘上的数据拷贝到操作系统内核的缓冲区中,于是我们应用程序就可以使用这一部分数据了
- 第二次拷贝:把内核缓冲区的数据拷贝到用户缓冲区中,于是我们的应用程序就可以使用这部分数据了
- 第三次拷贝:把刚才拷贝到用户缓冲区里的数据,再拷贝到内核的socket的缓冲区里,这个过程依然是CPU搬运的
- 第四次拷贝:把内核的socket缓冲区里的数据,拷贝到网卡缓冲区里,这个过程是由DMA拷贝的
我们可以发现很多次的数据拷贝,同时存在冗余的上下文切换和数据拷贝,多了很多不必要的开销,影响系统性能。
所以,要提高文件传输的性能,必须减少用户态和内核态的上下文切换和内存拷贝的次数
如何减少数据拷贝次数?
在上面那个场景中,我们会历经4次数据拷贝,而因为我们并没有对数据再加工,所以数据实际上可以不用搬运到用户空间,因此用户的缓冲区是没有必要存在的。
这里存在两种零拷贝技术:
- mmap + write
- sendfile
mmap + write
read() 系统调用会把内核缓冲区的数据拷贝到用户缓冲区中,于是为了减少这一步开销,我们可以用mmap()替换read()系统调用函数。
1 | buf = mmap(file, len); |
mmap() 系统调用函数会直接把内核缓冲区里的数据映射到用户空i教案,这样操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
具体流程
- 应用进程调用了mmap后,DMA会把磁盘的数据拷贝到内核缓冲区里,接着,应用进程跟操作系统内核共享这个缓冲区
- 应用进程再调用write(), 操作系统直接将内核缓冲区的数据拷贝到socket缓冲区中,这一切都发生在内核态,由CPU来搬运书
- 最后,把内核的socket缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由DMA搬运的
sendfile可以替换前面的read和write两个系统调用,这样可以减少一次系统调用,减少了2次上下文切换的开销
真正的零拷贝技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有CPU来搬运数据,所以数据都是通过DMA来进行传输的。简单来说就是,网卡控制器可以直接将内核缓冲区的数据拷贝到网卡缓冲区中,减少了一次CPU的拷贝