零拷贝
零拷贝字面上的意思包括两个,“零”和“拷贝”:
- “拷贝”:就是指数据从一个存储区域转移到另一个存储区域。
- “零” :表示次数为0,它表示拷贝数据的次数为0。
- 常见的零拷贝方式有mmap,sendfile,dma,directI/O等。
(一)前置知识
1.内核态和用户态
用户态(User Mode)和内核态(Kernel Mode),也分别称为用户空间和内核空间,是现代操作系统中CPU执行的两种不同权限级别,它们在操作系统中扮演着至关重要的角色,确保系统的稳定性和安全性。
1.1用户态(User Mode):
这是应用程序运行的正常状态。大多数程序,如文本编辑器、浏览器等,都在用户态下执行。
在用户态下,程序可以访问自己的内存区域(用户空间),但不能直接访问系统核心资源,如硬件设备、系统内存、其他进程的地址空间等。用户态限制了程序的行为,防止错误或恶意代码破坏系统。
1.2内核态(Kernel Mode):
当需要执行特权操作时,如硬件访问、修改内存映射、处理中断等,CPU会从用户态切换到内核态。
在内核态下,程序可以执行任何机器指令,访问所有内存和硬件资源,因为此时操作系统内核具有完全的控制权。
内核态是操作系统内核执行任务的状态,包括系统调用的处理、驱动程序的执行等。
1.3关联与转换:
- 系统调用:应用程序通过调用操作系统提供的API(应用编程接口)来请求服务,如文件读写、网络通信等,这会导致从用户态切换到内核态。
- 异常:当程序执行遇到错误或特殊情况(如除零错误、页面错误)时,CPU会自动切换到内核态来处理这些异常。
- 外围设备中断:硬件设备完成操作或需要服务时,会通过中断信号通知CPU,CPU响应中断时也会进入内核态处理中断事件。
1.4.总结
用户态和内核态是操作系统隔离应用程序与系统核心功能的一种机制,确保了系统的稳定运行和安全性。两者之间的转换由硬件支持,并由操作系统精心管理,以在性能和安全之间取得平衡。
- 例如:Java程序中的主线程还是JVM(Java虚拟机)管理的守护线程(如垃圾回收线程),它们在执行常规的程序逻辑时,都运行在用户态(User Mode)。这意味着包括读取文件这样的操作在内的大部分任务,都是在用户态下发起请求,然后通过系统调用委托给操作系统内核完成实际的低级操作。但是当堆内存不足且JVM尝试扩展堆空间时,可能需要向操作系统请求更多的内存,这时就需要通过系统调用进入内核态来完成内存分配。此外,如果GC过程中涉及到的内存页当前不在物理内存中(即发生了页面错误),那么处理这一情况也可能需要内核介入,从而导致用户态到内核态的切换。
(二)I/O流程
1.Buffer I/O
Buffer I/O为了提高读写效率和保护磁盘,使用了页缓存机制(page cache),不过由于页缓存处于内核空间,不能被应用程序(用户进程)直接寻址,所以还需要将页缓存数据再拷贝到内存对应的用户空间中。
1.1 DMA
DMA(Direct Memory Access)直接内存访问,解决传统I/O需要CPU参与的问题。
1.2 mmap + write
mmap(memory map)内存映射,将内核态的页缓存(page cache)与用户态内存映射到一起,减少了一次内存拷贝。
1.3 sendfile
只传输静态文件,用户态不做干预,减少了一次数据拷贝,上下文切换少了两次。例如:访问web服务器的静态资源。
1.4 sendfile + DMA Scatter/Gather
优化了网络访问中,访问静态文件,静态文件在内核态的页缓存(page cache)与socket缓存(socket cache)之间的copy,由DMA将数据从读缓冲区拷贝到网卡。
2. Direct I/O
Direct I/O也叫直接I/O,本质上就是跳过页缓存(bypass page cache),直接将用户态buffer写入存储系统中(通过DMA),操作系统不会再做刷盘处理。
- 例如:Mysql就是使用Direct I/O,同时自己实现了一套缓存系统。
- Jaydio是一个实现了对Direct I/O支持的Java I/O库