南桥中的DMA控制器是个什么东西?
我们曾经在硬件基础知识中讲过:CPU直接从磁盘/网卡中读/写取数据到内核(操作系统)缓冲区【磁盘高速缓存(PageCache)】会降低CPU效率,而这个工作CPU会交给南桥中的DMA控制器完成的。
当我们要将磁盘中的文件发送到互联网时,操作系统是怎么做的?
- 我的程序很简单:两个系统调用:
syscall.Read(fd int, p []byte)
syscall.Write(fd int, p []byte)
- 但这两个系统调用背后发生的事情却不简单:
– 两个系统调用
– 四次数据拷贝
– 四次CPU用户态与内核态的上下文的切换
四次数据拷贝:
- 第一次拷贝,把磁盘上的数据拷贝到操作系统内核的缓冲区里,这个拷贝的过程是通过 DMA 搬运的。
- 第二次拷贝,把内核缓冲区的数据拷贝到用户的缓冲区里,于是我们应用程序就可以使用这部分数据了,这个拷贝到过程是由 CPU 完成的。
- 第三次拷贝,把刚才拷贝到用户的缓冲区里的数据,再拷贝到内核的 socket 的缓冲区里,这个过程依然还是由 CPU 搬运的。
- 第四次拷贝,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程又是由 DMA 搬运的。
发现问题:
在数据传输的场景中,用户并不会对数据进行「再加工」,所以没必要将数据拷贝到用户缓冲区。针对这个问题,产生一种技术:零拷贝
零拷贝
零拷贝技术实现的方式通常有 2 种:
- mmap + write
- sendfile
mmap + write
系统调用:
syscall.Mmap(fd int, offset int64, length int, prot int, flags int)
syscall.Write(fd int, p []byte)
mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
具体过程如下:
- 应用进程调用了 mmap() 后,DMA 会把磁盘的数据拷贝到内核的缓冲区里。接着,应用进程跟操作系统内核「共享」这个缓冲区;
- 应用进程再调用 write(),操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据;
- 最后,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由 DMA 搬运的。
我们可以得知,通过使用 mmap() 来代替 read(), 可以减少一次数据拷贝的过程。
sendfile
在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile(),函数形式如下:
syscall.Sendfile(outfd int, infd int, offset *int64, count int)
从 Linux 内核 2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用更高效了:
- 通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
- 缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝.
零拷贝的应用
Kafka
Kafka 这个开源项目,就利用了「零拷贝」技术,从而大幅提升了 I/O 的吞吐率,这也是 Kafka 在处理海量数据为什么这么快的原因之一。
Nginx
Nginx 也支持零拷贝技术,一般默认是开启零拷贝技术,这样有利于提高文件传输的效率,是否开启零拷贝技术的配置如下:
http {
...
sendfile on
...
}
延伸:零拷贝是基于 磁盘高速缓存(PageCache)
「内核缓冲区」实际上是磁盘高速缓存(PageCache)
PageCache 的优点主要是两个:
- 缓存最近被访问的数据;
- 预读功能;