🍁基于面试的常见问题,学习总结输出的系列文章。可供面试参考。
本篇是零拷贝的下篇。是对上一篇一些知识点的补充。
一、虚拟内存
虚拟内存为每个进程提供私有的地址空间,每个进程拥有一片连续完整的内存空间。
虚拟内存则是指将硬盘的一块区域划分来作为内存。内存主要作用是在计算机运行时为操作系统和各种程序提供临时储存
虚拟内存是计算机的内存管理技术,虚拟内存是一种计算机技术,它让应用程序以为自己拥有一段连续的可用内存空间,实际上物理上是不连续的碎片。大多数操作系统都使用虚拟内存,比如 Windows 的虚拟内存和 Linux 的交换空间。
一般来说不同进程里的同一个虚拟地址指向的物理地址是不一样的,所以离开进程谈虚拟内存没有任何意义。
每个用户进程维护了一个单独的页表(Page Table),虚拟内存和物理内存就是通过这个页表实现地址空间的映射的。
其中页表(Page Table)可以简单地理解为单个内存映射(Memory Mapping)的链表(当然实际结构很复杂),里面的每个内存映射(Memory Mapping)都将一块虚拟地址映射到一个特定的地址空间(物理内存或者磁盘存储空间)。每个进程拥有自己的页表(Page Table),和其他进程的页表(Page Table)没有关系。
二、操作系统基础
注:本文的上下文切换,特指用户态与内核态上下文切换。
中断逻辑
- 外部中断:CPU 外部的中断:键盘、鼠标事件、IO事件、时钟控制台中断等。
- 内部中断:CPU 内部的中断:软中断和异常,如非法操作码、地址越界、算术溢出,除 0 等
内核态 && 用户态
- 内核态:对系统存储、外部设备进行访问。对应内核空间
- 用户态:应用进程,应用程序。对应用户空间
应用进程需要调用硬件资源,通过系统调用,再由内核态去实现。也因此需要上下文切换,1 次系统调用过程会经历 2 次上下文切换过程。(用户态-> 内核态;完成后,内核态 -> 用户态)。
存在大量的切换过程(高并发场景),对于整个系统来讲,消耗是不低的。(积少成多)。
用户空间和内核空间相互隔离,用户空间 crash。并不会影响到内核空间。从安全等考虑,用户空间不能直接操作磁盘等硬件资源。
DMA
外设和系统内存之间进行双向数据传输的硬件机制。 CPU 不参与数据拷贝。
直接 IO
当内核不需要对数据进行处理的时候。 程序能够直接访问硬件存储,直接将硬件上的数据拷贝至用户空间。 在 Java 中的技术是:allocateDirect(int capacity)。 过程有上下文的切换
二、mmap 技术理解
mmap 是一种内存映射文件的机制。省去了从内核缓冲区复制到用户空间的过程,可直接使用。
mmap是一个将文件映射到内存里的系统调用,这样就可以直接读写内存,无需再通过文件访问的方式进行流式操作。mmap可以将文件映射到多个进程共享,也可以建立私有映射。进程就可以采用指针的方式读写操作这一段内存。
简言之:应用层对于文件访问等于访问内存
文件映射到用户空间里的虚拟内存,文件的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件。效率很高!
减少了一次用户态和内核态的 CPU 拷贝,但是在内核空间内仍然有一次 CPU 拷贝。 图片来源
- mmap 向应用程序提供的内存访问接口是内存地址连续的,但是对应的磁盘文件的 block 可以不是地址连续的
- mmap 提供的内存空间是虚拟空间(虚拟内存),而不是物理空间(物理内存),因此完全可以分配远远大于物理内存大小的虚拟空间
三、PageCache 技术简单理解
PageCache(页缓存), PageCache 会缓存最近访问的数据,提升性能。
避免每次读写文件时,都需要对硬盘进行读写操作,Linux 内核使用 页缓存(Page Cache)
机制来对文件中的数据进行缓存。
为了提升对文件的读写效率,Linux 内核会以页大小(4KB)为单位,将文件划分为多个数据块。当用户对文件中的某个数据块进行读写操作时,内核首先会申请一个内存页(称为 页缓存
)与文件中的数据块进行绑定
当用户对文件进行读写时,实际上是对文件的 页缓存
进行读写 (-脏页: 当内存数据页跟磁盘数据页内容不一致的时候,称这个内存页为"脏页"。)
四、浅谈应用(Java场景)
Java#transfer
在 Java 中实现零拷贝的方法: FileChannel.transferTo
Java
File file = new File(FILE_PATH);
FileChannel fileChannel = new FileInputStream(file).getChannel();
fileChannel.transferTo(0, file.length(), socketChannel);
transferTo 对应 linux 中的 sendfile
应用:
- Tomcat 文件拷贝时也用到 transferTo。
- kafka 在客户端和 broker 进行数据传输时,会使用 transferTo 和 transferFrom 方法。
- Netty
Java#mmap
NIO 提供了 MappedByteBuffer 用来支持 mmap。它与常用的 DirectByteBuffer 一样,都是在堆外内存分配空间。(HeapByteBuffer 在堆内内存分配空间)。
应用:
- RocketMQ
最后小结
本文是对零拷贝学习总结的文章。至此对于零拷贝的学习结束了。
推荐阅读: